Refugnic Eternium
Refugnic Eternium

Reputation: 4291

QTcpSocket and TCP padding

Good day.

I am sending a custom protocol for logging via TCP which looks like this:

Timestamp (uint32_t -> 4 bytes)
Length of message (uint8_t -> 1 byte)
Message (char -> Length of message)

The Timestamp is converted to BigEndian for the transport and everything goes out correctly, except for one little detail: Padding

The Timestamp is sent on its own, however instead of just sending the timestamp (4 bytes) my application (using BSD sockets under Ubuntu) automatically appends two bytes of padding to the message.

Wireshark recognizes this correctly and marks the two extraneous bytes as padding, however the QTcpSocket (Qt 5.8, mingw 5.3.0) apparently assumes that the two extra bytes are actually payload, which obviously messes up my protocol.

Is there any way for me to 'teach' QTcpSocket to ignore the padding (like it should) or any way to get rid of the padding?

I'd like to avoid to do the whole 'create a sufficiently large buffer and preassemble the entire packet in it so it will be sent out in one go'-method if possible.

Thank you very much.

Because it was asked, the code used to send the data is:

return 
    C->sendInt(entry.TS)        &&
    C->send(&entry.LogLen, 1)   &&
    C->send(&entry.LogMsg, entry.LogLen);

where sendInt is declared as (Src being the parameter):

Src = htonl(Src);
return send(&Src, 4);

where 'send' is declared as (Source and Len being the parameters):

char *Src = (char *)Source;
while(Len) {
    int BCount = ::send(Sock, Src, Len, 0);
    if(BCount < 1) return false;
    Src     += BCount;
    Len     -= BCount;
}
return true;

::send is the standard BSD send function.

Reading is done via QTcpSocket:

uint32_t timestamp;
if (Sock.read((char *)&timestamp, sizeof(timestamp)) > 0)
{
    uint8_t logLen;
    char message[256];            
    if (Sock.read((char *)&logLen, sizeof(logLen)) > 0  &&
        logLen > 0                                      &&
        Sock.read(message, logLen) == logLen
    ) addToLog(qFromBigEndian(timestamp), message);
}

Sock is the QTcpSocket instance, already connected to the host and addToLog is the processing function.

Also to be noted, the sending side needs to run on an embedded system, using QTcpServer is therefor not an option.

Upvotes: 2

Views: 497

Answers (3)

G.M.
G.M.

Reputation: 12899

Your read logic appears to be incorrect. You have...

uint32_t timestamp;
if (Sock.read((char *)&timestamp, sizeof(timestamp)) > 0)
{
    uint8_t logLen;
    char message[256];            
    if (Sock.read((char *)&logLen, sizeof(logLen)) > 0  &&
        logLen > 0                                      &&
        Sock.read(message, logLen) == logLen
    ) addToLog(qFromBigEndian(timestamp), message);
}

From the documentation for QTcpSocket::read(data, MaxSize) it...

Reads at most maxSize bytes from the device into data, and returns the number of bytes read

What if one of your calls to Sock.read reads partial data? You essentially discard that data rather than buffering it for reuse next time.

Assuming you have a suitably scoped QByteArray...

QByteArray data;

your reading logic should be more along the lines of...

/*
 * Append all available data to `data'.
 */
data.append(Sock.readAll());

/*
 * Now repeatedly read/trim messages from data until
 * we have no further complete messages.
 */
while (contains_complete_log_message(data)) {
  auto message = read_message_from_data(data);
  data = data.right(data.size() - message.size());
}

/*
 * At this point `data' may be non-empty but doesn't
 * contain enough data for a complete message.
 */

Upvotes: 3

Rolf Winter
Rolf Winter

Reputation: 565

You should not invoke send three times but only once. For conversion into BigEndian you might use the Qt functions and write everything into a single buffer and only call send once. It is not what you want, but I assume it is what you'll need to do and it should be easy, as you already know the size of you message. You also will not need to leave the Qt world for sending the messages.

Upvotes: 0

IlBeldus
IlBeldus

Reputation: 1040

If the length of the padding is always fixed then just add socket->read(2); to ignore the 2 bytes.

On the other hand it might be just the tip of the iceberg. What are you using to read and write?

Upvotes: 1

Related Questions