Search code examples
tcpc++builderindy

Parsing bytes as BCD with Indy C++ Builder


I am trying to parse the length of a message received. The length is in BCD. When I use ReadSmallInt(), I get a reading interpreted as a hex value, not as BCD.

So, if I have a message like this:

00 84 60 00 00 00 19 02 10 70 38 00 00 0E C0 00
00 16 45 93 56 00 01 79 16 62 00 00 00 00 00 00
08 00 00 00 00 02 10 43 02 04 02 35 31 35 31 35
31 35 31 35 31 35 31 53 41 4C 45 35 31 30 30 31
32 33 34 35 36 37 38 31 32 33 34 35 36 37 38 39
30 31 32 33

I am expecting ReadSmallInt() to return 84, but instead it is returning 132, which is correct if you are reading a hex value instead of a BCD one.

According to this answer, ReadSmallInt() reads BCD, as in the examples it gets 11 and 13 (BCD) as lengths instead of 17 and 19 (hex).

I have fixed this with duct tape, but is there a more elegant way?

int calculated_length;
// getting the length in Hexa
calculated_length = AContext->Connection->IOHandler->ReadSmallInt();
// converting from hex binary to hex string
UnicodeString bcdLength = UnicodeString().sprintf(L"%04x", calculated_length);
// converting from hex string to int
calculated_length = bcdLength.ToInt();

ucBuffer.Length = calculated_length -2;
AContext->Connection->IOHandler->ReadBytes(ucBuffer, calculated_length - 2);

Solution

  • According to this answer, ReadSmallInt reads BCD

    That is incorrect. You have misinterpreted what that answer is saying. NOTHING in that answer indicates that ReadSmallInt() reads in a Binary Coded Decimal, because it doesn't, as Indy DOES NOT support reading/writing BCDs at all. ReadSmallInt() simply reads in 2 bytes and returns them as-is as a 16-bit decimal integer (swapping the byte order, if needed). So, if you need to read in a BCD instead, you will have to read in the bytes and then parse them yourself. Or find a BCD library to handle it for you.

    If you re-read that other question again more carefully, in the 2 examples it gives:

    24 24 00 11 12 34 56 FF FF FF FF 50 00 8B 9B 0D 0A
    24 24 00 13 12 34 56 FF FF FF FF 90 02 00 0A 8F D4 0D 0A

    The 3rd and 4th bytes represent the message lengths (x00 x11 and x00 x13, respectively). As 16-bit values in network byte order, they represent decimal integers 17 and 19, respectively. And if you count the bytes present, you will see those values are the correct byte lengths of those messages. So, there are no BCDs involved here.

    That is different than your example. Bytes x00 x84 in network byte order represent decimal integer 132. But your message is 84 bytes in size, not 132 bytes. So clearly the bytes x00 x84 DO NOT represent a 16-bit decimal value, so ReadSmallInt() is the wrong method to use in the first place.

    In your "duct tape" code, you are taking the decimal value that ReadSmallInt() returns (132), converting it to a hex string ('0084'), and then parsing that to a decimal value (84). There is no method in Indy that will do that kind of conversion for you.

    That "works" in your case, but whether or not that is the correct conversion to perform, I could not say for sure as you have not provided any details about the protocol you are dealing with. But, if you think the bytes represent a BCD then you should interpret the bytes in terms of an actual BCD.

    In a packed BCD, a byte can represent a 2-digit number. In this case, byte x84 (10000100b) contains two nibbles 1000b (8) and 0100b (4), thus put together they form decimal 84, which is calculated as follows:

    BYTE b = 0x84;
    int len = (int((b >> 4) & 0x0F) * 10) + int(b & 0x0F);
    

    Now, how that extends to multiple bytes in a BCD, I'm not sure, as my experience with BCDs is very limited. But, you are going to have to figure that out if you need to handle message lengths greater than 99 bytes, which is the highest decimal that a single BCD byte can represent.