pak.packets.subpacket

Packets which are contained in other Packets.

class SubPacket(*, ctx=None, **fields)[source]

Bases: Packet

A Packet contained within another Packet.

A frequent occurrence in many protocols are Packets which have certain structures within them which associate data together. This could be just a conceptual distincition, or it could be a more practical distinction. These structures can be thought of as “sub-packets”.

For instance, if we had a packet which had a variable amount of pairs of names of players and their levels, represented with a leading UInt16 which reports how many pairs there are, and then a TerminatedString for the name and a UInt8 for the level, we could define such a SubPacket:

import pak

class PlayerNameAndLevel(pak.SubPacket):
    name:  pak.TerminatedString
    level: pak.UInt8

We could then use it in a normal Packet like a Type:

class PlayerInfoPacket(pak.Packet):
    players: PlayerNameAndLevel[pak.UInt16]

And we can then use it all like so:

packet = PlayerInfoPacket(players=[
    PlayerNameAndLevel(name="Bob",   level=1),
    PlayerNameAndLevel(name="Alice", level=2),
])

assert packet.pack() == (
    b"\x02\x00" + b"Bob\x00\x01" + b"Alice\x00\x02"
)

Note

SubPackets (or rather, its subclasses) are typelike, enabling their use like any other Type.

Additionally, the terse array syntax of ElemType[size] works with SubPackets as well.

And because SubPackets are still Packets, they can leverage a bit of that functionality, notably with their header.

Note

SubPackets cannot define their own Packet.Context, as they should receive and use the context of their super Packet.

SubPacket headers may only have a size field and/or an id field. When a SubPacket header contains a size field, then that many bytes will be read from the buffer supplied to the super Packet and used to unpack the SubPacket:

import pak

class SizedSubPacket(pak.SubPacket):
    class Header(pak.SubPacket.Header):
        size: pak.UInt8

    data: pak.RawByte[None]

class MyPacket(pak.Packet):
    sized: SizedSubPacket
    rest:  pak.RawByte[None]

assert MyPacket.unpack(
    # The 'rest' data should not be consumed by the 'sized' field.
    b"\x0Asized data" + b"rest"
) == MyPacket(
    sized = SizedSubPacket(data=b"sized data"),
    rest  = b"rest",
)

When a SubPacket header contains an id field, then a subclass of the SubPacket is searched for using Packet.subclass_with_id(), and then used to marshal the data:

import pak

class IDSubPacket(pak.SubPacket):
    class Header(pak.SubPacket.Header):
        id: pak.UInt8

class NumberData(IDSubPacket):
    id = 1

    number: pak.UInt16

class ArrayData(IDSubPacket):
    id = 2

    array: pak.UInt8[2]

class DataPacket(pak.Packet):
    data: IDSubPacket

assert DataPacket.unpack(
    b"\x01" + b"\x02\x00"
) == DataPacket(
    data = NumberData(number=2),
)

assert DataPacket.unpack(
    b"\x02" + b"\x00\x01"
) == DataPacket(
    data = ArrayData(array=[0, 1]),
)

When an unknown ID is encountered, then _subclass_for_unknown_id() is called, allowing customization for different needs.

exception NoAvailableSubclassError(subpacket_cls, *, id)[source]

Bases: ValueError, UnsuppressedError

An error indicating that there was no corresponding subclass of a SubPacket for a particular ID.

By default, SubPacket._subclass_for_unknown_id() will throw a SubPacket.NoAvailableSubclassError.

Parameters:
  • subpacket_cls (subclass of SubPacket) – The SubPacket for which there was no corresponding subclass.

  • id – The ID for which there was no corresponding subclass.

classmethod _subclass_for_unknown_id(id, *, ctx)[source]

Gets the subclass for the SubPacket when an unknown ID is encountered.

This method is overridable in order to customize the relevant behavior. For instance, a subclass from Packet.GenericWithID() or Packet.EmptyWithID() could be returned.

By default, a SubPacket.NoAvailableSubclassError is raised upon encountering an unknown ID.

Parameters:
Returns:

The subclass of the SubPacket.

Return type:

Subclass of SubPacket

class AlignedSubPacket(*, ctx=None, **fields)[source]

Bases: SubPacket, AlignedPacket

A SubPacket which aligns its fields.

When converted to a Type, the alignment of the AlignedSubPacket will be propagated appropriately, namely to its super Packet.

Note

If an AlignedSubPacket defines its own header, then a TypeError is raised.