Behavior of substructure endianness #65
|
Hello, Is it possible to have some more information on the inheritance of endianness across sub structures ? from caterpillar.py import Dynamic, struct, uint32, LittleEndian, BigEndian, unpack, f, getstruct
@struct
class MyStruct:
my_uint: uint32
@struct(order=BigEndian)
class Response:
little: LittleEndian + getstruct(MyStruct) # or f[MyStruct, MyStruct, LittleEndian]
big: BigEndian + getstruct(MyStruct) # or f[MyStruct, MyStruct, BigEndian]
inherit: MyStruct
my_uint = 0x01020304
raw = my_uint.to_bytes(4, "little") + my_uint.to_bytes(4, "big") + my_uint.to_bytes(4, "big")
print("Raw: 0x" + raw.hex()) # Raw: 0x040302010102030401020304 (my_uint8 in little, big & big)
result = unpack(Response, raw)
print(f"Little-endian: 0x{result.little.my_uint:02X}")
print(f"Big-endian: 0x{result.big.my_uint:02X}")
print(f"Inherit (Little): 0x{result.inherit.my_uint:02X}")
# Little-endian: 0x1020304 <== Expected output: my_uint
# Big-endian: 0x4030201 <== Decoded as little != my_uint ==> How to decode as Big without changing MyStruct ?
# Inherit (Little): 0x4030201 <== Should decode with same endianness as Response (BigEndian) ==> Here != my_uintAs you can see, MyStruct never inherit the endianness from the parent context. I also tried using How can I make a structure inherit the endianness ? My use case: I have some structures that are sometimes transmitted in little endian and sometimes in big. They contain the same data and change only by endianness. My current workaround is to duplicated the structure as Best regards, |
Replies: 2 comments 7 replies
|
Hi @DenisD3D , thanks for reaching out! Endianess is kind of tricky with this library:
However, there is a quick solution here: the from caterpillar.py import (
CTX_ORDER,
Action,
BigEndian,
Dynamic,
Invisible,
LittleEndian,
StructDefMixin,
f,
struct,
uint32,
)
from caterpillar.types import uint32_t
# You can also create a custom class taking the ByteOrder as an argument
# (refer to Action docstring for more explanation)
def apply_be(context):
context._root[CTX_ORDER] = BigEndian
@struct(order=Dynamic)
class MyStruct(StructDefMixin):
my_uint: uint32_t
# the next field will ALWAYS decode as little endian
my_uint_le: f[int, LittleEndian + uint32]
@struct(order=BigEndian, kw_only=True)
class Response(StructDefMixin):
# fmt: off
little: MyStruct
# in order to change the endianess for integer fields, we have to set
# it globally. Fields (not struct types) that are combined with
# -------------------------------------------------------------------------# every field below that is annotated with Dynamic
_order_big: f[None, Action(both=apply_be)] = Invisible() # will be using the BigEndian byteorder
big: MyStruct #
inherit: MyStruct #
# -------------------------------------------------------------------------#
my_uint = 0x01020304
raw = (
my_uint.to_bytes(4, "little")
+ my_uint.to_bytes(4, "little")
# ---
+ my_uint.to_bytes(4, "big")
+ my_uint.to_bytes(4, "little")
# ---
+ my_uint.to_bytes(4, "big")
+ my_uint.to_bytes(4, "big") # my_uint_le should be 0x4030201 then
)
# Raw: 0x040302010102030401020304 (my_uint8 in little, big & big)
print("Raw: 0x" + raw.hex())
result = Response.from_bytes(raw)
# fmt: off
print(f"Little-endian: 0x{result.little.my_uint:02X}, le_int: 0x{result.little.my_uint_le:02X}")
print(f"Big-endian: 0x{result.big.my_uint:02X}, le_int: 0x{result.big.my_uint_le:02X}")
print(f"Inherit (Little): 0x{result.inherit.my_uint:02X}, le_int: 0x{result.inherit.my_uint_le:02X}")
# BEFORE
# Little-endian: 0x1020304 <== Expected output: my_uint
# Big-endian: 0x4030201 <== Decoded as little != my_uint ==> How to decode as Big without changing MyStruct ?
# Inherit (Little): 0x4030201 <== Should decode with same endianness as Response (BigEndian) ==> Here != my_uint
# NOW
# Little-endian: 0x1020304, le_int: 0x1020304
# Big-endian: 0x1020304, le_int: 0x1020304
# Inherit (Little): 0x1020304, le_int: 0x4030201
assert result.to_bytes() == rawI hope that helps a bit. |
|
Hello @MatrixEditor, While the result is working as expected, the syntax isn't really straight forward. It could be awesome to have structure inherit parent endianness in a future version of the library. Either with a direct inheritance like fields or with a custom endianness ( @struct(order=None) # or VariableEndianness
class MyStruct:
my_uint: uint32
@struct(order=BigEndian)
class Response_V1():
a: MyStruct # my_uint decoded in big
@struct(order=LittleEndian)
class Response_V2():
c: MyStruct # my_uint decoded in little
@struct
class Response_V3():
c: MyStruct # my_uint decoded as unpack(.... order=XXX)
@struct(order=BigEndian)
class Response_V4():
c: LittleEndian + MyStruct # my_uint decoded as little
@struct(order=BigEndian)
class Response_V4():
c: LittleEndian + MyStruct[10] # my_uint decoded as littleFor everything else, I really enjoy using your library. Best, |
You're right, action syntax should only be used in edge cases when nothing else is applicable.
Adding a new byteorder without breaking previous versions is a bit tricky: I pushed a dev branch with a poc for an
Inheritbyteorder that uses the defined endian from the parent context.For instance,
The
<endian> + <type>works too with this…