1

I'm trying to serialize a Record in Delphi by using MessagePack and then using ZeroMQ TCP protocol I send it to a Python server.

b'\xa8DataType\x01\xa4data\xbf{"major":1,"minor":0,"build":2}\x00'

I'm having trouble deserializing it on the server side. Any ideas why this is happening? Is it some kind of encoding issues? Thanks!

Update #1:

I use the messagepack library "QMsgPack" recommended on www.msgpack.org Here's some Delphi code. My user defined Records and an enum:

Version = Record
    major : Integer;
    minor : Integer;
    build : Integer;
end;

TDataType = (dtUnset, dtVersion, dtEntityDescription, dtEntityDescriptionVector, dtEntityState, dtEntityStateVector, dtCommandContinue, dtCommandComplete);

TPacket = Record
    DataType : TDataType;
    data : string;
end;

And the code to serialize the object:

begin
    dmVersion.major := 1;
    dmVersion.minor := 1;
    dmVersion.build := 1;

    lvMsg := TQMsgPack.Create;
    lvMsg.FromRecord(dmVersion);

    lvMsgString := lvMsg.ToString();

    packet.DataType := dtVersion;
    packet.data := lvMsgString;
    lvMsg.Clear;
    lvMsg.FromRecord(packet);
    lvbytes:=lvMsg.Encode;
    ZeroMQ.zSendByteArray(skt, lvbytes);

I then try to deserialize the received byte array in the python server which looks like this:

b'\xa8DataType\x01\xa4data\xbf{"major":1,"minor":0,"build":2}\x00'

by using umsgpack.unpack() and then print out the result in the result like this:

packet_packed = command.recv()

# Unpack the packet
umsgpack.compatibility = True
packet = umsgpack.unpackb( packet_packed )
print (packet) 
for item in packet:
    print (item)

and this is what I get printed out on the screen:

b'DataType'
68
97
116
97
84
121
112
101

I hope this helps! Thanks!

Update #2

Here is some server code on the python side. The VDS_PACKET_VERSION is a constant int set to 1.

    # Make sure its a version packet
    if VDS_PACKET_VERSION == packet[0]:

        # Unpack the data portion of the packet
        version = umsgpack.unpackb( packet[1] )

        roster = []
        if ( VDS_VERSION_MAJOR == version[0] ) and ( VDS_VERSION_MINOR == version[1] ) and ( VDS_VERSION_BUILD == version[2] ):
            dostuff()

With the current serialized object

b'\x82\xa8DataType\x01\xa4data\xbf{"major":1,"minor":1,"build":1}'

I get

KeyError: 0 on packet[0]

Why is that?

Yako
  • 191
  • 1
  • 2
  • 12
  • Looks like that the (Python) server isn't reversing the MessagePack packing of the message, as the result seems to match https://github.com/msgpack/msgpack/blob/master/spec.md – Nicholas Ring Sep 14 '14 at 21:10
  • I see, after I try reverting the MessagePack to a string I only get b'DataType' back. Any ideas why the rest of the string isn't being reverted? Thanks! – Yako Sep 14 '14 at 21:31
  • What you describe doesn't add up. The Delphi string appears to be JSON. But you are using MessagePack rather than JSON. Are you deserializing JSON and then serialising with MessagePack? And on the Python side you've got some bytes with serialized MessagePack. You need to deserialize this. Do you have any code? Wouldn't it be easier to send the JSON as is? – David Heffernan Sep 14 '14 at 21:35
  • Sorry, you're right. Ignore the first line, it's what the string looks like in JSON. I serialize the string using QMessagePack(Delphi) on the Client side, then I send the string to the python server. The serialized string looks like this: \xa8DataType\x01\xa4data\xbf{"major":1,"minor":0,"build":2}\x00 Is the serialization being done wrong? – Yako Sep 14 '14 at 21:40
  • Thanks for your help! But when I try deserializing the string using umsgpack.unpackb I only get b'DataType', nothing else. :( – Yako Sep 14 '14 at 22:02
  • which Delphi MessagePack library is it? – mjn Sep 15 '14 at 05:55
  • I use QMessagePack to serialize the objects. I have added some information to the original question, thanks. – Yako Sep 15 '14 at 09:05

1 Answers1

1

The packed data appears to be invalid.

>>> packet = { "DataType": 1, "data": "{\"major\":1,\"minor\":0,\"build\":2}"}
>>> umsgpack.packb(packet)
b'\x82\xa4data\xbf{"major":1,"minor":0,"build":2}\xa8DataType\x01'

The first byte is \x82 which, as can be seen in the specification, is a two entry fixmap.

Your packed data is missing that information, and launches straight in to a fixstr. So, yes, there could be a mismatch between your Delphi based packer and the Python based unpacker. However, when I take your Delphi code, using the latest qmsgpack from the repo, it produces the following bytes:

82A8446174615479706501A464617461
BF7B226D616A6F72223A312C226D696E
6F72223A312C226275696C64223A317D

Let's convert that into a Python bytes object. It looks like this:

b'\x82\xa8DataType\x01\xa4data\xbf{"major":1,"minor":1,"build":1}'

Now, that's quite different from what you report. And umsgpack can unpack it just fine. Note that the first byte is \x82, a two entry fixmap, just as expected. Yes, the entries are in a different order, but that's just fine. Order is not significant for a map.

So, I've been able to encode using qmsgpack in Delphi, and decode using umsgpack in Python. Which then suggests that this issue is really in the transmission. It looks to me as though there has been an off-by-one error. Instead of transmitting bytes 0 to N-1, bytes 1 to N have been transmitted. Note the spurious trailing zero in your received data.


In the comments you obverse that the data field is being coded as JSON and passed as a string. But you'd rather have that data encoded using MessagePack. So here's what to do:

  1. In the Delphi code change the data field's type from string to TBytes. That's because we are going to put a byte array in there.
  2. Populate the data field using Encode, like this: packet.data := lvMsg.Encode.
  3. On the Python side, when you unpack data you'll find that it is an array of integers. Convert that to bytes and then unpack: umsgpack.unpackb(bytes(data)).
David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • Thank you so much for your help. A question, according to the instructions I've got I'm supposed to serialize "data" inside the packet before I serialize the whole packet once again. This is because the server first unpacks the whole packet, and then unpacks the data. How should I store the serialized data in the data string inside the Record TPacket? When I use ToString it removes all MessagePack serialization and just returns a json string? – Yako Sep 15 '14 at 13:44
  • No I don't control the server side, I only got an example script for the server to test against, I will not be able to change anything on the server side. – Yako Sep 15 '14 at 13:55
  • OK, I added how to do that in the answer. Should really have been a new question, but there you go! Ah, our comments crossed. You only control the Delphi side. I think that what I propose in my edit will be accurate...... – David Heffernan Sep 15 '14 at 13:59
  • Thanks, and ahh, yes I see. That's the thing, I can't change anything on the serverside, so the server will just do unpack twice. Check the #2 update my original submission, I've added some servercode. – Yako Sep 15 '14 at 14:10
  • I think what I wrote in my update is the obvious way to do what you ask. If you could show some example code of how to serialize (or deserialize) the data then that would help. – David Heffernan Sep 15 '14 at 14:23
  • OK, edits and comments crossed again. Can you should where `packet` comes from? – David Heffernan Sep 15 '14 at 14:24
  • Serializing on the client side and deserializing on the server side? I think I've added it all to my original submission. And also, yes that seems like a great way to do it, but the thing is I will not be able to change anything on the server side, so changing to `umsgpack.unpackb(bytes(data)).` might not be an option.. EDIT: Ah, crossed again yes. Packet is in my original submission as well, it looks like this: `packet = umsgpack.unpackb( packet_packed )` and packet_packed `packet_packed = command.recv()` is just the received bytearray. – Yako Sep 15 '14 at 14:27
  • I think you'll have to do a bit more digging. To be honest I think we've gone as far as we can with the original question. We are now veering off into new question territory. Note how the Python server code uses `packet[0]` and `packet[1]`. That implies an array rather than a map. I think you need to be more clear on exactly what structure `packet` is. But ah, I wonder what `umsgpack.compatibility = True` means ..... – David Heffernan Sep 15 '14 at 14:34
  • Yes, I think you are right. Thank you so much for your help! – Yako Sep 15 '14 at 14:36
  • So, this `packet_packed = umsgpack.packb([1, umsgpack.packb([2, 3, 4])])` would result in something that would be correctly unpacked by your Python server code. I think you know know how to replicate that in your Delphi code. I hope I've helped. You should know that I'd never heard of MessagePack until I read this question!! – David Heffernan Sep 15 '14 at 14:39