Skip to content

Commit ff24fc4

Browse files
committed
util: ctypes: handle vla_counted_by
Handle automatically constructing VLA structs with the `vla_counted_by` attribute. Signed-off-by: Jordan Yates <jordan@embeint.com>
1 parent 6e7af7a commit ff24fc4

2 files changed

Lines changed: 71 additions & 9 deletions

File tree

src/infuse_iot/util/ctypes.py

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -43,23 +43,46 @@ class property is not `None`, it will consume the remainder of
4343
"""
4444

4545
base = cls.from_buffer_copy(source, offset)
46+
vla_val: list | VLACompatLittleEndianStruct
4647
if cls.vla_field is None:
4748
return base
4849

4950
remainder = source[ctypes.sizeof(cls) :]
5051
vla_field_name, vla_field_type = cls.vla_field # type: ignore
5152

5253
if issubclass(vla_field_type, ctypes.Array):
53-
array_base: ctypes._CData = vla_field_type._type_ # type: ignore
54+
array_base: ctypes._PyCSimpleType = vla_field_type._type_ # type: ignore
55+
if hasattr(array_base, "vla_counted_by"):
56+
# This is an array of VLA arrays where the sub-arrys define their own length
57+
vla_val = []
58+
# Consume all remaining buffer bytes
59+
while len(remainder) > 0:
60+
sub_vla_field_name, sub_vla_field_type = array_base.vla_field # type: ignore
61+
sub_array_base: ctypes._CData = sub_vla_field_type._type_ # type: ignore
62+
sub_base = array_base.from_buffer_copy(remainder)
63+
sub_base_size = ctypes.sizeof(sub_base)
64+
sub_count = getattr(sub_base, array_base.vla_counted_by)
65+
if sub_count < 0:
66+
# Assume that negative length is an error code and use 0
67+
vla_val.append(sub_base)
68+
else:
69+
sub_vla_type = sub_count * sub_array_base
70+
# Don't use ctypes.sizeof on constructed type, it returns the wrong value
71+
sub_vla_size = sub_count * ctypes.sizeof(sub_array_base)
72+
sub_vla_val = sub_vla_type.from_buffer_copy(remainder[sub_base_size:])
73+
setattr(sub_base, sub_vla_field_name, sub_vla_val)
74+
vla_val.append(sub_base)
75+
remainder = remainder[sub_base_size + sub_vla_size :]
76+
else:
77+
# Determine the number of VLA elements on "source"
78+
vla_byte_len = (len(source) - offset) - ctypes.sizeof(cls)
79+
vla_element_size = ctypes.sizeof(array_base)
80+
if vla_byte_len % vla_element_size != 0:
81+
raise TypeError(f"Unaligned VLA buffer for {cls} (len {len(source)})")
82+
vla_num = vla_byte_len // vla_element_size
83+
vla_type = vla_num * array_base
84+
vla_val = vla_type.from_buffer_copy(remainder)
5485

55-
# Determine the number of VLA elements on "source"
56-
vla_byte_len = (len(source) - offset) - ctypes.sizeof(cls)
57-
vla_element_size = ctypes.sizeof(array_base)
58-
if vla_byte_len % vla_element_size != 0:
59-
raise TypeError(f"Unaligned VLA buffer for {cls} (len {len(source)})")
60-
vla_num = vla_byte_len // vla_element_size
61-
vla_type = vla_num * array_base
62-
vla_val = vla_type.from_buffer_copy(remainder)
6386
elif issubclass(vla_field_type, VLACompatLittleEndianStruct):
6487
vla_val = vla_field_type.vla_from_buffer_copy(remainder)
6588
else:

tests/util/test_ctypes.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,20 +11,40 @@ class VLABase(VLACompatLittleEndianStruct):
1111
("first", ctypes.c_uint32),
1212
]
1313
vla_field = ("vla", 0 * ctypes.c_uint32)
14+
_pack_ = 1
15+
16+
17+
class VLACountedBy(VLACompatLittleEndianStruct):
18+
_fields_ = [
19+
("count", ctypes.c_uint8),
20+
]
21+
vla_field = ("data", 0 * ctypes.c_uint8)
22+
vla_counted_by = "count"
23+
_pack_ = 1
1424

1525

1626
class VLANested(VLACompatLittleEndianStruct):
1727
_fields_ = [
1828
("first", ctypes.c_uint32),
1929
]
2030
vla_field = ("vla", VLABase)
31+
_pack_ = 1
32+
33+
34+
class VLADualNested(VLACompatLittleEndianStruct):
35+
_fields_ = [
36+
("num", ctypes.c_uint8),
37+
]
38+
vla_field = ("values", 0 * VLACountedBy)
39+
_pack_ = 1
2140

2241

2342
class VLANone(VLACompatLittleEndianStruct):
2443
_fields_ = [
2544
("first", ctypes.c_uint32),
2645
("second", ctypes.c_uint32),
2746
]
47+
_pack_ = 1
2848

2949

3050
def test_vla_compat_struct():
@@ -47,6 +67,25 @@ def test_vla_compat_struct():
4767
assert none.first == 2
4868
assert none.second == 3
4969

70+
one_element = b"\x01\x01\xff"
71+
two_element = b"\x02\x01\x33\x02\xaa\x55"
72+
dual_nested_one = VLADualNested.vla_from_buffer_copy(one_element)
73+
assert dual_nested_one.num == 1
74+
assert len(dual_nested_one.values) == 1
75+
assert dual_nested_one.values[0].count == 1
76+
assert len(dual_nested_one.values[0].data) == 1
77+
assert dual_nested_one.values[0].data[0] == 0xFF
78+
dual_nested_two = VLADualNested.vla_from_buffer_copy(two_element)
79+
assert dual_nested_two.num == 2
80+
assert len(dual_nested_two.values) == 2
81+
assert dual_nested_two.values[0].count == 1
82+
assert len(dual_nested_one.values[0].data) == 1
83+
assert dual_nested_two.values[0].data[0] == 0x33
84+
assert dual_nested_two.values[1].count == 2
85+
assert len(dual_nested_two.values[1].data) == 2
86+
assert dual_nested_two.values[1].data[0] == 0xAA
87+
assert dual_nested_two.values[1].data[1] == 0x55
88+
5089
unaligned = b"\x00" * 31
5190
try:
5291
VLABase.vla_from_buffer_copy(unaligned)

0 commit comments

Comments
 (0)