Structs

Struct

A struct is a composite of fields. To create a struct, Inherit from Struct to combine multiple fields:

from hydration import *

class MyStruct(Struct):
    a = UInt8
    b = UInt8(value=3)  # You can set the default value
>>> st = MyStruct(a=10)  # Structs can receive field values as keyword arguments
>>> print(st) 
MyStruct
    a:  UInt8(10)
    b:  UInt8(3)
>>> bytes(st)
b'\n\x03'
>>> print(MyStruct.from_bytes(b'\n\x03'))
MyStruct:
    a:  UInt8(10)
    b:  UInt8(3)

Structs are also mutable, so you can set field values without explicitly accessing their value property:

>>> st = MyStruct(a=10)
>>> st.b = 5
>>> print(st)
MyStruct
    a:  UInt8(10)
    b:  UInt8(5)

Endianness

When defining a Struct, you may set the default Endianness for all of its scalars:

from hydration import *

class Clean(Struct, endianness=BigEndian):
    a = UInt64
    b = UInt32
    c = UInt16(endianness=LittleEndian)
    d = UInt8

The endianness of the fields will always override the one of their struct. In this case, c will be in LittleEndian.

Note that endianness doesn't affect d, because it's a UInt8. It can't "have" endianness

Vectors

A vector is a dynamic length sequence. The length of the vector needs to be the value of another field in the struct.

from hydration import *

class Dynamic(Struct):
    vec_len = UInt16()
    vector = Vector(length=vec_len)
>>> st = Dynamic()
>>> st.vector = [1, 2, 3]
>>> print(st)
Dynamic:
    vec_len:    UInt16(3)
    vector: Vector[1, 2, 3]

Notice that vec_len has been updated automatically.

By default, the vector is a sequence of UInt8, but this can be changed:

from hydration import *

class Dynamic(Struct):
    vec_len = UInt16()
    vector = Vector(length=vec_len, field_type=UInt32)
>>> st = Dynamic()
>>> st.vector = [1, 2, 3]
>>> bytes(st)
b'\x03\x00\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00'

Inheritance

Sometimes, some structs have common fields, in which case - they can be separated to reduce code duplication:

from hydration import *

class Common(Struct):
    common_a = UInt64

class Specific(Common):
    specific = UInt32
>>> print(Specific())
Specific:
    common_a:   UInt64(0)
    specific:   UInt32(0)

Inheriting classes will prepend their fields before the fields of the subclass, to append them instead, use footer=True:

from hydration import *

class CommonFooter(Struct, footer=True):
    common_a = UInt64

class Specific(CommonFooter):
    specific = UInt32
>>> print(Specific())
Specific:
    specific:   UInt32(0)
    common_a:   UInt64(0)

Multiple inheritance is also possible, albeit not recommended:

from hydration import *

class Header1(Struct):
    a = UInt8

class Header2(Struct):
    b = UInt8

class Specific(Header1, Header2):
    c = UInt16
>>> print(Specific())
Specific:
    a:  UInt8(0)
    b:  UInt8(0)
    c:  UInt16(0)

Subclassing a Struct will only inherit the fields. It doesn't inherit endianness or footer.