简体   繁体   中英

Issue using struct.pack and struct.unpack

Here is my code snippet:

import struct


class _packet:

    def __init__(self, payload):  
        self.version = 1
        self.syn = False
        self.fin = False
        self.reset = False
        self.hasOpt = False
        self.ack = 0
        self.payload = payload
        return

    def pack(self):
        return struct.pack('????i' + str(len(self.payload)) + 's', self.syn, self.fin, self.reset, self.hasOpt,self.ack, bytes(self.payload, 'utf-8'))

    def unpack(self):
        unpackedData = bytearray()
        return struct.unpack('????i5s', unpackedData)

def main():
    packet = _packet("Hello")
    packet.ack = 249
    packet.syn = True
    packet.fin = True
    packet.reset = True
    packedData = packet.pack()
    print(packedData)
    unpackedData = packet.unpack()
    print(unpackedData)

if __name__== "__main__":
    main()

My goal is to create a packet, use struct.pack to encode it and send it over a socket, and then use unpack to put the data back into a tuple so that I can extract the necessary bits from it. My packet doesn't have some of the needed bits because it's a minimal example of using packets. Once I execute the line

packedData = packet.pack()
print(packedData)

I receive this as my output:

b'\x01\x01\x01\x00\xf9\x00\x00\x00Hello'

This seems to be what I expect, but the issue arises when i run the following lines:

 unpackedData = packet.unpack()
 print(unpackedData)

I get the following error:

unpack requires a bytes object of length 13

If I change unpacked data to be a bytearray of length 13, I get the following output as my unpacked data:

(False, False, False, False, 0, b'\x00\x00\x00\x00\x00')

This is obviously wrong since it didn't keep my values and just seems to be a different packet instance.

Am I creating my packet objects incorrectly? Or am I packing and unpacking my data incorrectly?

If you want struct.unpack to return the data that you passed to struct.pack , then the argument you pass to struct.unpack must be the object that is returned from struct.pack . Right now, you're giving it a blank bytearray, so you're getting back blank data.

One possible solution is to pass the packed data as an argument to _packet.unpack , which you then pass to struct.unpack .

import struct


class _packet:

    def __init__(self, payload):  
        self.version = 1
        self.syn = False
        self.fin = False
        self.reset = False
        self.hasOpt = False
        self.ack = 0
        self.payload = payload
        return

    def pack(self):
        return struct.pack('????i' + str(len(self.payload)) + 's', self.syn, self.fin, self.reset, self.hasOpt,self.ack, bytes(self.payload, 'utf-8'))

    def unpack(self, data):
        header_size = 8 #four one-byte bools and one four-byte int
        return struct.unpack('????i' + str(len(packed_data)-header_size) + 's', data)

def main():
    packet = _packet("Hello")
    packet.ack = 249
    packet.syn = True
    packet.fin = True
    packet.reset = True
    packedData = packet.pack()
    print(packedData)
    unpackedData = packet.unpack(packedData)
    print(unpackedData)

if __name__== "__main__":
  main()

Or perhaps you would prefer to assign the packed data as an attribute of the _packet instance, so the caller doesn't need to supply any arguments.

import struct


class _packet:

    def __init__(self, payload):  
        self.version = 1
        self.syn = False
        self.fin = False
        self.reset = False
        self.hasOpt = False
        self.ack = 0
        self.payload = payload

        self.packed_data = None

    def pack(self):
        self.packed_data = struct.pack('????i' + str(len(self.payload)) + 's', self.syn, self.fin, self.reset, self.hasOpt,self.ack, bytes(self.payload, 'utf-8'))
        return self.packed_data

    def unpack(self):
        header_size = 8 #four one-byte bools and one four-byte int
        return struct.unpack('????i' + str(len(packed_data)-header_size) + 's', self.packed_data)

def main():
    packet = _packet("Hello")
    packet.ack = 249
    packet.syn = True
    packet.fin = True
    packet.reset = True
    packedData = packet.pack()
    print(packedData)
    unpackedData = packet.unpack()
    print(unpackedData)

if __name__== "__main__":
  main()

Personally, I would make unpack a classmethod, since you shouldn't need to create a _packet instance in order to deserialize some bytes into a new _packet object. I would also make the attributes of the object optionally settable during initialization so you don't need to assign to them individually within main .

import struct


class _packet:

    def __init__(self, payload, **kwargs):  
        self.version = 1
        self.syn = kwargs.get("syn", False)
        self.fin = kwargs.get("fin", False)
        self.reset = kwargs.get("reset", False)
        self.hasOpt = kwargs.get("hasOpt", False)
        self.ack = kwargs.get("ack", 0)
        self.payload = payload

    def pack(self):
        return struct.pack('????i' + str(len(self.payload)) + 's', self.syn, self.fin, self.reset, self.hasOpt,self.ack, bytes(self.payload, 'utf-8'))

    #optional: nice string representation of packet for printing purposes
    def __repr__(self):
        return "_packet(payload={}, syn={}, fin={}, reset={}, hasOpt={}, ack={})".format(self.payload, self.syn, self.fin, self.reset, self.hasOpt, self.ack)

    @classmethod
    def unpack(cls, packed_data):
        header_size = 8 #four one-byte bools and one four-byte int
        syn, fin, reset, hasOpt, ack, payload = struct.unpack('????i' + str(len(packed_data)-header_size) + 's', packed_data)
        return cls(payload, syn=syn, fin=fin, reset=reset, hasOpt=hasOpt, ack=ack)
def main():
    packet = _packet("Hello", ack=249, syn=True, fin=True, reset=True)
    packedData = packet.pack()
    print(packedData)
    unpackedData = _packet.unpack(packedData)
    print(unpackedData)

if __name__== "__main__":
  main()

A few notes:

  1. You can pre-create a Struct object for packing and unpacking the header
  2. The payload is easier to pack and unpack by itself, separately from the header. It can be appended to a packed header and extracted from a packet using slicing.
  3. unpack should be a class method that takes a bytes object and returns an instance of Packet .
  4. Making this a dataclass avoids having to write a lot of boilerplate for the __init__ method.

from dataclasses import dataclass
from typing import ClassVar
import struct

@dataclass
class Packet:

    header : ClassVar[struct.Struct] = struct.Struct('????i')
    payload: str
    syn: bool = False
    fin: bool = False
    reset: bool = False
    has_opt: bool = False
    ack: int = 0

    def pack(self):
        return self.header.pack(
                   self.syn, 
                   self.fin,
                   self.reset,
                   self.has_opt,
                   self.ack
               ) + self.payload.encode('utf-8')

    @classmethod
    def unpack(cls, data: bytes):
        payload = data[cls.header.size]
        syn, fin, reset, has_opt, ack = cls.header.unpack_from(data)
        return Packet(
                   payload.decode('utf'),
                   syn,
                   fin,
                   reset,
                   has_opt,
                   ack)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM