Пример #1
0
KineticStatus KineticClient_Connect(const KineticSession* config,
                                    KineticSessionHandle* handle)
{
    if (handle == NULL) {
        LOG0("Session handle is NULL!");
        return KINETIC_STATUS_SESSION_EMPTY;
    }
    *handle = KINETIC_HANDLE_INVALID;

    if (config == NULL) {
        LOG0("KineticSession is NULL!");
        return KINETIC_STATUS_SESSION_EMPTY;
    }

    if (strlen(config->host) == 0) {
        LOG0("Host is empty!");
        return KINETIC_STATUS_HOST_EMPTY;
    }

    if (config->hmacKey.len < 1 || config->hmacKey.data == NULL) {
        LOG0("HMAC key is NULL or empty!");
        return KINETIC_STATUS_HMAC_EMPTY;
    }

    // Obtain a new connection/handle
    *handle = KineticConnection_NewConnection(config);
    if (*handle == KINETIC_HANDLE_INVALID) {
        LOG0("Failed connecting to device!");
        return KINETIC_STATUS_SESSION_INVALID;
    }
    KineticConnection* connection = KineticConnection_FromHandle(*handle);
    if (connection == NULL) {
        LOG0("Failed getting valid connection from handle!");
        return KINETIC_STATUS_CONNECTION_ERROR;
    }

    // Create the connection
    KineticStatus status = KineticConnection_Connect(connection);
    if (status != KINETIC_STATUS_SUCCESS) {
        LOGF0("Failed creating connection to %s:%d", config->host, config->port);
        KineticConnection_FreeConnection(handle);
        *handle = KINETIC_HANDLE_INVALID;
        return status;
    }

    // Wait for initial unsolicited status to be received in order to obtain connectionID
    while(connection->connectionID == 0) {sleep(1);}

    return status;
}
Пример #2
0
KineticStatus KineticClient_CreateSession(KineticSessionConfig* const config,
    KineticClient * const client, KineticSession** session)
{
    if (config == NULL) {
        LOG0("KineticSessionConfig is NULL!");
        return KINETIC_STATUS_SESSION_INVALID;
    }

    if (session == NULL) {
        LOG0("Pointer to KineticSession pointer is NULL!");
        return KINETIC_STATUS_SESSION_EMPTY;
    }

    if (strlen(config->host) == 0) {
        LOG0("Host is empty!");
        return KINETIC_STATUS_HOST_EMPTY;
    }

    if (config->hmacKey.len < 1 || config->hmacKey.data == NULL)
    {
        LOG0("HMAC key is NULL or empty!");
        return KINETIC_STATUS_HMAC_REQUIRED;
    }

    // Create a new session
    KineticSession* s = KineticAllocator_NewSession(client->bus, config);
    if (s == NULL) {
        LOG0("Failed to create session instance!");
        return KINETIC_STATUS_MEMORY_ERROR;
    }
    KineticStatus status = KineticSession_Create(s, client);
    if (status != KINETIC_STATUS_SUCCESS) {
        LOG0("Failed to create session instance!");
        KineticAllocator_FreeSession(s);
        return status;
    }

    // Establish the connection
    status = KineticSession_Connect(s);
    if (status != KINETIC_STATUS_SUCCESS) {
        LOGF0("Failed creating connection to %s:%d", config->host, config->port);
        KineticAllocator_FreeSession(s);
        return status;
    }

    *session = s;

    return status;
}
Пример #3
0
KineticStatus bus_to_kinetic_status(bus_send_status_t const status)
{
    KineticStatus res = KINETIC_STATUS_INVALID;

    switch(status) {
    // TODO scrutinize all these mappings
    case BUS_SEND_SUCCESS:
        res = KINETIC_STATUS_SUCCESS;
        break;
    case BUS_SEND_TX_TIMEOUT_NOTIFYING_LISTENER:
    case BUS_SEND_TX_TIMEOUT:
        res = KINETIC_STATUS_SOCKET_TIMEOUT;
        break;
    case BUS_SEND_TX_FAILURE:
        res = KINETIC_STATUS_SOCKET_ERROR;
        break;
    case BUS_SEND_RX_TIMEOUT:
        res = KINETIC_STATUS_OPERATION_TIMEDOUT;
        break;
    case BUS_SEND_RX_FAILURE:
        res = KINETIC_STATUS_SOCKET_ERROR;
        break;
    case BUS_SEND_BAD_RESPONSE:
        res = KINETIC_STATUS_SOCKET_ERROR;
        break;
    case BUS_SEND_UNREGISTERED_SOCKET:
        res = KINETIC_STATUS_SOCKET_ERROR;
        break;
    case BUS_SEND_RX_TIMEOUT_EXPECT:
        res = KINETIC_STATUS_OPERATION_TIMEDOUT;
        break;
    case BUS_SEND_UNDEFINED:
    default:
    {
        LOGF0("bus_to_kinetic_status: UNMATCHED %d", status);
        KINETIC_ASSERT(false);
        return KINETIC_STATUS_INVALID;
    }
    }

    LOGF3("bus_to_kinetic_status: mapping status %d => %d",
          status, res);
    return res;
}
Пример #4
0
void KineticController_HandleResult(bus_msg_result_t *res, void *udata)
{
    KineticOperation* op = udata;
    KINETIC_ASSERT(op);
    KINETIC_ASSERT(op->session);

    KineticStatus status = bus_to_kinetic_status(res->status);

    if (status == KINETIC_STATUS_SUCCESS) {
        KineticResponse * response = res->u.response.opaque_msg;

        status = KineticResponse_GetStatus(response);

        LOGF2("[PDU RX] pdu: %p, session: %p, bus: %p, "
              "fd: %6d, seq: %8lld, protoLen: %8u, valueLen: %8u, op: %p, status: %s",
              (void*)response,
              (void*)op->session, (void*)op->session->messageBus,
              op->session->socket, response->command->header->acksequence,
              KineticResponse_GetProtobufLength(response),
              KineticResponse_GetValueLength(response),
              (void*)op,
              Kinetic_GetStatusDescription(status));
        KineticLogger_LogHeader(3, &response->header);
        KineticLogger_LogProtobuf(3, response->proto);

        if (op->response == NULL) {
            op->response = response;
        }
    } else {
        LOGF0("Error receiving response, got message bus error: %s", bus_error_string(res->status));
        if (res->status == BUS_SEND_RX_TIMEOUT) {
            LOG0("RX_TIMEOUT");
        }
    }

    // Call operation-specific callback, if configured
    if (op->opCallback != NULL) {
        status = op->opCallback(op, status);
    }

    KineticOperation_Complete(op, status);
}
Пример #5
0
KineticStatus KineticSocket_Write(int socket, ByteBuffer* src)
{
    LOGF3("Writing %zu bytes to socket...", src->bytesUsed);
    for (unsigned int bytesSent = 0; bytesSent < src->bytesUsed;) {
        int bytesRemaining = src->bytesUsed - bytesSent;
        int status = write(socket, &src->array.data[bytesSent], bytesRemaining);
        if (status == -1 &&
            ((errno == EINTR) || (errno == EAGAIN) || (errno == EWOULDBLOCK))) {
            LOG2("Write interrupted. retrying...");
            continue;
        }
        else if (status <= 0) {
            LOGF0("Failed to write to socket! status=%d, errno=%d\n", status, errno);
            return KINETIC_STATUS_SOCKET_ERROR;
        }
        else {
            bytesSent += status;
            LOGF2("Wrote %d bytes (%d of %zu sent)", status, bytesSent, src->bytesUsed);
        }
    }
    LOG3("Socket write completed successfully");
    return KINETIC_STATUS_SUCCESS;
}
Пример #6
0
void test_KineticHMAC_Populate_should_compute_and_populate_the_SHA1_HMAC_for_the_supplied_message_and_key(void)
{
    KineticHMAC actual;
    Com__Seagate__Kinetic__Proto__Message msg = COM__SEAGATE__KINETIC__PROTO__MESSAGE__INIT;
    Com__Seagate__Kinetic__Proto__Message__HMACauth hmacAuth = COM__SEAGATE__KINETIC__PROTO__MESSAGE__HMACAUTH__INIT;
    uint8_t data[KINETIC_HMAC_MAX_LEN];
    ProtobufCBinaryData hmac = {.len = KINETIC_HMAC_MAX_LEN, .data = data};
    const ByteArray key = ByteArray_CreateWithCString("1234567890ABCDEFGHIJK");
    uint8_t commandBytes[123];
    ByteArray commandArray = ByteArray_Create(commandBytes, sizeof(commandBytes));
    ByteArray_FillWithDummyData(commandArray);
    ProtobufCBinaryData dummyCommandData = {.data = commandArray.data, .len = commandArray.len};

    msg.commandbytes = dummyCommandData;
    msg.has_commandbytes = true;
    msg.authtype = COM__SEAGATE__KINETIC__PROTO__MESSAGE__AUTH_TYPE__HMACAUTH;
    msg.has_authtype = true;
    hmacAuth.has_hmac = true;
    hmacAuth.hmac = hmac;
    msg.hmacauth = &hmacAuth;

    KineticHMAC_Init(&actual, COM__SEAGATE__KINETIC__PROTO__COMMAND__SECURITY__ACL__HMACALGORITHM__HmacSHA1);
    KineticHMAC_Populate(&actual, &msg, key);

    TEST_ASSERT_TRUE(msg.hmacauth->has_hmac);
    TEST_ASSERT_EQUAL_PTR(hmac.data, msg.hmacauth->hmac.data);
    TEST_ASSERT_EQUAL(KINETIC_HMAC_MAX_LEN, msg.hmacauth->hmac.len);

    LOG0("Computed HMAC: ");
    LOGF0("  %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
         actual.data[0],  actual.data[1],  actual.data[2],  actual.data[3],
         actual.data[4],  actual.data[5],  actual.data[6],  actual.data[7]);
    LOGF0("  %02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX%02hhX",
         actual.data[8],  actual.data[9],  actual.data[10], actual.data[11],
         actual.data[12], actual.data[13], actual.data[14], actual.data[15]);
    LOGF0("  %02hhX%02hhX%02hhX%02hhX",
         actual.data[16], actual.data[17], actual.data[18], actual.data[19]);
}

void test_KineticHMAC_Validate_should_return_true_if_the_HMAC_for_the_supplied_message_and_key_is_correct(void)
{
    KineticHMAC actual;
    Com__Seagate__Kinetic__Proto__Command__Status status = COM__SEAGATE__KINETIC__PROTO__COMMAND__STATUS__INIT;
    Com__Seagate__Kinetic__Proto__Command command = COM__SEAGATE__KINETIC__PROTO__COMMAND__INIT;
    status.code = COM__SEAGATE__KINETIC__PROTO__COMMAND__STATUS__STATUS_CODE__NO_SPACE;
    status.has_code = true;
    command.status = &status;
    Com__Seagate__Kinetic__Proto__Message proto = COM__SEAGATE__KINETIC__PROTO__MESSAGE__INIT;
    Com__Seagate__Kinetic__Proto__Message__HMACauth hmacAuth = COM__SEAGATE__KINETIC__PROTO__MESSAGE__HMACAUTH__INIT;
    uint8_t data[KINETIC_HMAC_MAX_LEN];
    ProtobufCBinaryData hmac = {.len = KINETIC_HMAC_MAX_LEN, .data = data};
    const ByteArray key = ByteArray_CreateWithCString("1234567890ABCDEFGHIJK");
    proto.has_commandbytes = true;
    uint8_t packedCmd[128];
    size_t packedLen = com__seagate__kinetic__proto__command__pack(&command, packedCmd);
    proto.commandbytes = (ProtobufCBinaryData){.data = packedCmd, .len = packedLen};
    hmacAuth.identity = 7;
    hmacAuth.has_identity = true;
    hmacAuth.hmac = hmac;
    hmacAuth.has_hmac = true;
    proto.hmacauth = &hmacAuth;
    proto.authtype = COM__SEAGATE__KINETIC__PROTO__MESSAGE__AUTH_TYPE__HMACAUTH;
    proto.has_authtype = true;

    KineticHMAC_Init(&actual, COM__SEAGATE__KINETIC__PROTO__COMMAND__SECURITY__ACL__HMACALGORITHM__HmacSHA1);
    KineticHMAC_Populate(&actual, &proto, key);

    TEST_ASSERT_TRUE(KineticHMAC_Validate(&proto, key));
}
Пример #7
0
KineticStatus KineticBuilder_BuildUpdateFirmware(KineticOperation* const op, const char* fw_path)
{
    KineticOperation_ValidateOperation(op);

    KineticStatus status = KINETIC_STATUS_INVALID;
    FILE* fp = NULL;

    if (fw_path == NULL) {
        LOG0("ERROR: FW update file was NULL");
        status = KINETIC_STATUS_INVALID_FILE;
        goto cleanup;
    }

    fp = fopen(fw_path, "r");
    if (fp == NULL) {
        LOG0("ERROR: Specified FW update file could not be opened");
        return KINETIC_STATUS_INVALID_FILE;
        goto cleanup;
    }

    if (fseek(fp, 0L, SEEK_END) != 0) {
        LOG0("ERROR: Specified FW update file could not be seek");
        status = KINETIC_STATUS_INVALID_FILE;
        goto cleanup;
    }

    long len = ftell(fp);
    if (len < 1) {
        LOG0("ERROR: Specified FW update file could not be queried for length");
        status = KINETIC_STATUS_INVALID_FILE;
        goto cleanup;
    }
    if (fseek(fp, 0L, SEEK_SET) != 0) {
        LOG0("ERROR: Specified FW update file could not be seek back to start");
        status = KINETIC_STATUS_INVALID_FILE;
        goto cleanup;
    }

    op->value.data = calloc(len, 1);
    if (op->value.data == NULL) {
        LOG0("ERROR: Failed allocating memory to store FW update image");
        status = KINETIC_STATUS_MEMORY_ERROR;
        goto cleanup;
    }

    size_t read = fread(op->value.data, 1, len, fp);
    if ((long)read != len) {
        LOGF0("ERROR: Expected to read %ld bytes from FW file, but read %zu", len, read);
        status = KINETIC_STATUS_INVALID_FILE;
        goto cleanup;
    }
    fclose(fp);

    op->value.len = len;
    
    op->request->message.command.header->messagetype = COM__SEAGATE__KINETIC__PROTO__COMMAND__MESSAGE_TYPE__SETUP;
    op->request->message.command.header->has_messagetype = true;
    op->request->command->body = &op->request->message.body;
    
    op->request->command->body->setup = &op->request->message.setup;
    op->request->command->body->setup->firmwaredownload = true;
    op->request->command->body->setup->has_firmwaredownload = true;

    op->opCallback = &KineticCallbacks_UpdateFirmware;

    return KINETIC_STATUS_SUCCESS;

cleanup:
    if (fp != NULL) {
        fclose(fp);
    }
    return status;
}
Пример #8
0
Com__Seagate__Kinetic__Proto__Command__P2POperation* build_p2pOp(uint32_t nestingLevel, KineticP2P_Operation const * const p2pOp)
{
    // limit nesting level to KINETIC_P2P_MAX_NESTING
    if (nestingLevel >= KINETIC_P2P_MAX_NESTING) {
        LOGF0("P2P op nesting level is too deep. Max is %d.", KINETIC_P2P_MAX_NESTING);
        return NULL;
    }

    Com__Seagate__Kinetic__Proto__Command__P2POperation* proto_p2pOp = calloc(1, sizeof(Com__Seagate__Kinetic__Proto__Command__P2POperation));
    if (proto_p2pOp == NULL) { goto error_cleanup; }

    com__seagate__kinetic__proto__command__p2_poperation__init(proto_p2pOp);

    proto_p2pOp->peer = calloc(1, sizeof(Com__Seagate__Kinetic__Proto__Command__P2POperation__Peer));
    if (proto_p2pOp->peer == NULL) { goto error_cleanup; }

    com__seagate__kinetic__proto__command__p2_poperation__peer__init(proto_p2pOp->peer);

    proto_p2pOp->peer->hostname = p2pOp->peer.hostname;
    proto_p2pOp->peer->has_port = true;
    proto_p2pOp->peer->port = p2pOp->peer.port;
    proto_p2pOp->peer->has_tls = true;
    proto_p2pOp->peer->tls = p2pOp->peer.tls;

    proto_p2pOp->n_operation = p2pOp->numOperations;
    proto_p2pOp->operation = calloc(p2pOp->numOperations, sizeof(Com__Seagate__Kinetic__Proto__Command__P2POperation__Operation*));
    if (proto_p2pOp->operation == NULL) { goto error_cleanup; }

    for(size_t i = 0; i < proto_p2pOp->n_operation; i++) {
        KINETIC_ASSERT(!ByteBuffer_IsNull(p2pOp->operations[i].key)); // TODO return invalid operand?
        
        Com__Seagate__Kinetic__Proto__Command__P2POperation__Operation * p2p_op_op = calloc(1, sizeof(Com__Seagate__Kinetic__Proto__Command__P2POperation__Operation));
        if (p2p_op_op == NULL) { goto error_cleanup; }

        com__seagate__kinetic__proto__command__p2_poperation__operation__init(p2p_op_op);

        p2p_op_op->has_key = true;
        p2p_op_op->key.data = p2pOp->operations[i].key.array.data;
        p2p_op_op->key.len = p2pOp->operations[i].key.bytesUsed;

        p2p_op_op->has_newkey = !ByteBuffer_IsNull(p2pOp->operations[i].newKey);
        p2p_op_op->newkey.data = p2pOp->operations[i].newKey.array.data;
        p2p_op_op->newkey.len = p2pOp->operations[i].newKey.bytesUsed;

        p2p_op_op->has_version = !ByteBuffer_IsNull(p2pOp->operations[i].version);
        p2p_op_op->version.data = p2pOp->operations[i].version.array.data;
        p2p_op_op->version.len = p2pOp->operations[i].version.bytesUsed;

        // force if no version was specified
        p2p_op_op->has_force = ByteBuffer_IsNull(p2pOp->operations[i].version);
        p2p_op_op->force = ByteBuffer_IsNull(p2pOp->operations[i].version);

        if (p2pOp->operations[i].chainedOperation == NULL) {
            p2p_op_op->p2pop = NULL;
        } else {
            p2p_op_op->p2pop = build_p2pOp(nestingLevel + 1, p2pOp->operations[i].chainedOperation);
            if (p2p_op_op->p2pop == NULL) { goto error_cleanup; }
        }

        p2p_op_op->status = NULL;

        proto_p2pOp->operation[i] = p2p_op_op;
    }
    return proto_p2pOp;

error_cleanup:
    KineticAllocator_FreeP2PProtobuf(proto_p2pOp);
    return NULL;
}
Пример #9
0
int KineticSocket_Connect(const char* host, int port)
{
    char port_str[32];
    struct addrinfo hints;
    struct addrinfo* ai_result = NULL;
    struct addrinfo* ai = NULL;
    socket99_result result;

    // Setup server address info
    socket99_config cfg = {
        .host = (char*)host,
        .port = port,
        .nonblocking = true,
    };
    sprintf(port_str, "%d", port);

    // Open socket
    LOGF1("Connecting to %s:%d", host, port);
    if (!socket99_open(&cfg, &result)) {
        char err_buf[256];
        socket99_snprintf(err_buf, 256, &result);
        LOGF0("Failed to open socket connection with host: %s", err_buf);
        return KINETIC_SOCKET_DESCRIPTOR_INVALID;
    }

    // Configure the socket
    socket99_set_hints(&cfg, &hints);
    if (getaddrinfo(cfg.host, port_str, &hints, &ai_result) != 0) {
        LOGF0("Failed to get socket address info: errno %d", errno);
        close(result.fd);
        return KINETIC_SOCKET_DESCRIPTOR_INVALID;
    }

    KineticSocket_EnableTCPNoDelay(result.fd);

    for (ai = ai_result; ai != NULL; ai = ai->ai_next) {
        int setsockopt_result;
        int buffer_size = KINETIC_OBJ_SIZE;

#if defined(SO_NOSIGPIPE) && !defined(__APPLE__)
        // On BSD-like systems we can set SO_NOSIGPIPE on the socket to
        // prevent it from sending a PIPE signal and bringing down the whole
        // application if the server closes the socket forcibly
        int enable = 1;
        setsockopt_result = setsockopt(result.fd,
                                       SOL_SOCKET, SO_NOSIGPIPE,
                                       &enable, sizeof(enable));
        // Allow ENOTSOCK because it allows tests to use pipes instead of
        // real sockets
        if (setsockopt_result != 0 && setsockopt_result != ENOTSOCK) {
            LOG0("Failed to set SO_NOSIGPIPE on socket");
            continue;
        }
#endif

        // Increase send buffer to KINETIC_OBJ_SIZE
        // Note: OS allocates 2x this value for its overhead
        setsockopt_result = setsockopt(result.fd,
                                       SOL_SOCKET, SO_SNDBUF,
                                       &buffer_size, sizeof(buffer_size));
        if (setsockopt_result == -1) {
            LOG0("Error setting socket send buffer size");
            continue;
        }

        // Increase receive buffer to KINETIC_OBJ_SIZE
        // Note: OS allocates 2x this value for its overheadbuffer_size
        setsockopt_result = setsockopt(result.fd,
                                       SOL_SOCKET, SO_RCVBUF,
                                       &buffer_size, sizeof(buffer_size));
        if (setsockopt_result == -1) {
            LOG0("Error setting socket receive buffer size");
            continue;
        }

        break;
    }

    freeaddrinfo(ai_result);

    if (ai == NULL || result.fd == KINETIC_SOCKET_DESCRIPTOR_INVALID) {
        // we went through all addresses without finding one we could bind to
        LOGF0("Could not connect to %s:%d", host, port);
        return KINETIC_SOCKET_DESCRIPTOR_INVALID;
    }
    else {
        LOGF1("Successfully connected to %s:%d (fd=%d)", host, port, result.fd);
        return result.fd;
    }
}

void KineticSocket_Close(int fd)
{
    if (fd == -1) {
        LOG1("Not connected so no cleanup needed");
    }
    else {
        LOGF0("Closing socket with fd=%d", fd);
        if (close(fd) == 0) {
            LOG2("Socket closed successfully");
        }
        else {
            LOGF0("Error closing socket file descriptor!"
                 " (fd=%d, errno=%d, desc='%s')",
                 fd, errno, strerror(errno));
        }
    }
}

void KineticSocket_EnableTCPNoDelay(int fd)
{
    int on = 1;
    setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &on, sizeof(on));
}
Пример #10
0
int KineticSocket_Connect(const char* host, int port, bool nonBlocking)
{
    char port_str[32];
    struct addrinfo hints;
    struct addrinfo* ai_result = NULL;
    struct addrinfo* ai = NULL;
    socket99_result result;

    // Setup server address info
    socket99_config cfg = {
        .host = (char*)host,
        .port = port,
        .nonblocking = nonBlocking,
    };
    sprintf(port_str, "%d", port);

    // Open socket
    LOGF0("Connecting to %s:%d", host, port);
    if (!socket99_open(&cfg, &result)) {
        LOGF0("Failed to open socket connection with host: status %d, errno %d",
             result.status, result.saved_errno);
        return KINETIC_SOCKET_DESCRIPTOR_INVALID;
    }

    // Configure the socket
    socket99_set_hints(&cfg, &hints);
    if (getaddrinfo(cfg.host, port_str, &hints, &ai_result) != 0) {
        LOGF0("Failed to get socket address info: errno %d", errno);
        close(result.fd);
        return KINETIC_SOCKET_DESCRIPTOR_INVALID;
    }

    for (ai = ai_result; ai != NULL; ai = ai->ai_next) {
        int setsockopt_result;
        int buffer_size = KINETIC_OBJ_SIZE;

#if defined(SO_NOSIGPIPE) && !defined(__APPLE__)
        // On BSD-like systems we can set SO_NOSIGPIPE on the socket to
        // prevent it from sending a PIPE signal and bringing down the whole
        // application if the server closes the socket forcibly
        int enable = 1;
        setsockopt_result = setsockopt(result.fd,
                                       SOL_SOCKET, SO_NOSIGPIPE,
                                       &enable, sizeof(enable));
        // Allow ENOTSOCK because it allows tests to use pipes instead of
        // real sockets
        if (setsockopt_result != 0 && setsockopt_result != ENOTSOCK) {
            LOG0("Failed to set SO_NOSIGPIPE on socket");
            continue;
        }
#endif

        // Increase send buffer to KINETIC_OBJ_SIZE
        // Note: OS allocates 2x this value for its overhead
        setsockopt_result = setsockopt(result.fd,
                                       SOL_SOCKET, SO_SNDBUF,
                                       &buffer_size, sizeof(buffer_size));
        if (setsockopt_result == -1) {
            LOG0("Error setting socket send buffer size");
            continue;
        }

        // Increase receive buffer to KINETIC_OBJ_SIZE
        // Note: OS allocates 2x this value for its overheadbuffer_size
        setsockopt_result = setsockopt(result.fd,
                                       SOL_SOCKET, SO_RCVBUF,
                                       &buffer_size, sizeof(buffer_size));
        if (setsockopt_result == -1) {
            LOG0("Error setting socket receive buffer size");
            continue;
        }

        break;
    }

    freeaddrinfo(ai_result);

    if (ai == NULL || result.fd == KINETIC_SOCKET_DESCRIPTOR_INVALID) {
        // we went through all addresses without finding one we could bind to
        LOGF0("Could not connect to %s:%d", host, port);
        return KINETIC_SOCKET_DESCRIPTOR_INVALID;
    }
    else {
        LOGF1("Successfully connected to %s:%d (fd=%d)", host, port, result.fd);
        return result.fd;
    }
}

void KineticSocket_Close(int socket)
{
    if (socket == -1) {
        LOG1("Not connected so no cleanup needed");
    }
    else {
        LOGF0("Closing socket with fd=%d", socket);
        if (close(socket) == 0) {
            LOG2("Socket closed successfully");
        }
        else {
            LOGF0("Error closing socket file descriptor!"
                 " (fd=%d, errno=%d, desc='%s')",
                 socket, errno, strerror(errno));
        }
    }
}

KineticWaitStatus KineticSocket_WaitUntilDataAvailable(int socket, int timeout)
{
    if (socket < 0) {return -1;}
    struct pollfd fd = {
        .fd = socket,
        .events = POLLIN,
        .revents = 0,
    };

    int res = poll(&fd, 1, timeout);

    if (res > 0) {
        //if (fd.revents & POLLHUP) // hung up
        if (fd.revents & POLLIN)
        {
            return KINETIC_WAIT_STATUS_DATA_AVAILABLE;
        }
        else
        {
            return KINETIC_WAIT_STATUS_FATAL_ERROR;
        }
    }
    else if (res == 0)
    {
        return KINETIC_WAIT_STATUS_TIMED_OUT;
    }
    else if ((errno & (EAGAIN | EINTR)) != 0)
    {
        return KINETIC_WAIT_STATUS_RETRYABLE_ERROR;
    }
    else
    {
        return KINETIC_WAIT_STATUS_FATAL_ERROR;
    }
}

int KineticSocket_DataBytesAvailable(int socket)
{
    if (socket < 0) {return -1;}
    int count = -1;
    ioctl(socket, FIONREAD, &count);
    return count;
}

KineticStatus KineticSocket_Read(int socket, ByteBuffer* dest, size_t len)
{
    LOGF2("Reading %zd bytes into buffer @ 0x%zX from fd=%d",
         len, (size_t)dest->array.data, socket);

    KineticStatus status = KINETIC_STATUS_INVALID;

    // Read "up to" the allocated number of bytes into dest buffer
    size_t bytesToReadIntoBuffer = len;
    if (dest->array.len < len) {
        bytesToReadIntoBuffer = dest->array.len;
    }
    while (dest->bytesUsed < bytesToReadIntoBuffer) {
        int opStatus;
        fd_set readSet;
        struct timeval timeout;

        // Time out after 5 seconds
        timeout.tv_sec = 5;
        timeout.tv_usec = 0;

        FD_ZERO(&readSet);
        FD_SET(socket, &readSet);
        opStatus = select(socket + 1, &readSet, NULL, NULL, &timeout);

        if (opStatus < 0) { // Error occurred
            LOGF0("Failed waiting to read from socket!"
                 " status=%d, errno=%d, desc='%s'",
                 opStatus, errno, strerror(errno));
            return KINETIC_STATUS_SOCKET_ERROR;
        }
        else if (opStatus == 0) { // Timeout occurred
            LOG0("Timed out waiting for socket data to arrive!");
            return KINETIC_STATUS_SOCKET_TIMEOUT;
        }
        else if (opStatus > 0) { // Data available to read
            // The socket is ready for reading
            opStatus = read(socket,
                            &dest->array.data[dest->bytesUsed],
                            dest->array.len - dest->bytesUsed);
            // Retry if no data yet...
            if (opStatus == -1 &&
                ((errno == EINTR) ||
                 (errno == EAGAIN) ||
                 (errno == EWOULDBLOCK)
                )) {
                continue;
            }
            else if (opStatus <= 0) {
                LOGF0("Failed to read from socket!"
                     " status=%d, errno=%d, desc='%s'",
                     opStatus, errno, strerror(errno));
                return KINETIC_STATUS_SOCKET_ERROR;
            }
            else {
                dest->bytesUsed += opStatus;
                LOGF3("Received %d bytes (%zd of %zd)",
                     opStatus, dest->bytesUsed, len);
            }
        }
    }

    // Flush any remaining data, in case of a truncated read w/short dest buffer
    if (dest->bytesUsed < len) {
        bool abortFlush = false;

        uint8_t* discardedBytes = malloc(len - dest->bytesUsed);
        if (discardedBytes == NULL) {
            LOG0("Failed allocating a socket read discard buffer!");
            abortFlush = true;
            status = KINETIC_STATUS_MEMORY_ERROR;
        }

        while (!abortFlush && dest->bytesUsed < len) {
            int opStatus;
            fd_set readSet;
            struct timeval timeout;
            size_t remainingLen = len - dest->bytesUsed;

            // Time out after 5 seconds
            timeout.tv_sec = 5;
            timeout.tv_usec = 0;

            FD_ZERO(&readSet);
            FD_SET(socket, &readSet);
            opStatus = select(socket + 1, &readSet, NULL, NULL, &timeout);

            if (opStatus < 0) { // Error occurred
                LOGF0("Failure trying to flush read socket data!"
                     " status=%d, errno=%d, desc='%s'",
                     status, errno, strerror(errno));
                abortFlush = true;
                status = KINETIC_STATUS_SOCKET_ERROR;
                continue;
            }
            else if (opStatus == 0) { // Timeout occurred
                LOG0("Timed out waiting to flush socket data!");
                abortFlush = true;
                status = KINETIC_STATUS_SOCKET_TIMEOUT;
                continue;
            }
            else if (opStatus > 0) { // Data available to read
                // The socket is ready for reading
                opStatus = read(socket, discardedBytes, remainingLen);
                // Retry if no data yet...
                if (opStatus == -1 &&
                    ((errno == EINTR) ||
                     (errno == EAGAIN) ||
                     (errno == EWOULDBLOCK)
                    )) {
                    continue;
                }
                else if (opStatus <= 0) {
                    LOGF0("Failed to read from socket while flushing!"
                         " status=%d, errno=%d, desc='%s'",
                         opStatus, errno, strerror(errno));
                    abortFlush = true;
                    status = KINETIC_STATUS_SOCKET_ERROR;
                }
                else {
                    dest->bytesUsed += opStatus;
                    LOGF3("Flushed %d bytes from socket read pipe (%zd of %zd)",
                         opStatus, dest->bytesUsed, len);
                }
            }
        }

        // Free up dynamically allocated memory before returning
        if (discardedBytes != NULL) {
            free(discardedBytes);
        }

        // Report any error that occurred during socket flush
        if (abortFlush) {
            LOG0("Socket read pipe flush aborted!");
            assert(status == KINETIC_STATUS_SUCCESS);
            return status;
        }

        // Report truncation of data for any variable length byte arrays
        LOGF1("Socket read buffer was truncated due to buffer overrun!"
             " received=%zu, copied=%zu",
             len, dest->array.len);
        return KINETIC_STATUS_BUFFER_OVERRUN;
    }
    LOGF3("Received %zd of %zd bytes requested", dest->bytesUsed, len);
    return KINETIC_STATUS_SUCCESS;
}

KineticStatus KineticSocket_ReadProtobuf(int socket, KineticPDU* pdu)
{
    size_t bytesToRead = pdu->header.protobufLength;
    LOGF2("Reading %zd bytes of protobuf", bytesToRead);

    uint8_t* packed = (uint8_t*)malloc(bytesToRead);
    if (packed == NULL) {
        LOG0("Failed allocating memory for protocol buffer");
        return KINETIC_STATUS_MEMORY_ERROR;
    }

    ByteBuffer recvBuffer = ByteBuffer_Create(packed, bytesToRead, 0);
    KineticStatus status = KineticSocket_Read(socket, &recvBuffer, bytesToRead);

    if (status != KINETIC_STATUS_SUCCESS) {
        LOG0("Protobuf read failed!");
        free(packed);
        return status;
    }
    else {
        pdu->proto = KineticProto_Message__unpack(
                         NULL, recvBuffer.bytesUsed, recvBuffer.array.data);
    }

    free(packed);

    if (pdu->proto == NULL) {
        pdu->protobufDynamicallyExtracted = false;
        LOG0("Error unpacking incoming Kinetic protobuf message!");
        return KINETIC_STATUS_DATA_ERROR;
    }
    else {
        pdu->protobufDynamicallyExtracted = true;
        LOG3("Protobuf unpacked successfully!");
        return KINETIC_STATUS_SUCCESS;
    }
}
void run_throughput_tests(KineticClient * client, size_t num_ops, size_t value_size)
{
    LOGF0("STRESS THREAD: object_size: %zu bytes, count: %zu entries", value_size, num_ops);

    // Configure and establish a session with the specified device
    KineticSession* session;
    const char HmacKeyString[] = "asdfasdf";
    KineticSessionConfig config = {
        .clusterVersion = 0,
        .identity = 1,
        .hmacKey = ByteArray_CreateWithCString(HmacKeyString),
    };
    strncpy(config.host, GetSystemTestHost1(), sizeof(config.host)-1);
    config.port = GetSystemTestPort1();
    KineticStatus status = KineticClient_CreateSession(&config, client, &session);
    if (status != KINETIC_STATUS_SUCCESS) {
        char msg[128];
        sprintf(msg, "Failed connecting to the Kinetic device w/status: %s", Kinetic_GetStatusDescription(status));
        TEST_FAIL_MESSAGE(msg);
    }

    // Generate test entry data
    ByteBuffer test_data = ByteBuffer_Malloc(value_size);
    ByteBuffer_AppendDummyData(&test_data, test_data.array.len);
    uint8_t tag_data[] = {0x00, 0x01, 0x02, 0x03};
    ByteBuffer tag = ByteBuffer_Create(tag_data, sizeof(tag_data), sizeof(tag_data));
    uint64_t r = rand();
    uint64_t keys[num_ops];
    KineticEntry entries[num_ops];
    for (uint32_t put = 0; put < num_ops; put++) {
        keys[put] = put | (r << 16);
    }

    // Measure PUT performance
    {
        OpStatus put_statuses[num_ops];
        for (size_t i = 0; i < num_ops; i++) {
            put_statuses[i] = (OpStatus){
                .sem = KineticSemaphore_Create(),
                .status = KINETIC_STATUS_INVALID,
            };
        };

        struct timeval start_time;
        gettimeofday(&start_time, NULL);

        for (uint32_t put = 0; put < num_ops; put++) {
            ByteBuffer key = ByteBuffer_Create(&keys[put], sizeof(keys[put]), sizeof(keys[put]));

            KineticSynchronization sync = (put == num_ops - 1)
                ? KINETIC_SYNCHRONIZATION_FLUSH
                : KINETIC_SYNCHRONIZATION_WRITEBACK;

            entries[put] = (KineticEntry) {
                .key = key,
                .tag = tag,
                .algorithm = KINETIC_ALGORITHM_SHA1,
                .value = test_data,
                .synchronization = sync,
            };

            KineticStatus status = KineticClient_Put(
                session,
                &entries[put],
                &(KineticCompletionClosure) {
                    .callback = op_finished,
                    .clientData = &put_statuses[put],
                }
            );

            if (status != KINETIC_STATUS_SUCCESS) {
                char msg[128];
                sprintf(msg, "PUT failed w/status: %s", Kinetic_GetStatusDescription(status));
                TEST_FAIL_MESSAGE(msg);
            }
        }

        for (size_t i = 0; i < num_ops; i++)
        {
            KineticSemaphore_WaitForSignalAndDestroy(put_statuses[i].sem);
            if (put_statuses[i].status != KINETIC_STATUS_SUCCESS) {
                char msg[128];
                sprintf(msg, "PUT failed w/status: %s", Kinetic_GetStatusDescription(put_statuses[i].status));
                TEST_FAIL_MESSAGE(msg);
            }
        }

        struct timeval stop_time;
        gettimeofday(&stop_time, NULL);
        size_t bytes_written = num_ops * test_data.array.len;
        int64_t elapsed_us = ((stop_time.tv_sec - start_time.tv_sec) * 1000000)
            + (stop_time.tv_usec - start_time.tv_usec);
        float elapsed_ms = elapsed_us / 1000.0f;
        float bandwidth = (bytes_written * 1000.0f) / (elapsed_ms * 1024 * 1024);
        LOGF0("PUT Performance: wrote: %.1f kB, duration: %.3f sec, throughput: %.2f MB/sec",
            bytes_written / 1024.0f, elapsed_ms / 1000.0f, bandwidth);
    }

    // Measure GET performance
    {
        OpStatus get_statuses[num_ops];
        for (size_t i = 0; i < num_ops; i++) {
            get_statuses[i] = (OpStatus){
                .sem = KineticSemaphore_Create(),
                .status = KINETIC_STATUS_INVALID,
            };
        };

        ByteBuffer test_get_datas[num_ops];
        for (size_t i = 0; i < num_ops; i++)
        {
            test_get_datas[i] = ByteBuffer_Malloc(value_size);
        }

        struct timeval start_time;
        gettimeofday(&start_time, NULL);

        for (uint32_t get = 0; get < num_ops; get++) {
            ByteBuffer key = ByteBuffer_Create(&keys[get], sizeof(keys[get]), sizeof(keys[get]));

            entries[get] = (KineticEntry) {
                .key = key,
                .tag = tag,
                .value = test_get_datas[get],
            };

            KineticStatus status = KineticClient_Get(
                session,
                &entries[get],
                &(KineticCompletionClosure) {
                    .callback = op_finished,
                    .clientData = &get_statuses[get],
                }
            );

            if (status != KINETIC_STATUS_SUCCESS) {
                char msg[128];
                sprintf(msg, "GET failed w/status: %s", Kinetic_GetStatusDescription(status));
                TEST_FAIL_MESSAGE(msg);
            }
        }

        size_t bytes_read = 0;
        for (size_t i = 0; i < num_ops; i++)
        {
            KineticSemaphore_WaitForSignalAndDestroy(get_statuses[i].sem);
            if (get_statuses[i].status != KINETIC_STATUS_SUCCESS) {
                char msg[128];
                sprintf(msg, "GET failed w/status: %s", Kinetic_GetStatusDescription(get_statuses[i].status));
                TEST_FAIL_MESSAGE(msg);
            }
            else
            {
                bytes_read += entries[i].value.bytesUsed;
            }
        }

        // Check data for integrity
        size_t numFailures = 0;
        for (size_t i = 0; i < num_ops; i++) {
            int res = memcmp(test_data.array.data, test_get_datas[i].array.data, test_data.array.len);
            if (res != 0) {
                LOGF0("Failed validating data in object %zu of %zu!", i+1, num_ops);
                numFailures++;
            }
        }
        TEST_ASSERT_EQUAL_MESSAGE(0, numFailures, "DATA INTEGRITY CHECK FAILED UPON READBACK!");

        // Calculate and report performance
        struct timeval stop_time;
        gettimeofday(&stop_time, NULL);
        int64_t elapsed_us = ((stop_time.tv_sec - start_time.tv_sec) * 1000000)
            + (stop_time.tv_usec - start_time.tv_usec);
        float elapsed_ms = elapsed_us / 1000.0f;
        float bandwidth = (bytes_read * 1000.0f) / (elapsed_ms * 1024 * 1024);
        LOGF0("GET Performance: read: %.1f kB, duration: %.3f sec, throughput: %.2f MB/sec",
            bytes_read / 1024.0f, elapsed_ms / 1000.0f, bandwidth);

        for (size_t i = 0; i < num_ops; i++) {
            ByteBuffer_Free(test_get_datas[i]);
        }
    }