8
votes

I am trying to implement a simple serial port protocol. It goes like this:

  1. discard all data until 0xff is received
  2. read header (node address and data length, 4 bytes)
  3. read data (max. 64 bytes)
  4. read crc
  5. process the received packet
  6. send response
  7. when 0xff is seen, even when not expected like in the middle of the data, it means a new packet is received

I can implement this using boost::asio::serial_port with boost::asio::read() reading a single byte and processing this byte when it is received. Although this works, I was wondering if there is a more 'boost' like way to do this?

I looked at boost::asio::read_until() for reading until 0xff, but then I don't know how to discard the data. Storing the data in a buffer and then not using the buffer seems a bit wasteful.

I can use boost::asio::read_until() for reading until the end of the packet but then the MatchCondition needs to have access to (the header of the packet in) the buffer. It seems the MatchCondition only gets an iterator to the first and last byte recently received.

Also, the data received using boost::asio::read() ends up in a stream_buf and I have to parse the received data into a Packet object. I can do this parsing inside Packet, in a separate ParsePacket object or somehow integrate it with boost::asio (something like boost::asio::read(serial, myPacket); where myPacket is a Packet object)

When 0xff is seen anywhere in the received data, it means a new packet is starting. So when 0xff is received, it must forget any previous received data and start receiving a new packet.

I am planning on using asynchronous operations and adding timeouts.

So, my question is: where to implement a protocol like this? Or more generally, where to implement a protocol using boost::asio. I am not looking for working code but something like advise on where to implement the protocol and which boost::asio functionality to use.

update:

There is no flow control (hardware or software) used in this case.

2
A comment on your protocol: what if the crc calculates to be 0xff?James
@Autopulated: crc can never be 0xff, also the data and the headers never contain 0xff. 0xff is reserved for the beginning of a new packet.rve

2 Answers

2
votes

First of all, as @Autopulated pointed in comments, I want to warn you about using delimiters (your 0xFF) in binary protocols. It's dangerous technique (brings a lot of ambiguity) and requires complicated implementations. Even if you can guarantee that your data doesn't contain 0xFF bytes you cannot do this about CRC field.

I would recommend to not bother with any delimiters and concentrate on simple and predictable binary protocol: [packet][packet]... where [packet] = [node address:X][data length:4][data:data length][CRC:1]

Sending such packets can look like:

size_t const data_length_bytes = 4;

std::vector<char> data = ...;
size_t data_length = data.size();
Node_address node_address = ...;

std::vector<boost::asio::const_buffer> bufs;
bufs.push_back(boost::asio::buffer(&node_address, sizeof(node_address)));
bufs.push_back(boost::asio::buffer(&data_length, data_length_bytes));
bufs.push_back(boost::asio::buffer(data));

boost::system::error_code error;
boost::asio::write(socket, boost::asio::buffer(bufs), error);
if (error)
    throw boost::system::system_error(error);

Receiving:

size_t data_length;
std::vector<char> data;
Node_address node_address;
char crc;

std::vector<boost::asio::mutable_buffer> bufs;
boost::system::error_code error;

bufs.push_back(boost::asio::buffer(&node_address, sizeof(node_address)));
bufs.push_back(boost::asio::buffer(&data_length, data_length_bytes));
boost::asio::read(serial_port, bufs, error);
if (error)
    throw boost::system::system_error(error);

data.resize(data_length);
bufs.clear();
bufs.push_back(boost::asio::buffer(&data.front(), data_length));
bufs.push_back(boost::asio::buffer(&crc, sizeof(crc));
boost::asio::read(serial_port, bufs, error);
if (error)
    throw boost::system::system_error(error);

// check CRC
// send response

Please note that this example makes assumption that both peers has the same endianess.

I wrote this code here so don't expect it's correct, use it just as an idea.

0
votes

When implementing protocols, I have previously found state-machines very useful.

Whilst I haven't used asio, this technique may be applicable here.

These can be implemented in C++ as an enum, a loop and a switch as follows:

enum States { StateInitial, StateHeader, StateData ... };

States state = StateInitial;
while (1)
{
    char ch = get_byte_function();
    switch (state)
    {
        case StateInitial:
            if (ch == '\xFF')
                state = StateHeader;
            break;

        case StateHeader:
            ...
    }
}

You would need to add more flags to keep track of your progress within the protocol.

You may also want to look at boost::statechart for implementing the state machine.