Skip to content

Commit 10001c1

Browse files
committed
Pad array writes to the correct number of entries
1 parent 506d1df commit 10001c1

7 files changed

Lines changed: 104 additions & 6 deletions

File tree

dissect/cstruct/types/base.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -273,8 +273,10 @@ def _write(cls, stream: BinaryIO, data: list[Any]) -> int:
273273
if cls.null_terminated:
274274
return cls.type._write_0(stream, data)
275275

276-
if not cls.dynamic and cls.num_entries != (actual_size := len(data)):
277-
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
276+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
277+
if remaining < 0:
278+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
279+
data += [cls.type.__default__()] * remaining
278280

279281
return cls.type._write_array(stream, data)
280282

dissect/cstruct/types/char.py

Lines changed: 9 additions & 2 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:
@@ -20,15 +21,21 @@ def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
2021
return type.__call__(cls, super()._read(stream, context))
2122

2223
@classmethod
23-
def _write(cls, stream: BinaryIO, data: bytes) -> int:
24+
def _write(cls, stream: BinaryIO, data: bytes | str | list[int]) -> int:
2425
if isinstance(data, list) and data and isinstance(data[0], int):
2526
data = bytes(data)
2627

2728
elif isinstance(data, str):
2829
data = data.encode("latin-1")
2930

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

3441

dissect/cstruct/types/structure.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -259,7 +259,7 @@ def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Self:
259259
offset = stream.tell()
260260

261261
if field.offset is not None and offset != struct_start + field.offset:
262-
# Field is at a specific offset, either alligned or added that way
262+
# Field is at a specific offset, either aligned or added that way
263263
offset = struct_start + field.offset
264264
stream.seek(offset)
265265

@@ -325,7 +325,7 @@ def _write(cls, stream: BinaryIO, data: Structure) -> int:
325325
offset = stream.tell()
326326

327327
if field.offset is not None and offset < struct_start + field.offset:
328-
# Field is at a specific offset, either alligned or added that way
328+
# Field is at a specific offset, either aligned or added that way
329329
stream.write(b"\x00" * (struct_start + field.offset - offset))
330330
offset = struct_start + field.offset
331331

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 Any, BinaryIO, ClassVar
55

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

89

@@ -23,6 +24,12 @@ def _read(cls, stream: BinaryIO, context: dict[str, Any] | None = None) -> Wchar
2324
def _write(cls, stream: BinaryIO, data: str) -> int:
2425
if cls.null_terminated:
2526
data += "\x00"
27+
28+
if not cls.dynamic and (remaining := cls.num_entries - (actual_size := len(data))):
29+
if remaining < 0:
30+
raise ArraySizeError(f"Expected static array size {cls.num_entries}, got {actual_size} instead.")
31+
data += "\x00" * remaining
32+
2633
return stream.write(data.encode(Wchar.__encoding_map__[cls.cs.endian]))
2734

2835

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])
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)])
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")
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")
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")
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")
68+
69+
4170
def test_wchar_be_read(cs: cstruct) -> None:
4271
cs.endian = ">"
4372

0 commit comments

Comments
 (0)