/*
 *****************************************************************************
 *                                                                           *
 *              IMPINJ CONFIDENTIAL AND PROPRIETARY                          *
 *                                                                           *
 * This source code is the sole property of Impinj, Inc. Reproduction or     *
 * utilization of this source code in whole or in part is forbidden without  *
 * the prior written consent of Impinj, Inc.                                 *
 *                                                                           *
 * (c) Copyright Impinj, Inc. 2014. All rights reserved.                     *
 *                                                                           *
 *****************************************************************************/

#ifdef __gnu_linux__
#include <errno.h>
#include <netdb.h>
#include <pthread.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#endif

#ifdef _WIN32
#include <Winsock2.h>
#include <WS2tcpip.h>
#include <windows.h>
#include <tchar.h>
#ifndef snprintf
#define snprintf _snprintf
#endif
#endif

#include <stdio.h>
#include <stdint.h>

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

#ifndef IPJ_EXPORT_MODULE
#define IPJ_EXPORT_MODULE
#endif
#include "stp.h"
#include "stp_api.pb.h"

#define FRAME_MARKER_LEN        4
#define FRAME_VERSION           1
#define FRAME_VERSION_LEN       1
#define FRAME_DATA_LENGTH_LEN   4
#define FRAME_CHECKSUM_LEN      1
#define FRAME_HEADER_SIZE      10
#define FRAME_CRC_LEN           2

static const uint8_t kaFrameMarker[] = { 'i', 'm', 'p', '!' };

#define STP_API_VERSION 1
#define STP_API_NAME "STP"

uint16_t crc16_table[] = {
    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
    0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
    0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
    0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
    0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
    0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
    0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
    0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
    0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
    0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
    0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
    0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
    0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
    0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
    0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
    0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
    0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
    0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
    0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
    0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
    0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
    0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
    0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
    0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
    0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
    0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
    0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
    0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
    0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
    0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
    0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
    0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};

const uint16_t CRC16_PRELOAD = 0xFFFF;
const uint32_t IPJ_DEFAULT_RESPONSE_TIMEOUT_MS = 10000;

const int IPJ_RX_BUFFER_SIZE = 1024;
const int IPJ_TX_BUFFER_SIZE = 1024;

bool b_sock_init = false;

namespace Impinj
{

/**
 * Convert a binary string to a (human-readable) ASCII hexadecimal string
 * like:
 *     "e2801130200286667340b000".
 * @param[in] InStr Binary string
 * @return (human-readable) ASCII hexadecimal string.
 */
IPJ_EXPORT
std::string BinToHexString(const std::string& InStr)
{
    std::string RetStr;
    std::string::const_iterator iter;

    RetStr.reserve(InStr.size() * 4);
    for (iter=InStr.begin(); iter< InStr.end(); iter++)
    {
        char ByteStr[4];
        snprintf(ByteStr, 4, "%02x", ((int)*iter & 0xFF));
        /* Append hex byte string */
        RetStr += ByteStr;
    }
    return RetStr;
}

/**
 * Convert a (human-readable) ASCII hexadecimal string like:
 *    "e280 1130 2002 8666 7340 b000"
 * to a binary string suitable for TIDs, EPCs, and masks. Spaces in the
 * string are optional. An odd number of nibbles will be prepended with
 * a zero so "123" will return the same result as "0123".
 * @param[in] InStr ASCII hexadecimal string
 * @return Binary string.
 */ 
IPJ_EXPORT
std::string HexStringToBin(const std::string& InStr)
{
    std::string RetStr;

    bool bRightNibble = true;

    /* Loop from right side of string, LSB to MSB. */
    std::string::const_reverse_iterator iter;
    uint8_t ByteVal = 0;
    uint8_t NibbleVal = 0;
    for (iter=InStr.rbegin(); iter != InStr.rend(); iter++)
    {
        /* Skip spaces */
        if (' ' == *iter)
        {
            continue;
        }
        /* Must be hex digits */
        if (!isxdigit((int)*iter))
        {
            return "";
        }
        /* Convert lower case hex digit to a byte */
        NibbleVal = tolower(*iter);
        if (NibbleVal <= '9' )
        {
            NibbleVal -= '0';
        }
        else
        {
            NibbleVal -= 'a';
            NibbleVal += 10;
        }
        if (bRightNibble)
        {
            ByteVal = NibbleVal;
            bRightNibble = false;
        }
        else
        {
            ByteVal |= (NibbleVal << 4);
            bRightNibble = true;

            /* Prepend value */
            RetStr.insert(0, 1, ByteVal);
        }
    }
    if (!bRightNibble)
    {
        // Odd number of input digits; prepend just the right nibble as a byte.
        RetStr.insert(0, 1, ByteVal);
    }

    return RetStr;
}

} /* Impinj namespace */

namespace Impinj {
namespace STP {

namespace InternalHelpers
{

static uint16_t CalcCRC(const uint8_t* pData, uint32_t dataLength)
{
    uint16_t crc = CRC16_PRELOAD;
    for (uint32_t i = 0; i < dataLength; i++)
    {
        uint8_t tmp;
        tmp = (*pData++) ^ ((crc >> 8) & 0xFF);
        crc = (crc << 8) ^ crc16_table[tmp];
    }
    /*
     * The non-inverted value matches other CRC-CCITT implemenations.
     * Return the ones-complement as required in the gen2 air interface.
     */
    crc = ~crc & 0xFFFF;

    return crc;
}

static bool CheckCRC(const uint8_t* data,
                     uint32_t dataLength,
                     uint16_t expected_crc)
{
    uint32_t crc = CalcCRC(data, dataLength);
    return (crc == expected_crc);
}

static int8_t ModularSum(uint32_t data)
{
    int8_t sum = -1;
    sum += (int8_t)(data & 0x000000FF);
    sum += (int8_t)((data & 0x0000FF00) >> 8);
    sum += (int8_t)((data & 0x00FF0000) >> 16);
    sum += (int8_t)((data & 0xFF000000) >> 24);

    sum = (int8_t)~sum;

    return sum;
}

static bool CheckModularSum(uint32_t data, int8_t modsum)
{
    int8_t modSumVerify = (int8_t)(ModularSum(data) - modsum);

    return (modSumVerify == 0);
}

const Impinj::STP::Reply NameToIp(const std::string& HostNameOrIp,
                                  std::string& HostIp)
{
    Impinj::STP::Reply reply;
    reply.set_error_code(ERROR_CODE_SUCCESS);

    // First, if it's already the dotted number format, just don't do 
    // the lookups
    unsigned long addr_from_inet_addr = inet_addr(HostNameOrIp.c_str());
    if (INADDR_NONE != addr_from_inet_addr)
    {
        // It already was the IP, so no sense going through the effort
        // of sending it through the other lookup systems
        HostIp = HostNameOrIp;
        return reply;
    }

    addrinfo hints;
    struct addrinfo* pAddr;
    memset(&hints, 0, sizeof(hints));
    hints.ai_flags = AI_CANONNAME;
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_protocol = IPPROTO_UDP;

    int AddrRetval = getaddrinfo(HostNameOrIp.c_str(), NULL, &hints, &pAddr);
    if (0 != AddrRetval)
    {
        reply.set_error_code(ERROR_CODE_NETWORK_INIT);
        reply.set_error_string("Failed to get IP address for " +
            HostNameOrIp);
        return reply;
    }

    char pListenerIp[INET6_ADDRSTRLEN];
    int NameRetval = getnameinfo(pAddr->ai_addr,
                                 pAddr->ai_addrlen,
                                 pListenerIp,
                                 sizeof(pListenerIp),
                                 NULL,
                                 0,
                                 NI_NUMERICHOST);
    if (0 != NameRetval)
    {
        freeaddrinfo(pAddr);
        reply.set_error_code(ERROR_CODE_NETWORK_INIT);
        reply.set_error_string("Failed to get IP string for " +
            HostNameOrIp);
        return reply;
    }
    HostIp = pListenerIp;

    freeaddrinfo(pAddr);

    return reply;
}    

} /* InternalHelpers */

/*=======================================================*/
TidTableSender::TidTableSender(uint32_t nInitialFrameSize)
{
    this->m_Frame.reserve(nInitialFrameSize);
    this->m_Frame.resize(FRAME_HEADER_SIZE);

    // Load up frame marker
    this->m_Frame[0] = 'i';
    this->m_Frame[1] = 'm';
    this->m_Frame[2] = 'p';
    this->m_Frame[3] = '!';

    // Load up frame version
    this->m_Frame[4] = FRAME_VERSION;
}

/**
 * Send tag TID table encode data to all devices.
 * Use Device::AddTidTableEntries to send entries to a
 * single device using TCP.
 * @param[out] table_entries A list of TID table entries.
 * @return See ErrorCode enums
 */
const Impinj::STP::Reply TidTableSender::AddTidTableEntries(
    const Impinj::STP::TIDTableEntries& table_entries)
{
    Impinj::STP::Reply reply;
    reply.set_error_code(ERROR_CODE_SUCCESS);

    *(this->m_Packet.mutable_update()
          ->mutable_add_tid_table_entries()
          ->mutable_table_entries()) = table_entries;

    this->BroadcastPacket(this->m_Packet);
    this->m_Packet.mutable_update()->mutable_add_tid_table_entries()->Clear();

    return reply;
}

Impinj::STP::TIDTableEntries* TidTableSender::MutableTidTableEntries()
{
    return this->m_Packet.mutable_update()
        ->mutable_add_tid_table_entries()
        ->mutable_table_entries();
}

void TidTableSender::SendTidTableEntries()
{
    this->BroadcastPacket(this->m_Packet);
}

int TidTableSender::NetConnect(std::string& reader, uint16_t port)
{
    int sSocket = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if (-1 == sSocket)
    {
        return sSocket;
    }

    /* Disable Nagle */
    int bFlag = 0;
    if (-1 == setsockopt(sSocket,
                         IPPROTO_TCP,
                         TCP_NODELAY,
                         (const char*)&bFlag,
                         sizeof(bFlag)))
    {
        return -1;
    }

    struct sockaddr_in Sin;
    memset(&Sin, 0, sizeof(Sin));
    Sin.sin_family = AF_INET;

#ifdef UNDER_CE
    Sin.sin_addr.s_addr = inet_addr(reader.c_str());
#else
    int addrRet = inet_pton(AF_INET, reader.c_str(), &Sin.sin_addr);
    if (1 != addrRet)
    {
        return -1;
    }
#endif

    Sin.sin_port = htons(port);

    int connRet = connect(sSocket, (struct sockaddr*)&Sin, sizeof(Sin));
    if (0 != connRet)
    {
        return -1;
    }

    return sSocket;
}

const Impinj::STP::Reply TidTableSender::AddListener(
    const std::string& ListenerNameOrIp)
{
    Impinj::STP::Reply reply;
    reply.set_error_code(ERROR_CODE_SUCCESS);

    std::string ListenerIp;
    reply = InternalHelpers::NameToIp(ListenerNameOrIp, ListenerIp);
    if (ERROR_CODE_SUCCESS != reply.error_code())
    {
        return reply;
    }

    int socket = this->NetConnect(ListenerIp, IPJ_PORT);
    if (-1 == socket)
    {
        reply.set_error_code(ERROR_CODE_NETWORK_INIT);
        reply.set_error_string("Failed to connect to device");
        return reply;
    }

    this->m_TidEntryListeners[ListenerIp] = socket;

    return reply;
}

const Impinj::STP::Reply TidTableSender::RemoveListener(
    const std::string& ListenerNameOrIp)
{
    Impinj::STP::Reply reply;
    reply.set_error_code(ERROR_CODE_SUCCESS);

    std::string ListenerIp;
    reply = InternalHelpers::NameToIp(ListenerNameOrIp, ListenerIp);
    if (ERROR_CODE_SUCCESS != reply.error_code())
    {
        return reply;
    }

    int socket = this->m_TidEntryListeners[ListenerIp];
    #ifdef __gnu_linux__
    close(socket);
    #endif
    #ifdef _WIN32
    closesocket(socket);
    #endif

    this->m_TidEntryListeners.erase(ListenerIp);

    return reply;
}

void TidTableSender::GenerateFrame(const Impinj::STP::Packet& packet)
{
    uint32_t nPacketLength = static_cast<uint32_t>(packet.ByteSize());
    uint32_t nFrameLength = FRAME_HEADER_SIZE + nPacketLength + FRAME_CRC_LEN;

    if (nFrameLength > this->m_Frame.capacity())
    {
        this->m_Frame.reserve(nFrameLength);
    }

    this->m_Frame.resize(nFrameLength);
    
    // Copy over data length
    this->m_Frame[5] = (nPacketLength & 0xFF000000) >> 24;
    this->m_Frame[6] = (nPacketLength & 0x00FF0000) >> 16;
    this->m_Frame[7] = (nPacketLength & 0x0000FF00) >>  8;
    this->m_Frame[8] = (nPacketLength & 0x000000FF);

    // Calculate modular sum and set it.
    this->m_Frame[9] = InternalHelpers::ModularSum(nPacketLength);
    
    packet.SerializeToArray(&this->m_Frame[FRAME_HEADER_SIZE], nPacketLength);

    // Calculate CRC and add it on.
    uint16_t nPacketCrc = InternalHelpers::CalcCRC(
        &this->m_Frame[FRAME_HEADER_SIZE], nPacketLength);
    this->m_Frame[FRAME_HEADER_SIZE + nPacketLength] =
        (nPacketCrc & 0xFF00) >> 8;
    this->m_Frame[FRAME_HEADER_SIZE + nPacketLength + 1] =
        (nPacketCrc & 0x00FF);
}

void TidTableSender::BroadcastPacket(const Impinj::STP::Packet& packet)
{
    this->GenerateFrame(packet);
    this->BroadcastFrame();
}

void TidTableSender::BroadcastFrame()
{
    int flags = 0;
    std::map<std::string, int>::const_iterator iter;
    for (iter = this->m_TidEntryListeners.begin();
         iter != this->m_TidEntryListeners.end();
         iter++)
    {
        int socket = iter->second;

        #ifdef __gnu_linux__
        int sendRet =
            send(socket, &this->m_Frame[0], this->m_Frame.size(), flags);
        #endif

        #ifdef _WIN32
        int sendRet =
            send(socket,
                 reinterpret_cast<const char*>(&this->m_Frame[0]),
                 this->m_Frame.size(),
                 flags);
        #endif
    }
}

/*=======================================================*/

/**
 * STP (Reader) Device constructor
 */
IPJ_EXPORT
Device::Device(void):
    m_connectionType(CONNECTION_TYPE_NONE),
    m_socket(-1),
    m_u32ResponseTimeout_ms(IPJ_DEFAULT_RESPONSE_TIMEOUT_MS),
    m_pAllReportHandler(NULL)
{
    /* Init class members */
    this->m_pOperationSummaryReportHandler = NULL;
    this->m_pTagOperationReportHandler = NULL;
    this->m_pDeviceEventReportHandler = NULL;
    this->m_pCartonContentsReportHandler = NULL;
    

    #ifdef __gnu_linux__
    pthread_mutex_init(&this->m_read_mutex, NULL);
    pthread_cond_init(&this->m_read_cond, NULL);
    this->m_bResponseReceived = false;
    #endif

    #ifdef _WIN32
    InitializeCriticalSection(&m_read_mutex);
    bool bManualReset = false;
    bool bInitialState = false;
    LPTSTR pName = NULL;
    this->m_read_cond = CreateEvent(NULL, bManualReset, bInitialState, pName);
    #endif

    this->m_TxFrame.reserve(IPJ_TX_BUFFER_SIZE);
    this->m_TxFrame.resize(FRAME_HEADER_SIZE);
    
    // Load up frame marker
    this->m_TxFrame[0] = 'i';
    this->m_TxFrame[1] = 'm';
    this->m_TxFrame[2] = 'p';
    this->m_TxFrame[3] = '!';

    // Load up frame version
    this->m_TxFrame[4] = FRAME_VERSION;
}

/**
 * STP (Reader) Device destructor
 */
IPJ_EXPORT
Device::~Device(void)
{
    #ifdef __gnu_linux__
    pthread_cond_destroy(&m_read_cond);
    pthread_mutex_destroy(&m_read_mutex);
    #endif

    #ifdef _WIN32
    DeleteCriticalSection(&m_read_mutex);
    #endif
}

/**
 * Initiates STP API connection to the reader specified.
 * @param[in] reader The IP address or DNS name of the reader to connect to.
 * @param[in] port The port number to connect to on the specified reader.
 * @return See ErrorCode enums
 */
IPJ_EXPORT
const Impinj::STP::Reply Device::NetConnect(const std::string reader,
                                            uint16_t port)
{
    Impinj::STP::Reply resp;

    #ifdef _WIN32
    if (false == b_sock_init)
    {
        WSADATA wsaData;
        int wsStart = WSAStartup(MAKEWORD(2,2), &wsaData);
        if (wsStart != 0)
        {
            std::cerr << "Failed to initialize winsock";
            resp.set_error_code(ERROR_CODE_NETWORK_INIT);
            return resp;
        }
        b_sock_init = true;
    }
    #endif

    // Connect to socket
    #ifdef __gnu_linux__
    this->m_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (this->m_socket < 0)
    #endif

    #ifdef _WIN32
    this->m_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (INVALID_SOCKET == this->m_socket)
    #endif
    {
        resp.set_error_code(ERROR_CODE_NETWORK_INIT);
        resp.set_error_string("Failed to create network connection");
        return resp;
    }

    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    // If reader was provided as an IP address, just use it without
    // doing the lookups
    serv_addr.sin_addr.s_addr = inet_addr(reader.c_str());

    if (INADDR_NONE == serv_addr.sin_addr.s_addr)
    {
        // it must not have been an ip address, so try to resolve the
        // host name
        struct hostent* server = gethostbyname(reader.c_str());
        if (NULL == server)
        {
#ifdef _WIN32
            std::cerr << "gethostbyname error=" << GetLastError() << std::endl;
#endif
            resp.set_error_code(ERROR_CODE_INVALID_ADDRESS);
            return resp;
        }
        memcpy(&serv_addr.sin_addr.s_addr, server->h_addr, server->h_length);
    }

    serv_addr.sin_port = htons(IPJ_PORT);
    int connect_retval;
    connect_retval =
        connect(m_socket, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    if (connect_retval != 0)
    {
#ifdef _WIN32
        std::cerr << "connect error=" << GetLastError() << std::endl;
#endif
        resp.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        resp.set_error_string("Failed to connect");
        return resp;
    }

    this->m_connectionType = CONNECTION_TYPE_SOCKET;

    // Start the listen thread
    #ifdef __gnu_linux__
    pthread_create(&this->m_rxThread, NULL, RXThreadFunc, this);
    #endif

    #ifdef _WIN32
    this->m_rxThread = CreateThread(NULL,
                                    0,
                                    (LPTHREAD_START_ROUTINE)RXThreadFunc,
                                    this,
                                    0,
                                    &this->m_thread_id);
    #endif

    // Finish up the connect with the handshake
    resp = this->SendHandshake();
    return resp;
}

IPJ_EXPORT
void Device::Disconnect(void)
{
    #ifdef __gnu_linux__
    /* Calling shutdown vs close will unblock recv in the listening thread. */
    shutdown(this->m_socket, SHUT_RDWR);
    pthread_join(this->m_rxThread, NULL);
    #endif

    #ifdef _WIN32
    closesocket(this->m_socket);
    /* Don't shutdown winsock so mutiple device connections stay alive */
    //WSACleanup();
    //b_sock_init = false;
    #endif

    this->m_socket = -1;

    this->m_connectionType = CONNECTION_TYPE_NONE;
}

/**
 * Disconnects the current STP API session with the connected reader.
 */
IPJ_EXPORT
void Device::NetDisconnect(void)
{
    this->Disconnect();
}

void Device::DispatchPacket(const uint8_t* buffer, int32_t length)
{
    // Regenerate packet
    this->m_RxPacket.ParseFromArray(buffer, length);

    // If it's a report, pass along to handler.
    if (this->m_RxPacket.has_report())
    {
        if (!this->HandleReport(this->m_RxPacket.report()))
        {
            //TODO Log
        }
    }
    else
    {
        // Not a report. Signal wait for command response that we got something.
        #ifdef __gnu_linux__
        pthread_mutex_lock(&this->m_read_mutex);
        this->m_bResponseReceived = true;
        pthread_cond_signal(&this->m_read_cond);
        pthread_mutex_unlock(&this->m_read_mutex);
        #endif

        #ifdef _WIN32
        SetEvent(this->m_read_cond);
        #endif
    }
}

enum FRAME_STATE
{
    E_MARKER,
    E_VERSION,
    E_LENGTH,
    E_MODSUM,
    E_DATA,
    E_CRC
};


void* Device::RXThreadFunc(void* pThreadParam)
{
    Device* pDevice = (Device*) pThreadParam;

    uint32_t decPos = 0; // index into what is being decoded to
    uint32_t bufPos = 0; // index into what is being decoded from
    uint32_t iRx = 0;
    uint32_t dataLength = 0;
    uint16_t crc = 0;
    uint8_t buffer[IPJ_RX_BUFFER_SIZE]; //1024
    uint8_t* data = NULL;
    FRAME_STATE curState = E_MARKER;

    do {
        // Need more bytes
        if (bufPos == iRx)
        {
            // TODO : Do we need a timeout: Example, Datalength is correctly
            // modsummed, but waiting for some of "data" on socket?
            iRx = pDevice->Rx(&(buffer[0]), IPJ_RX_BUFFER_SIZE);
            if (0 == iRx)
            {
                /* Connection has been shutdown */
                return NULL;
            }

            bufPos = 0;
        }
        
        switch (curState)
        {
            case E_MARKER:  // Look for Marker
            {
                while (bufPos < iRx)
                {
                    if (buffer[bufPos] == kaFrameMarker[decPos])
                    {
                        decPos++;
                        if (decPos > 3) // Marker found
                        {
                            curState = E_VERSION;
                            decPos = 0;

                            // Next Byte
                            bufPos++;
                            break;
                        }                                    
                    }
                    else
                    {
                        // TODO : Logging?
                        // Start Over
                        decPos = 0;

                        // Check to see if this is the start of a marker
                        if (buffer[bufPos] == kaFrameMarker[decPos]) 
                        {
                            decPos++;
                        }
                    }

                    // Next Byte
                    bufPos++;
                }
                break;
            }
            case E_VERSION: // Look for Frame version
            {
                if (bufPos < iRx)
                {
                    if (buffer[bufPos] == FRAME_VERSION)
                    {
                        curState = E_LENGTH;
                        dataLength = 0;
                        decPos = 0;
                        bufPos++;
                    }
                    else
                    {
                        // TODO : logging
                        //Start all over!
                        curState = E_MARKER;
                        decPos = 0;
                    }
                }
                break;
            }
            case E_LENGTH: // Look for Data Length
            {
                while (bufPos < iRx)
                {
                    // Increment length
                    dataLength += static_cast<uint32_t>(buffer[bufPos])
                                  << ((3 - decPos) * 8);

                    // Length is 4 bytes, have we got there yet?
                    if (decPos > 2)
                    {
                        data = new uint8_t[dataLength];
                        // Length is done
                        curState = E_MODSUM;
                        decPos = 0;

                        // Leave buffer on MODSUM byte
                        bufPos++;
                        break;
                    }
                    bufPos++;
                    decPos++;
                }
                break;
            }
            case E_MODSUM: // Get Modsum and check it
            {
                if (bufPos < iRx)
                {
                    int8_t modSum = buffer[bufPos];
                    if (InternalHelpers::CheckModularSum(dataLength, modSum))
                    {
                        curState = E_DATA;
                        bufPos++;
                        decPos = 0;
                    }
                    else
                    {
                        // TODO : Logging
                        // Bad Modular Sum...start over
                        curState = E_MARKER;
                        decPos = 0;
                    }
                }
                break;
            }
            case E_DATA: // Get the data
            {
                while (bufPos < iRx)
                {
                    if (decPos >= dataLength)
                    {
                        // Done
                        curState = E_CRC;
                        decPos = 0;

                        // This time, don't increase bufPos,
                        // since CRC comes in separate frame.
                        // Reset CRC though!
                        crc = 0;
                        break;
                    }
                    data[decPos] = buffer[bufPos];
                    bufPos++;
                    decPos++;
                }
                break;
            }
            case E_CRC: // Get the CRC
            {
                while (bufPos < iRx)
                {
                    crc += (uint16_t)((buffer[bufPos]) << ((1 - decPos) * 8));

                    // CRC is only 2 bytes, check if we got there.
                    if (decPos > 0)
                    {
                        // CRC receieved
                        if (InternalHelpers::CheckCRC(data, dataLength, crc))
                        {
                            // Dispatch packet
                            pDevice->DispatchPacket(
                                data, static_cast<int>(dataLength));

                            // Now flag engine to start over.
                            curState = E_MARKER;
                            bufPos++;
                            decPos = 0;
                            break;
                        }
                        else
                        {
                            // TODO : Logging
                            // Bad crc...start over
                            curState = E_MARKER;
                            bufPos++;
                            decPos = 0;
                            break;
                        }
                    }                                
                    bufPos++;
                    decPos++;
                }
                delete [] data;
                data = NULL;
                break;
            }
        }
    } while(true);

    //TODO
}

int32_t Device::Rx(uint8_t* pBuffer, std::size_t nSize)
{
    // This should block to receive bytes
    // return number of bytes received
    // bytes are received into buffer
    switch (this->m_connectionType)
    {
        case CONNECTION_TYPE_SOCKET:
        {
            int flags = 0;
            #ifdef __gnu_linux__
            ssize_t nReceivedBytes =
                recv(this->m_socket, pBuffer, nSize, flags);
            #endif

            #ifdef _WIN32
            int nReceivedBytes = recv(this->m_socket,
                                      reinterpret_cast<char*>(pBuffer),
                                      nSize,
                                      flags);
            if (SOCKET_ERROR == nReceivedBytes)
            {
                int nError = WSAGetLastError(); 
                if (nError != WSAEWOULDBLOCK)
                {
                    //std::cerr << "Winsock error code: " << nError << std::endl; 
                    break;
                }
            }

            #endif
            return nReceivedBytes;
            break;
        }
        case CONNECTION_TYPE_NONE:
        {
            std::cerr << "No connection to device.";
        }
    }
    return 0;
}

int32_t Device::Tx(const uint8_t* pBuffer, int32_t length)
{
    // Does not perform the framing...just sends out bytes
    switch (this->m_connectionType)
    {
        case CONNECTION_TYPE_SOCKET:
        {
            #ifdef __gnu_linux__
            return send(this->m_socket, pBuffer, length, 0);
            #endif

            #ifdef _WIN32
            return send(this->m_socket,
                        reinterpret_cast<const char*>(pBuffer),
                        length,
                        0);
            #endif
            break;
        }
        case CONNECTION_TYPE_SERIAL:
        {
            break;
        }
        case CONNECTION_TYPE_NONE:
        {
            std::cerr << "No connection to device.";
            return -1;
            break;
        }
    }

    return 0;
}

void Device::GenerateFrame(const Impinj::STP::Packet& packet)
{
    uint32_t nPacketLength = static_cast<uint32_t>(packet.ByteSize());
    uint32_t nFrameLength = FRAME_HEADER_SIZE + nPacketLength + FRAME_CRC_LEN;

    if (nFrameLength > this->m_TxFrame.capacity())
    {
        this->m_TxFrame.reserve(nFrameLength);
    }

    this->m_TxFrame.resize(nFrameLength);
    
    // Copy over data length
    this->m_TxFrame[5] = (nPacketLength & 0xFF000000) >> 24;
    this->m_TxFrame[6] = (nPacketLength & 0x00FF0000) >> 16;
    this->m_TxFrame[7] = (nPacketLength & 0x0000FF00) >>  8;
    this->m_TxFrame[8] = (nPacketLength & 0x000000FF);

    // Calculate modular sum and set it.
    this->m_TxFrame[9] = InternalHelpers::ModularSum(nPacketLength);
    
    packet.SerializeToArray(&this->m_TxFrame[FRAME_HEADER_SIZE], nPacketLength);

    // Calculate CRC and add it on.
    uint16_t nPacketCrc = InternalHelpers::CalcCRC(
        &this->m_TxFrame[FRAME_HEADER_SIZE], nPacketLength);
    this->m_TxFrame[FRAME_HEADER_SIZE + nPacketLength] =
        (nPacketCrc & 0xFF00) >> 8;
    this->m_TxFrame[FRAME_HEADER_SIZE + nPacketLength + 1] =
        (nPacketCrc & 0x00FF);
}

int32_t Device::TxPacket(const Impinj::STP::Packet& packet)
{
    this->GenerateFrame(packet);
    int32_t nBytesTransmitted =
        this->Tx(&(this->m_TxFrame[0]), this->m_TxFrame.size());

    if (nBytesTransmitted != this->m_TxFrame.size())
    {
        std::cerr << "Failed to transmit frame.";
        return -1;
    }
    return nBytesTransmitted;
}

/**
 * Gets the version of the reader's STP firmware, host library and API version.
 * @param[out] versions A list of all version information.
 * @return See ErrorCode enums
 */
const Impinj::STP::Reply Device::GetVersions(Impinj::STP::Versions& versions)
{
    // Get versions from reader.
    Impinj::STP::Reply reply = this->GetVersionsReader(&versions);

    /* Check if get reader versions failed */
    if (ERROR_CODE_SUCCESS != reply.error_code())
    {
        return reply;
    }

    // Add local version information for Kodiak
    Impinj::STP::Version* pVersion = versions.add_version_list();
    pVersion->set_version_id(VERSION_TYPE_KODIAK_HOST);
    pVersion->set_ident("Comp=impinj_api");

    // Add local version information for STP API
    pVersion = versions.add_version_list();
    pVersion->set_version_id(VERSION_TYPE_API_HOST);
    pVersion->set_ident("Comp=impinj_api");

    return reply;
}

/**
 * Send a command to the device and wait for a response.
 * @return True if a command was received in time.
 */
bool Device::SendCommandWaitForResponse(const Impinj::STP::Packet& packet)
{
    // Transmit frame
    int32_t nResult = this->TxPacket(packet);

    if (0 > nResult)
    {
        return false;
    }

    // Wait for response
    #ifdef __gnu_linux__
    struct timespec timeout;
    clock_gettime(CLOCK_REALTIME, &timeout);
    timeout.tv_sec += (this->m_u32ResponseTimeout_ms/1000);
    timeout.tv_nsec += (this->m_u32ResponseTimeout_ms % 1000) * 1000;
    
    pthread_mutex_lock(&m_read_mutex);
    int cond_retval;
    while(!this->m_bResponseReceived)
    {
        cond_retval = pthread_cond_timedwait(&m_read_cond, 
                                             &m_read_mutex, 
                                             &timeout);
    }
    this->m_bResponseReceived = false;
    pthread_mutex_unlock(&m_read_mutex);
    if (ETIMEDOUT == cond_retval)
    #endif

    #ifdef _WIN32
    DWORD timeout_ms = this->m_u32ResponseTimeout_ms;
    DWORD waitRet = WaitForSingleObject(m_read_cond, timeout_ms);
    if (WAIT_OBJECT_0 != waitRet)
    #endif
    {
        std::cerr << "Command timed-out" << std::endl;
        return false;
    }

    // When we got here, there is a good response in rxPacket.
    return true;
}

const Impinj::STP::Reply Device::SendHandshake(void)
{
    // Create a message for sending to reader.
    Impinj::STP::Packet packet;
    Impinj::STP::HandShake* pHandshake =
        packet.mutable_command()->mutable_connect()->mutable_hand_shake();
    pHandshake->set_api_name(this->ApiName());
    pHandshake->set_api_version(this->ApiVersion());

    // Transmit message and block until we get a response.
    this->SendCommandWaitForResponse(packet);

    // Process any data that came back and return response.
    return this->m_RxPacket.response().connect().reply();
}

std::string Device::ApiToolVersion(void)
{
    return std::string("2.7.11.240");
}

uint32_t Device::ApiVersion(void)
{
    return STP_API_VERSION;
}

std::string Device::ApiName(void)
{
    return STP_API_NAME;
}

void Device::RegisterAllReportsHandler(IAllReportsHandler* pHandler)
{
    this->m_pAllReportHandler = pHandler;
}

void Device::UnregisterAllReportsHandler(IAllReportsHandler* pHandler)
{
    this->m_pAllReportHandler = NULL;
}

void Device::SetResponseTimeout(uint32_t u32ResponseTimeout_ms)
{
    this->m_u32ResponseTimeout_ms = u32ResponseTimeout_ms;
}

/**
 * Set the timeout for a response to a command back to its default.
 */
void Device::ResetResponseTimeout(void)
{
    this->m_u32ResponseTimeout_ms = IPJ_DEFAULT_RESPONSE_TIMEOUT_MS;
}

void ReportHandler::HandleAllReports(const Impinj::STP::Report& report,
                                     const Impinj::STP::ReportType type)
{
    std::cerr << "STP: No report hander registered for "
              << type << std::endl;
}


/**
 * \details 
 
 */
const Impinj::STP::Reply Device::Connect()
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_connect()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .connect()
                .reply();

    return reply;
}

/**
 * \details 
 * \param versions A list of remote device API and firmware versions. 
 */
const Impinj::STP::Reply Device::GetVersionsReader(
    Impinj::STP::Versions* versions)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_get_versions_reader()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .get_versions_reader()
                .reply();
    if (NULL == versions)
    {
        reply.set_error_code(ERROR_CODE_INVALID_VALUE);
        return reply;
    }
    *versions = 
        this->m_RxPacket.response().get_versions_reader().versions();

    return reply;
}

/**
 * \details 
 
 */
const Impinj::STP::Reply Device::Start()
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_start()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .start()
                .reply();

    return reply;
}

/**
 * \details 
 
 */
const Impinj::STP::Reply Device::Stop()
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_stop()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .stop()
                .reply();

    return reply;
}

/**
 * \details Set how GPOs change during encode and inventory. 
 * \param device_config 
 */
const Impinj::STP::Reply Device::SetDeviceConfig(
    const Impinj::STP::DeviceConfig& device_config)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_set_device_config()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_set_device_config()
          ->mutable_device_config()) = device_config;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .set_device_config()
                .reply();

    return reply;
}

/**
 * \details 
 * \param device_config 
 */
const Impinj::STP::Reply Device::GetDeviceConfig(
    Impinj::STP::DeviceConfig* device_config)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_get_device_config()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .get_device_config()
                .reply();
    if (NULL == device_config)
    {
        reply.set_error_code(ERROR_CODE_INVALID_VALUE);
        return reply;
    }
    *device_config = 
        this->m_RxPacket.response().get_device_config().device_config();

    return reply;
}

/**
 * \details 
 * \param antennas_config 
 */
const Impinj::STP::Reply Device::SetAntennaConfig(
    const Impinj::STP::AntennasConfig& antennas_config)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_set_antenna_config()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_set_antenna_config()
          ->mutable_antennas_config()) = antennas_config;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .set_antenna_config()
                .reply();

    return reply;
}

/**
 * \details 
 * \param antennas_config 
 */
const Impinj::STP::Reply Device::GetAntennaConfig(
    Impinj::STP::AntennasConfig* antennas_config)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_get_antenna_config()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .get_antenna_config()
                .reply();
    if (NULL == antennas_config)
    {
        reply.set_error_code(ERROR_CODE_INVALID_VALUE);
        return reply;
    }
    *antennas_config = 
        this->m_RxPacket.response().get_antenna_config().antennas_config();

    return reply;
}

/**
 * \details Call before \ref start to enable GPI triggering. 
 
 */
const Impinj::STP::Reply Device::Engage()
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_engage()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .engage()
                .reply();

    return reply;
}

/**
 * \details Call after stopping the encode or inventory operation so the device can be reconfigured. 
 
 */
const Impinj::STP::Reply Device::Disengage()
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_disengage()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .disengage()
                .reply();

    return reply;
}

/**
 * \details When the GPOConfig enable_operation_result_gpo is set false, this command lets the client application set the pass/fail GPO based on its own pass/fail conditions. 
 * \param operation_summary_result 
 */
const Impinj::STP::Reply Device::SetOperationResultGpo(
    const Impinj::STP::OperationSummaryResult& operation_summary_result)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_set_operation_result_gpo()
        ->Clear();
    packet.mutable_command()
        ->mutable_set_operation_result_gpo()
        ->set_operation_summary_result(operation_summary_result);
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .set_operation_result_gpo()
                .reply();

    return reply;
}

/**
 * \details 
 * \param serialization_job 
 */
const Impinj::STP::Reply Device::AddSerializationJob(
    const Impinj::STP::SerializationJob& serialization_job)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_add_serialization_job()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_add_serialization_job()
          ->mutable_serialization_job()) = serialization_job;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .add_serialization_job()
                .reply();

    return reply;
}

/**
 * \details 
 * \param job_id 
 */
const Impinj::STP::Reply Device::RemoveSerializationJob(uint32_t job_id)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_remove_serialization_job()
        ->Clear();
    packet.mutable_command()
        ->mutable_remove_serialization_job()
        ->set_job_id(job_id);
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .remove_serialization_job()
                .reply();

    return reply;
}

/**
 * \details 
 * \param table_entries 
 */
const Impinj::STP::Reply Device::AddTidTableEntries(
    const Impinj::STP::TIDTableEntries& table_entries)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_add_tid_table_entries()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_add_tid_table_entries()
          ->mutable_table_entries()) = table_entries;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .add_tid_table_entries()
                .reply();

    return reply;
}

/**
 * \details 
 * \param ordered_list_entries 
 */
const Impinj::STP::Reply Device::AddOrderedListEntries(
    const Impinj::STP::OrderedListEntries& ordered_list_entries)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_add_ordered_list_entries()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_add_ordered_list_entries()
          ->mutable_ordered_list_entries()) = ordered_list_entries;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .add_ordered_list_entries()
                .reply();

    return reply;
}

/**
 * \details 
 * \param inventory_job 
 */
const Impinj::STP::Reply Device::SetInventoryJob(
    const Impinj::STP::InventoryJob& inventory_job)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_set_inventory_job()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_set_inventory_job()
          ->mutable_inventory_job()) = inventory_job;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .set_inventory_job()
                .reply();

    return reply;
}

/**
 * \details 
 * \param carton_contents_job 
 */
const Impinj::STP::Reply Device::SetCartonContentsJob(
    const Impinj::STP::CartonContentsJob& carton_contents_job)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_set_carton_contents_job()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_set_carton_contents_job()
          ->mutable_carton_contents_job()) = carton_contents_job;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .set_carton_contents_job()
                .reply();

    return reply;
}

/**
 * \details 
 * \param operation_data 
 */
const Impinj::STP::Reply Device::GetOperationData(
    Impinj::STP::OperationData* operation_data)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_get_operation_data()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .get_operation_data()
                .reply();
    if (NULL == operation_data)
    {
        reply.set_error_code(ERROR_CODE_INVALID_VALUE);
        return reply;
    }
    *operation_data = 
        this->m_RxPacket.response().get_operation_data().operation_data();

    return reply;
}

/**
 * \details 
 * \param license_key 
 */
const Impinj::STP::Reply Device::AddLicenseKey(
    const Impinj::STP::LicenseKey& license_key)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_add_license_key()
        ->Clear();
    *(packet.mutable_command()
          ->mutable_add_license_key()
          ->mutable_license_key()) = license_key;
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .add_license_key()
                .reply();

    return reply;
}

/**
 * \details 
 * \param device_state 
 */
const Impinj::STP::Reply Device::GetDeviceState(
    Impinj::STP::DeviceState* device_state)
{
    Impinj::STP::Reply reply;
    Impinj::STP::Packet packet;

    packet.mutable_command()
        ->mutable_get_device_state()
        ->Clear();
    
    // Transmit message and block until we get a response.
    if (!this->SendCommandWaitForResponse(packet))
    {
        reply.set_error_code(ERROR_CODE_NO_DEVICE_CONNECTION);
        return reply;
    }
    reply = this->m_RxPacket.response()
                .get_device_state()
                .reply();
    if (NULL == device_state)
    {
        reply.set_error_code(ERROR_CODE_INVALID_VALUE);
        return reply;
    }
    *device_state = 
        this->m_RxPacket.response().get_device_state().device_state();

    return reply;
}


/** Handle a report and return true. Return false if unknown or empty report. */
bool Device::HandleReport(const Impinj::STP::Report& rxReport)
{
    if (rxReport.has_operation_summary_report())
    {
        if (this->m_pOperationSummaryReportHandler)
        {
            this->m_pOperationSummaryReportHandler
                ->HandleOperationSummaryReport(
                    rxReport.operation_summary_report());
        }
        if (this->m_pAllReportHandler)
        {
            this->m_pAllReportHandler->HandleAllReports(
                rxReport, Impinj::STP::REPORT_TYPE_OPERATION_SUMMARY);
        }
    }
    else if (rxReport.has_tag_operation_report())
    {
        if (this->m_pTagOperationReportHandler)
        {
            this->m_pTagOperationReportHandler
                ->HandleTagOperationReport(
                    rxReport.tag_operation_report());
        }
        if (this->m_pAllReportHandler)
        {
            this->m_pAllReportHandler->HandleAllReports(
                rxReport, Impinj::STP::REPORT_TYPE_TAG_OPERATION);
        }
    }
    else if (rxReport.has_device_event_report())
    {
        if (this->m_pDeviceEventReportHandler)
        {
            this->m_pDeviceEventReportHandler
                ->HandleDeviceEventReport(
                    rxReport.device_event_report());
        }
        if (this->m_pAllReportHandler)
        {
            this->m_pAllReportHandler->HandleAllReports(
                rxReport, Impinj::STP::REPORT_TYPE_DEVICE_EVENT);
        }
    }
    else if (rxReport.has_carton_contents_report())
    {
        if (this->m_pCartonContentsReportHandler)
        {
            this->m_pCartonContentsReportHandler
                ->HandleCartonContentsReport(
                    rxReport.carton_contents_report());
        }
        if (this->m_pAllReportHandler)
        {
            this->m_pAllReportHandler->HandleAllReports(
                rxReport, Impinj::STP::REPORT_TYPE_CARTON_CONTENTS);
        }
    }
    else
    {
        return false;
    }
    return true;
}

void Device::HandleOperationSummaryReport(const Impinj::STP::Report& rxReport)
{
    this->m_pOperationSummaryReportHandler->HandleOperationSummaryReport(
        rxReport.operation_summary_report());
}

void Device::HandleTagOperationReport(const Impinj::STP::Report& rxReport)
{
    this->m_pTagOperationReportHandler->HandleTagOperationReport(
        rxReport.tag_operation_report());
}

void Device::HandleDeviceEventReport(const Impinj::STP::Report& rxReport)
{
    this->m_pDeviceEventReportHandler->HandleDeviceEventReport(
        rxReport.device_event_report());
}

void Device::HandleCartonContentsReport(const Impinj::STP::Report& rxReport)
{
    this->m_pCartonContentsReportHandler->HandleCartonContentsReport(
        rxReport.carton_contents_report());
}

void Device::RegisterOperationSummaryReportHandler(
    IOperationSummaryReportHandler * pIOperationSummaryReport)
{
    this->m_pOperationSummaryReportHandler = pIOperationSummaryReport;
}

void Device::UnregisterOperationSummaryReportHandler(
    IOperationSummaryReportHandler *)
{
    this->m_pOperationSummaryReportHandler = NULL;
}

void Device::RegisterTagOperationReportHandler(
    ITagOperationReportHandler * pITagOperationReport)
{
    this->m_pTagOperationReportHandler = pITagOperationReport;
}

void Device::UnregisterTagOperationReportHandler(
    ITagOperationReportHandler *)
{
    this->m_pTagOperationReportHandler = NULL;
}

void Device::RegisterDeviceEventReportHandler(
    IDeviceEventReportHandler * pIDeviceEventReport)
{
    this->m_pDeviceEventReportHandler = pIDeviceEventReport;
}

void Device::UnregisterDeviceEventReportHandler(
    IDeviceEventReportHandler *)
{
    this->m_pDeviceEventReportHandler = NULL;
}

void Device::RegisterCartonContentsReportHandler(
    ICartonContentsReportHandler * pICartonContentsReport)
{
    this->m_pCartonContentsReportHandler = pICartonContentsReport;
}

void Device::UnregisterCartonContentsReportHandler(
    ICartonContentsReportHandler *)
{
    this->m_pCartonContentsReportHandler = NULL;
}

void ReportHandler::HandleOperationSummaryReport(
    const OperationSummaryReport& report)
{
    std::cerr << "STP: No handler for OperationSummaryReport." << std::endl;
}

void ReportHandler::HandleTagOperationReport(
    const TagOperationReport& report)
{
    std::cerr << "STP: No handler for TagOperationReport." << std::endl;
}

void ReportHandler::HandleDeviceEventReport(
    const DeviceEventReport& report)
{
    std::cerr << "STP: No handler for DeviceEventReport." << std::endl;
}

void ReportHandler::HandleCartonContentsReport(
    const CartonContentsReport& report)
{
    std::cerr << "STP: No handler for CartonContentsReport." << std::endl;
}

}
}