0
votes

I have a fairly complex issue regarding the interpretation of packets in an app that I am making. A host app sends a packet to client apps with the following structure:

[Header of 10 bytes][peerID of selected client of variable byte length][empty byte][peerID of a client of variable byte length][empty byte][int of 4 bytes][peerID of client of variable byte length][empty byte][int of 4 bytes]

Here is a sample packet that is produced under this structure:

434e4c50 00000000 006a3134 31303837 34393634 00313233 38313638 35383900 000003e8 31343130 38373439 36340000 0003e8

Converted it looks like this:

CNLP j1410874964 1238168589 Ë1410874964 Ë

"CNLP j" is the packet header of 10 bytes. "1410874964" is the peerID of the selected client. "1238168589" is the peerID of another client. " Ë" has an int value of 1000. "1410874964" is the peerID of the other client (in this case, the selected client). " Ë" also has an int value of 1000. Basically, in this packet I am communicating 2 things - who the selected client is and the int value associated with each client.

My problem exists on the interpretation side (client side). To interpret this particular type of packet, I use the following method:

    + (NSMutableDictionary *)infoFromData:(NSData *)data atOffset:(size_t) offset
{
    size_t count;

    NSMutableDictionary *info = [NSMutableDictionary dictionaryWithCapacity:8];

    while (offset < [data length])
    {
        NSString *peerID = [data cnl_stringAtOffset:offset bytesRead:&count];
        offset += count;

        NSNumber *number = [NSNumber numberWithInteger:[data cnl_int32AtOffset:offset]];
        offset += 4;

        [info setObject:number forKey:peerID];
    }

    return info;
}

Typically, each of these packets range between 49 and 51 bytes. "offset" is set in a previous method to reflect the byte number after the packet header plus the empty byte after the selected player (in the case of the above packet, 21). "count" is initialized with a value of 1. In the case of this particular example, length is 51. The following method is passed the above arguments:

    - (NSString *)cnl_stringAtOffset:(size_t)offset bytesRead:(size_t *)amount
{
    const char *charBytes = (const char *)[self bytes];
    NSString *string = [NSString stringWithUTF8String:charBytes + offset];
    *amount = strlen(charBytes + offset) + 1;
    return string;
}

This method is supposed to read through a variable length string in the packet, set the offset to the byte immediately after the empty byte pad behind the peerID string, and return the string that was read. "amount" is then set to the number of bytes the method read through for the string (this is becomes the new value of count after returning to the first method). "offset" and "count" are then added together to become the new "offset" - where interpretation of the int portion of the packet will begin. The above arguments are passed to the following method:

- (int)cnl_int32AtOffset:(size_t)offset
{
    const int *intBytes = (const int *)[self bytes];
    return ntohl(intBytes[offset / 4]);
}

This method is intended to return the 32 bit (4 byte) int value read at the current offset value of the packet. I believe that the problem exists in this method when the offset is a number that is not divisible by 4. In this case, the first int value of 1000 was correctly interpreted, and 32 was returned as the offset during the first iteration of the while loop. However, during the second iteration, the int value interpreted was 909377536 (obtained from reading bytes 36340000 in the packet instead of bytes 000003E8) This was likely due to the fact that the offset during this iteration was set to 47 (not divisible by 4). After interpreting the 32 bit int in the category above, 4 is added to the offset in the first method to account for a 4 byte (32 bit int). If my intuition about an offset not divisible by zero is correct, any suggestions to get around this problem are greatly appreciated. I have been looking for a way to solve this problem for quite some time and perhaps fresh eyes may help. Thanks for any help!!!

1

1 Answers

0
votes

The unportable version (undefined behaviour for many reasons):

return ntohl(*(const int *)([self bytes]+offset));

A semi-portable version is somewhat trickier, but in C99 it appears that you can assume int32_t is "the usual" two's complement representation (no trap representations, no padding bits), thus:

// The cast is necessary to prevent arithmetic on void* which is nonstandard.
const uint8_t * p = (const uint8_t *)[self bytes]+offset;
// The casts ensure the result type is big enough to hold the shifted value.
// We use uint32_t to prevent UB when shifting into the sign bit.
uint32_t n = ((uint32_t)p[0]<<24) | ((uint32_t)p[1]<<16) | ((uint32_t)p[2]<<8) | ((uint32_t)p[3]);
// Jump through some hoops to prevent UB on "negative" numbers.
// An equivalent to the third expression is -(int32_t)~n-1.
// A good compiler should be able to optimize this into nothing.
return (n <= INT32_MAX) ? (int32_t)n : -(int32_t)(UINT32_MAX-n)-1;

This won't work on architectures without 8-bit bytes, but such architectures probably have different conventions for how things are passed over the network.

A good compiler should be able to optimize this into a single (possibly byte-swapped) load on suitable architectures.