Skip to content

Writing To and Reading From a Device

Christian Findlay edited this page Aug 3, 2019 · 12 revisions

Usb.Net and Hid.Net are designed to be used in a similar fashion to the Request/Response pattern.

Not all devices fit this pattern so theses libraries won't fit all scenarios. For example, these libraries would not suit the scenario of creating a mouse driver. A mouse driver's feedback is driven by the user's physical movement of the mouse. The device will send data to the computer in an ad hoc way. These libraries are generally designed for cases where you want to send some data to the device, and then have some data returned to the computer.

The libraries expect you to send data in the form of an array of bytes, and another array of bytes will be returned. Generally speaking, the device will expect an array of 64 bytes, or a higher number if the device is designed for high speed transfer. If you need to send more data to the device than the largest size the device will accept, you will need to break it up in to smaller chunks. Most devices will support this, but you will need to see the device's specific documentation for this. You can check the max size of the array in the WriteBufferSize, and ReadBufferSize properties of DeviceDefinition for the device. The definition can be found on any class that inherits from DeviceBase.

To write data, call the WriteAndReadAsync method with your array of data. If the class inherits from DeviceBase, the method will be thread safe. You can safely call it and processing will occur in order. It should happen in a first in, first out fashion. If you use the WriteAsync, or ReadAsync methods, thread safety is not guaranteed.

Here is a very basic example:

//Create a buffer with 3 bytes (initialize)
var buffer = new byte[64];
buffer[0] = 0x3f;
buffer[1] = 0x23;
buffer[2] = 0x23;

//Write the data to the device and get the response
var readBuffer = await trezorDevice.WriteAndReadAsync(buffer);

Seeing that data is all written in binary, you will probably be required to serialize or deserialize the data in some way. Google's Protocol Buffers protocol is a good protocol for converting data to objects, and converting objects to data. Satoshi Labs implemented this protocol for their devices. Here is an example of how objects can be converted to data arrays, and vice versa:

This code takes an object of a known type, and converts it a data array before sending it to the device:

private async Task WriteAsync(object msg)
{
    Logger.Log($"Write: {msg}", null, LogSection);

    var byteArray = Serialize(msg);

    //This confirms that the message data is correct
    // var testMessage = Deserialize(msg.GetType(), byteArray);

    var msgSize = byteArray.Length;
    var msgName = msg.GetType().Name;

    var messageTypeString = "MessageType" + msgName;

    var messageType = GetEnumValue(messageTypeString);

    var msgId = (int)messageType;
    var data = new ByteBuffer(msgSize + 1024); // 32768);
    data.Put((byte)'#');
    data.Put((byte)'#');
    data.Put((byte)((msgId >> 8) & 0xFF));
    data.Put((byte)(msgId & 0xFF));
    data.Put((byte)((msgSize >> 24) & 0xFF));
    data.Put((byte)((msgSize >> 16) & 0xFF));
    data.Put((byte)((msgSize >> 8) & 0xFF));
    data.Put((byte)(msgSize & 0xFF));
    data.Put(byteArray);

    while (data.Position % 63 > 0)
    {
        data.Put(0);
    }

    var chunks = data.Position / 63;

    var wholeArray = data.ToArray();

    for (var i = 0; i < chunks; i++)
    {
        var range = new byte[64];
        range[0] = (byte)'?';

        for (var x = 0; x < 63; x++)
        {
            range[x + 1] = wholeArray[(i * 63) + x];
        }

        await Device.WriteAsync(range);
    }

    _LastWrittenMessage = msg;
}

Code Reference

Here is an example of reading the raw data and then converting the data to a known typed object:

private async Task<object> ReadAsync()
{
    //Read a chunk
    var readBuffer = await Device.ReadAsync();

    //Check to see that this is a valid first chunk 
    var firstByteNot63 = readBuffer[0] != (byte)'?';
    var secondByteNot35 = readBuffer[1] != 35;
    var thirdByteNot35 = readBuffer[2] != 35;
    if (firstByteNot63 || secondByteNot35 || thirdByteNot35)
    {
        var message = $"An error occurred while attempting to read the message from the device. The last written message was a {_LastWrittenMessage?.GetType().Name}. In the first chunk of data ";

        if (firstByteNot63)
        {
            message += "the first byte was not 63";
        }

        if (secondByteNot35)
        {
            message += "the second byte was not 35";
        }

        if (thirdByteNot35)
        {
            message += "the third byte was not 35";
        }

        throw new ReadException(message, readBuffer, _LastWrittenMessage);
    }

    //Looks like the message type is at index 4
    var messageTypeInt = readBuffer[4];

    if (!Enum.IsDefined(MessageTypeType, (int)messageTypeInt))
    {
        throw new Exception($"The number {messageTypeInt} is not a valid MessageType");
    }

    //Get the message type
    var messageTypeValueName = Enum.GetName(MessageTypeType, messageTypeInt);

    var messageType = (TMessageType)Enum.Parse(MessageTypeType, messageTypeValueName);

    //msgLength:= int(binary.BigEndian.Uint32(buf[i + 4 : i + 8]))
    //TODO: Is this correct?
    var remainingDataLength = ((readBuffer[5] & 0xFF) << 24)
                                + ((readBuffer[6] & 0xFF) << 16)
                                + ((readBuffer[7] & 0xFF) << 8)
                                + (readBuffer[8] & 0xFF);

    var length = Math.Min(readBuffer.Length - (FirstChunkStartIndex), remainingDataLength);

    //This is the first chunk so read from 9-64
    var allData = GetRange(readBuffer, FirstChunkStartIndex, length);

    remainingDataLength -= allData.Length;

    _InvalidChunksCounter = 0;

    while (remainingDataLength > 0)
    {
        //Read a chunk
        readBuffer = await Device.ReadAsync();

        //check that there was some data returned
        if (readBuffer.Length <= 0)
        {
            continue;
        }

        //Check what's smaller, the buffer or the remaining data length
        length = Math.Min(readBuffer.Length, remainingDataLength);

        if (readBuffer[0] != (byte)'?')
        {
            if (_InvalidChunksCounter++ > 5)
            {
                throw new Exception("messageRead: too many invalid chunks (2)");
            }
        }

        allData = Append(allData, GetRange(readBuffer, 1, length - 1));

        //Decrement the length of the data to be read
        remainingDataLength -= (length - 1);

        //Super hack! Fix this!
        if (remainingDataLength != 1)
        {
            continue;
        }

        allData = Append(allData, GetRange(readBuffer, length, 1));
        remainingDataLength = 0;
    }

    var msg = Deserialize(messageType, allData);

    Logger.Log($"Read: {msg}", null, LogSection);

    return msg;
}

Code Reference