Skip to content

Commit d79403a

Browse files
committed
Pad array writes to the correct number of entries
1 parent 599b5e9 commit d79403a

7 files changed

Lines changed: 105 additions & 8 deletions

File tree

dissect/cstruct/types/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,8 +294,10 @@ def _write(cls, stream: BinaryIO, data: list[Any], *, endian: Endianness) -> int
294294
if cls.null_terminated:
295295
return cls.type._write_0(stream, data, endian=endian)
296296

297-
if not cls.dynamic and cls.num_entries != (actual_size := len(data)):
298-
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
297+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
298+
if remaining < 0:
299+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
300+
data = data + [cls.type.__default__()] * remaining
299301

300302
return cls.type._write_array(stream, data, endian=endian)
301303

dissect/cstruct/types/char.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import TYPE_CHECKING, Any, BinaryIO
44

5+
from dissect.cstruct.exception import ArraySizeError
56
from dissect.cstruct.types.base import EOF, BaseArray, BaseType
67

78
if TYPE_CHECKING:
@@ -22,15 +23,20 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia
2223
return type.__call__(cls, super()._read(stream, context=context, endian=endian))
2324

2425
@classmethod
25-
def _write(cls, stream: BinaryIO, data: bytes, *, endian: Endianness) -> int:
26-
if isinstance(data, list) and data and isinstance(data[0], int):
26+
def _write(cls, stream: BinaryIO, data: bytes | str | list[int], *, endian: Endianness) -> int:
27+
if isinstance(data, list):
2728
data = bytes(data)
28-
2929
elif isinstance(data, str):
3030
data = data.encode("latin-1")
3131

3232
if cls.null_terminated:
33-
return stream.write(data + b"\x00")
33+
data += b"\x00"
34+
35+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
36+
if remaining < 0:
37+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
38+
data += b"\x00" * remaining
39+
3440
return stream.write(data)
3541

3642

dissect/cstruct/types/structure.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia
252252
offset = stream.tell()
253253

254254
if field.offset is not None and offset != struct_start + field.offset:
255-
# Field is at a specific offset, either alligned or added that way
255+
# Field is at a specific offset, either aligned or added that way
256256
offset = struct_start + field.offset
257257
stream.seek(offset)
258258

@@ -318,7 +318,7 @@ def _write(cls, stream: BinaryIO, data: Structure, *, endian: Endianness) -> int
318318
offset = stream.tell()
319319

320320
if field.offset is not None and offset < struct_start + field.offset:
321-
# Field is at a specific offset, either alligned or added that way
321+
# Field is at a specific offset, either aligned or added that way
322322
stream.write(b"\x00" * (struct_start + field.offset - offset))
323323
offset = struct_start + field.offset
324324

dissect/cstruct/types/wchar.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import sys
44
from typing import TYPE_CHECKING, Any, BinaryIO, ClassVar
55

6+
from dissect.cstruct.exception import ArraySizeError
67
from dissect.cstruct.types.base import EOF, BaseArray, BaseType
78

89
if TYPE_CHECKING:
@@ -26,6 +27,12 @@ def _read(cls, stream: BinaryIO, *, context: dict[str, Any] | None = None, endia
2627
def _write(cls, stream: BinaryIO, data: str, *, endian: Endianness) -> int:
2728
if cls.null_terminated:
2829
data += "\x00"
30+
31+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
32+
if remaining < 0:
33+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
34+
data += "\x00" * remaining
35+
2936
return stream.write(data.encode(Wchar.__encoding_map__[endian]))
3037

3138

tests/test_types_base.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
import io
34
from typing import TYPE_CHECKING, BinaryIO
45

56
import pytest
@@ -20,6 +21,24 @@ def test_array_size_mismatch(cs: cstruct) -> None:
2021
assert cs.uint8[2]([1, 2]).dumps()
2122

2223

24+
def test_array_write_padding(cs: cstruct) -> None:
25+
buf = io.BytesIO()
26+
cs.uint8[4]._write(buf, [1, 2], endian=cs.endian)
27+
assert buf.getvalue() == b"\x01\x02\x00\x00"
28+
29+
cdef = """
30+
struct point {
31+
uint8 x;
32+
uint8 y;
33+
};
34+
"""
35+
cs.load(cdef)
36+
37+
buf = io.BytesIO()
38+
cs.point[4]._write(buf, [cs.point(x=1, y=2)], endian=cs.endian)
39+
assert buf.getvalue() == b"\x01\x02" + b"\x00\x00" * 3
40+
41+
2342
def test_eof(cs: cstruct, compiled: bool) -> None:
2443
cdef = """
2544
struct test_char {

tests/test_types_char.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import pytest
77

8+
from dissect.cstruct.exception import ArraySizeError
9+
810
if TYPE_CHECKING:
911
from dissect.cstruct.cstruct import cstruct
1012

@@ -36,6 +38,38 @@ def test_char_array_write(cs: cstruct) -> None:
3638
assert cs.char[None](buf).dumps() == b"AAAA\x00"
3739

3840

41+
def test_char_array_write_padding(cs: cstruct) -> None:
42+
buf = io.BytesIO()
43+
cs.char[8]._write(buf, "hi", endian=cs.endian)
44+
assert buf.getvalue() == b"hi\x00\x00\x00\x00\x00\x00"
45+
46+
cdef = """
47+
struct test_struct {
48+
int x;
49+
char y[8];
50+
char z[16];
51+
};
52+
"""
53+
cs.load(cdef)
54+
55+
obj = cs.test_struct()
56+
obj.x = 4
57+
obj.y = "hi"
58+
obj.z = "bye"
59+
60+
assert len(obj.dumps()) == len(cs.test_struct)
61+
assert (
62+
obj.dumps()
63+
== b"\x04\x00\x00\x00hi\x00\x00\x00\x00\x00\x00bye\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
64+
)
65+
66+
67+
def test_char_array_write_size_error(cs: cstruct) -> None:
68+
buf = io.BytesIO()
69+
with pytest.raises(ArraySizeError):
70+
cs.char[4]._write(buf, b"toolong", endian=cs.endian)
71+
72+
3973
def test_char_eof(cs: cstruct) -> None:
4074
with pytest.raises(EOFError):
4175
cs.char(b"")

tests/test_types_wchar.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
import pytest
77

8+
from dissect.cstruct.exception import ArraySizeError
9+
810
if TYPE_CHECKING:
911
from dissect.cstruct.cstruct import cstruct
1012

@@ -38,6 +40,33 @@ def test_wchar_array_write(cs: cstruct) -> None:
3840
assert cs.wchar[None](buf).dumps() == b"A\x00A\x00A\x00A\x00\x00\x00"
3941

4042

43+
def test_wchar_array_write_padding(cs: cstruct) -> None:
44+
buf = io.BytesIO()
45+
cs.wchar[8]._write(buf, "hi", endian=cs.endian)
46+
assert buf.getvalue() == b"h\x00i\x00" + b"\x00" * 12
47+
48+
cdef = """
49+
struct test_struct {
50+
int x;
51+
wchar y[8];
52+
wchar z[16];
53+
};
54+
"""
55+
cs.load(cdef)
56+
obj = cs.test_struct()
57+
obj.x = 4
58+
obj.y = "hi"
59+
obj.z = "bye"
60+
assert len(obj.dumps()) == len(cs.test_struct)
61+
assert obj.dumps() == b"\x04\x00\x00\x00h\x00i\x00" + (b"\x00" * 12) + b"b\x00y\x00e\x00" + (b"\x00" * 26)
62+
63+
64+
def test_wchar_array_write_size_error(cs: cstruct) -> None:
65+
buf = io.BytesIO()
66+
with pytest.raises(ArraySizeError):
67+
cs.wchar[4]._write(buf, "toolong", endian=cs.endian)
68+
69+
4170
def test_wchar_be_read(cs: cstruct) -> None:
4271
cs.endian = ">"
4372

0 commit comments

Comments
 (0)