Program Listing for File usb_tunnel_interface.cpp

Return to documentation for file (src/knx/usb_tunnel_interface.cpp)

#include "config.h"
#ifdef USE_USB

#include "bits.h"
#include "usb_tunnel_interface.h"
#include "cemi_server.h"
#include "cemi_frame.h"

#include <stdio.h>
#include <string.h>

#define MIN(a, b) ((a < b) ? (a) : (b))

#define MAX_EP_SIZE 64
#define HID_HEADER_SIZE 3
#define MAX_KNX_TELEGRAM_SIZE 263
#define KNX_HID_REPORT_ID 0x01
#define PROTOCOL_VERSION 0x00
#define PROTOCOL_HEADER_LENGTH 0x08

// Maximum possible payload data bytes in a transfer protocol body
#define MAX_DATASIZE_START_PACKET 52
#define MAX_DATASIZE_PARTIAL_PACKET 61

#define PACKET_TYPE_START 1
#define PACKET_TYPE_END 2
#define PACKET_TYPE_PARTIAL 4

//#define DEBUG_TX_HID_REPORT
//#define DEBUG_RX_HID_REPORT

extern bool sendHidReport(uint8_t* data, uint16_t length);
extern bool isSendHidReportPossible();

// class UsbTunnelInterface

UsbTunnelInterface::UsbTunnelInterface(CemiServer& cemiServer,
                                       uint16_t mId,
                                       uint16_t mV)
    : _cemiServer(cemiServer),
      _manufacturerId(mId),
      _maskVersion(mV)
{
}

void UsbTunnelInterface::loop()
{
    // Make sure that the USB HW is also ready to send another report
    if (!isTxQueueEmpty() && isSendHidReportPossible())
    {
        uint8_t* buffer;
        uint16_t length;
        loadNextTxFrame(&buffer, &length);
        sendHidReport(buffer, length);
        delete buffer;
    }

    // Check if we already a COMPLETE transport protocol packet
    // A transport protocol packet might be split into multiple HID reports and
    // need to be assembled again
    if (rxHaveCompletePacket)
    {
        handleHidReportRxQueue();
        rxHaveCompletePacket = false;
    }
}

/* USB TX */

void UsbTunnelInterface::sendCemiFrame(CemiFrame& frame)
{
    sendKnxHidReport(KnxTunneling, ServiceIdNotUsed, frame.data(), frame.dataLength());
}

void UsbTunnelInterface::addBufferTxQueue(uint8_t* data, uint16_t length)
{
    _queue_buffer_t* tx_buffer = new _queue_buffer_t;

    tx_buffer->length = MAX_EP_SIZE;
    tx_buffer->data = new uint8_t[MAX_EP_SIZE]; // We always have to send full max. USB endpoint size of 64 bytes
    tx_buffer->next = nullptr;

    memcpy(tx_buffer->data, data, tx_buffer->length);
    memset(&tx_buffer->data[length], 0x00, MAX_EP_SIZE - length); // Set unused bytes to zero

    if (_tx_queue.back == nullptr)
    {
        _tx_queue.front = _tx_queue.back = tx_buffer;
    }
    else
    {
        _tx_queue.back->next = tx_buffer;
        _tx_queue.back = tx_buffer;
    }
}

bool UsbTunnelInterface::isTxQueueEmpty()
{
    if (_tx_queue.front == nullptr)
    {
        return true;
    }

    return false;
}

void UsbTunnelInterface::loadNextTxFrame(uint8_t** sendBuffer, uint16_t* sendBufferLength)
{
    if (_tx_queue.front == nullptr)
    {
        return;
    }

    _queue_buffer_t* tx_buffer = _tx_queue.front;
    *sendBuffer = tx_buffer->data;
    *sendBufferLength = tx_buffer->length;
    _tx_queue.front = tx_buffer->next;

    if (_tx_queue.front == nullptr)
    {
        _tx_queue.back = nullptr;
    }

    delete tx_buffer;

#ifdef DEBUG_TX_HID_REPORT
    print("TX HID report: len: ");
    // We do not print the padded zeros
    uint8_t len = (*sendBuffer)[2];
    println(len, DEC);

    for (int i = 0; i < len; i++)
    {
        if ((*sendBuffer)[i] < 16)
            print("0");

        print((*sendBuffer)[i], HEX);
        print(" ");
    }

    println("");
#endif
}

void UsbTunnelInterface::sendKnxHidReport(ProtocolIdType protId, ServiceIdType servId, uint8_t* data, uint16_t length)
{
    uint16_t maxData = MAX_DATASIZE_START_PACKET;
    uint8_t packetType = PACKET_TYPE_START;

    if (length > maxData)
    {
        packetType |= PACKET_TYPE_PARTIAL;
    }

    uint16_t offset = 0;
    uint8_t* buffer = nullptr;

    // In theory we can only have sequence numbers from 1..5
    // First packet: 51 bytes max
    // Other packets: 62 bytes max.
    // -> 51 + 4*62 = 296 bytes -> enough for a KNX cEMI extended frame APDU + Transport Protocol Header length
    for (uint8_t seqNum = 1; seqNum < 6; seqNum++)
    {
        uint16_t copyLen = MIN(length, maxData);

        // If this is the first packet we include the transport protocol header
        if (packetType & PACKET_TYPE_START)
        {
            buffer = new uint8_t[copyLen + 8 + HID_HEADER_SIZE]; // length of transport protocol header: 11 bytes
            buffer[2]  = 8 + copyLen; // KNX USB Transfer Protocol Body length
            buffer[3]  = PROTOCOL_VERSION; // Protocol version (fixed 0x00)
            buffer[4]  = PROTOCOL_HEADER_LENGTH; // USB KNX Transfer Protocol Header Length (fixed 0x08)
            pushWord(copyLen, &buffer[5]); // KNX USB Transfer Protocol Body length (e.g. cEMI length)
            buffer[7]  = (uint8_t) protId; // KNX Tunneling (0x01) or KNX Bus Access Server (0x0f)
            buffer[8]  = (protId == KnxTunneling) ? (uint8_t)CEMI : (uint8_t)servId; // either EMI ID or Service Id
            buffer[9]  = 0x00; // Manufacturer (fixed 0x00) see KNX Spec 9/3 p.23 3.4.1.3.5
            buffer[10] = 0x00; // Manufacturer (fixed 0x00) see KNX Spec 9/3 p.23 3.4.1.3.5
            memcpy(&buffer[11], &data[offset], copyLen); // Copy payload for KNX USB Transfer Protocol Body
        }
        else
        {
            buffer = new uint8_t[copyLen]; // no transport protocol header in partial packets
            buffer[2] = copyLen; // KNX USB Transfer Protocol Body length
            memcpy(&buffer[0], &data[offset], copyLen); // Copy payload for KNX USB Transfer Protocol Body
        }

        offset += copyLen;

        if (offset >= length)
        {
            packetType |= PACKET_TYPE_END;
        }

        buffer[0]  = KNX_HID_REPORT_ID; // ReportID (fixed 0x01)
        buffer[1]  = ((seqNum << 4) & 0xF0) | (packetType & 0x07); // PacketInfo (SeqNo and Type)

        addBufferTxQueue(buffer, (buffer[2] + HID_HEADER_SIZE));

        delete[] buffer;

        if (offset >= length)
        {
            break;
        }

        packetType &= ~PACKET_TYPE_START;
        maxData = MAX_DATASIZE_PARTIAL_PACKET;
    }
}

/* USB RX */

// Invoked when received SET_REPORT control request or via interrupt out pipe
void UsbTunnelInterface::receiveHidReport(uint8_t const* data, uint16_t bufSize)
{
    // Check KNX ReportID (fixed 0x01)
    if (data[0] == KNX_HID_REPORT_ID)
    {
        // We just store only the used space of the HID report buffer
        // which is normally padded with 0 to fill the complete USB EP size (e.g. 64 bytes)
        uint8_t packetLength = data[2] + HID_HEADER_SIZE;
        UsbTunnelInterface::addBufferRxQueue(data, packetLength);

        // Check if packet type indicates last packet
        if ((data[1] & PACKET_TYPE_END) == PACKET_TYPE_END)
        {
            // Signal main loop that we have a complete KNX USB packet
            rxHaveCompletePacket = true;
        }
    }
}

UsbTunnelInterface::_queue_t UsbTunnelInterface::_rx_queue;
bool UsbTunnelInterface::rxHaveCompletePacket = false;

void UsbTunnelInterface::addBufferRxQueue(const uint8_t* data, uint16_t length)
{
    _queue_buffer_t* rx_buffer = new _queue_buffer_t;

    rx_buffer->length = length;
    rx_buffer->data = new uint8_t[rx_buffer->length];
    rx_buffer->next = nullptr;

    memcpy(rx_buffer->data, data, rx_buffer->length);

    if (_rx_queue.back == nullptr)
    {
        _rx_queue.front = _rx_queue.back = rx_buffer;
    }
    else
    {
        _rx_queue.back->next = rx_buffer;
        _rx_queue.back = rx_buffer;
    }
}

bool UsbTunnelInterface::isRxQueueEmpty()
{
    if (_rx_queue.front == nullptr)
    {
        return true;
    }

    return false;
}

void UsbTunnelInterface::loadNextRxBuffer(uint8_t** receiveBuffer, uint16_t* receiveBufferLength)
{
    if (_rx_queue.front == nullptr)
    {
        return;
    }

    _queue_buffer_t* rx_buffer = _rx_queue.front;
    *receiveBuffer = rx_buffer->data;
    *receiveBufferLength = rx_buffer->length;
    _rx_queue.front = rx_buffer->next;

    if (_rx_queue.front == nullptr)
    {
        _rx_queue.back = nullptr;
    }

    delete rx_buffer;

#ifdef DEBUG_RX_HID_REPORT
    print("RX HID report: len: ");
    println(*receiveBufferLength, DEC);

    for (int i = 0; i < (*receiveBufferLength); i++)
    {
        if ((*receiveBuffer)[i] < 16)
            print("0");

        print((*receiveBuffer)[i], HEX);
        print(" ");
    }

    println("");
#endif
}

void UsbTunnelInterface::handleTransferProtocolPacket(uint8_t* data, uint16_t length)
{
    if (data[0] == PROTOCOL_VERSION && // Protocol version (fixed 0x00)
            data[1] == PROTOCOL_HEADER_LENGTH)   // USB KNX Transfer Protocol Header Length (fixed 0x08)
    {
        uint16_t bodyLength;
        popWord(bodyLength, (uint8_t*)&data[2]); // KNX USB Transfer Protocol Body length

        if (data[4] == (uint8_t) BusAccessServer)   // Bus Access Server Feature (0x0F)
        {
            handleBusAccessServerProtocol((ServiceIdType)data[5], &data[8], bodyLength);
        }
        else if (data[4] == (uint8_t) KnxTunneling) // KNX Tunneling (0x01)
        {
            if (data[5] == (uint8_t) CEMI)   // EMI type: only cEMI supported (0x03))
            {
                // Prepare the cEMI frame
                CemiFrame frame((uint8_t*)&data[8], bodyLength);
                /*
                        print("cEMI USB RX len: ");
                        print(length);

                        print(" data: ");
                        printHex(" data: ", buffer, length);
                */
                _cemiServer.frameReceived(frame);
            }
            else
            {
                println("Error: Only cEMI is supported!");
            }
        }
    }
}

void UsbTunnelInterface::handleHidReportRxQueue()
{
    if (isRxQueueEmpty())
    {
        println("Error: RX HID report queue was empty!");
        return;
    }

    uint8_t tpPacket[MAX_KNX_TELEGRAM_SIZE + PROTOCOL_HEADER_LENGTH]; // Transport Protocol Header + Body
    uint16_t offset = 0;
    bool success = false;

    // Now we have to reassemble the whole transport protocol packet which might be distributed over multiple HID reports

    // In theory we can only have sequence numbers from 1..5
    // First packet: 51 bytes max
    // Other packets: 62 bytes max.
    // -> 51 + 4*62 = 296 bytes -> enough for a KNX cEMI extended frame APDU + Transport Protocol Header length
    for (int expSeqNum = 1; expSeqNum < 6; expSeqNum++)
    {
        // We should have at least one packet: either single packet (START and END set) or
        // start packet (START and PARTIAL set) -> thus load first part
        uint8_t* data;
        uint16_t bufSize;
        loadNextRxBuffer(&data, &bufSize); // bufSize contains the complete HID report length incl. HID header

        // Get KNX HID report header details
        uint8_t seqNum = data[1] >> 4;
        uint8_t packetType = data[1] & 0x07;
        uint8_t packetLength = MIN(data[2], bufSize - HID_HEADER_SIZE); // Do not try to read more than we actually have!

        // Does the received sequence number match the expected one?
        if (expSeqNum != seqNum)
        {
            println("Error: Wrong sequence number!");
            delete data;
            continue;
        }

        // first RX buffer from queue should contain the first part of the transfer protocol packet
        if ((expSeqNum == 1) && ((packetType & PACKET_TYPE_START) != PACKET_TYPE_START))
        {
            println("Error: Sequence number 1 does not contain a START packet!");
            delete data;
            continue;
        }

        // Make sure we only have one START packet
        if ((expSeqNum != 1) && ((packetType & PACKET_TYPE_START) == PACKET_TYPE_START))
        {
            println("Error: Sequence number (!=1) contains a START packet!");
            delete data;
            continue;
        }

        // Make sure other packets are marked correctly as PARTIAL packet
        if ((expSeqNum != 1) && ((packetType & PACKET_TYPE_PARTIAL) != PACKET_TYPE_PARTIAL))
        {
            println("Error: Sequence number (!=1) must be a PARTIAL packet!");
            delete data;
            continue;
        }

        // Not really necessary, but we reset the offset here to zero
        if ((packetType & PACKET_TYPE_START) == PACKET_TYPE_START)
        {
            offset = 0;
        }

        // Copy KNX HID Report Body to final buffer for concatenating
        memcpy(&tpPacket[offset], &data[3], packetLength);
        // Remove the source HID report buffer
        delete data;
        // Move offset
        offset += packetLength;

        // If we reached the end of the transport protocol packet, leave the loop
        if ((packetType & PACKET_TYPE_END) == PACKET_TYPE_END)
        {
            success = true;
            break;
        }
    }

    // Make sure that we really saw the end of the transport protocol packet
    if (success)
    {
        handleTransferProtocolPacket(tpPacket, offset);
    }
    else
    {
        println("Error: Did not find END packet!");
    }
}

void UsbTunnelInterface::handleBusAccessServerProtocol(ServiceIdType servId, const uint8_t* requestData, uint16_t packetLength)
{
    uint8_t respData[3]; // max. 3 bytes are required for a response

    switch (servId)
    {
        case DeviceFeatureGet: // Device Feature Get
        {
            FeatureIdType featureId = (FeatureIdType)requestData[0];
            respData[0] = (uint8_t) featureId; // first byte in repsonse is the featureId itself again

            switch (featureId)
            {
                case SupportedEmiType: // Supported EMI types
                    println("Device Feature Get: Supported EMI types");
                    respData[1] = 0x00; // USB KNX Transfer Protocol Body: Feature Data
                    respData[2] = 0x04; // USB KNX Transfer Protocol Body: Feature Data -> only cEMI supported
                    sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3);
                    break;

                case HostDeviceDescriptorType0: // Host Device Descriptor Type 0
                    println("Device Feature Get: Host Device Descriptor Type 0");
                    pushWord(_maskVersion, &respData[1]); // USB KNX Transfer Protocol Body: Feature Data -> Mask version
                    sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3);
                    break;

                case BusConnectionStatus: // Bus connection status
                    println("Device Feature Get: Bus connection status");
                    respData[1] = 1; // USB KNX Transfer Protocol Body: Feature Data -> bus connection status
                    sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 2);
                    break;

                case KnxManufacturerCode: // KNX manufacturer code
                    println("Device Feature Get: KNX manufacturer code");
                    pushWord(_manufacturerId, &respData[1]); // USB KNX Transfer Protocol Body: Feature Data -> Manufacturer Code
                    sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 3);
                    break;

                case ActiveEmiType: // Active EMI type
                    println("Device Feature Get: Active EMI type");
                    respData[1] = (uint8_t) CEMI; // USB KNX Transfer Protocol Body: Feature Data -> cEMI type ID
                    sendKnxHidReport(BusAccessServer, DeviceFeatureResponse, respData, 2);
                    break;

                default:
                    break;
            }

            break;
        }

        case DeviceFeatureSet: // Device Feature Set
        {
            FeatureIdType featureId = (FeatureIdType)requestData[0];

            switch (featureId)
            {
                case ActiveEmiType: // Active EMI type
                    print("Device Feature Set: Active EMI type: ");

                    if (requestData[1] < 16)
                        print("0");

                    println(requestData[1], HEX); // USB KNX Transfer Protocol Body: Feature Data -> EMI TYPE ID
                    break;

                // All other featureIds must not be set
                case SupportedEmiType: // Supported EMI types
                case HostDeviceDescriptorType0: // Host Device Descriptor Type 0
                case BusConnectionStatus: // Bus connection status
                case KnxManufacturerCode: // KNX manufacturer code
                default:
                    break;
            }

            break;
        }

        // These are only sent from the device to the host
        case DeviceFeatureResponse: // Device Feature Response
        case DeviceFeatureInfo:     // Device Feature Info
        case DeviceFeatureEscape:   // reserved (ESCAPE for future extensions)
        default:
            break;
    }
}

/* USB HID report descriptor for KNX HID */

const uint8_t UsbTunnelInterface::descHidReport[] =
{
    //TUD_HID_REPORT_DESC_KNXHID_INOUT(64)
    0x06, 0xA0, 0xFF,  // Usage Page (Vendor Defined 0xFFA0)
    0x09, 0x01,        // Usage (0x01)
    0xA1, 0x01,        // Collection (Application)
    0x09, 0x01,        //   Usage (0x01)
    0xA1, 0x00,        //   Collection (Physical)
    0x06, 0xA1, 0xFF,  //     Usage Page (Vendor Defined 0xFFA1)
    0x09, 0x03,        //     Usage (0x03)
    0x09, 0x04,        //     Usage (0x04)
    0x15, 0x80,        //     Logical Minimum (-128)
    0x25, 0x7F,        //     Logical Maximum (127)
    0x35, 0x00,        //     Physical Minimum (0)
    0x45, 0xFF,        //     Physical Maximum (-1)
    0x75, 0x08,        //     Report Size (8)
    0x85, 0x01,        //     Report ID (1)
    0x95, 0x3F,        //     Report Count (63)
    0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    0x09, 0x05,        //     Usage (0x05)
    0x09, 0x06,        //     Usage (0x06)
    0x15, 0x80,        //     Logical Minimum (-128)
    0x25, 0x7F,        //     Logical Maximum (127)
    0x35, 0x00,        //     Physical Minimum (0)
    0x45, 0xFF,        //     Physical Maximum (-1)
    0x75, 0x08,        //     Report Size (8)
    0x85, 0x01,        //     Report ID (1)
    0x95, 0x3F,        //     Report Count (63)
    0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
    0xC0,              //   End Collection
    0xC0               // End Collection
};

const uint8_t* UsbTunnelInterface::getKnxHidReportDescriptor()
{
    return &descHidReport[0];
}

uint16_t UsbTunnelInterface::getHidReportDescriptorLength()
{
    return sizeof(descHidReport);
}

#endif