dissect.cstruct 4.0.dev3__tar.gz → 4.0.dev5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. {dissect_cstruct-4.0.dev3/dissect.cstruct.egg-info → dissect_cstruct-4.0.dev5}/PKG-INFO +53 -44
  2. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/README.md +52 -43
  3. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/compiler.py +8 -3
  4. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5/dissect.cstruct.egg-info}/PKG-INFO +53 -44
  5. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_bitfield.py +30 -0
  6. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_compiler.py +78 -1
  7. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/COPYRIGHT +0 -0
  8. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/LICENSE +0 -0
  9. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/MANIFEST.in +0 -0
  10. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/__init__.py +0 -0
  11. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/bitbuffer.py +0 -0
  12. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/cstruct.py +0 -0
  13. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/exceptions.py +0 -0
  14. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/expression.py +0 -0
  15. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/parser.py +0 -0
  16. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/__init__.py +0 -0
  17. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/base.py +0 -0
  18. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/char.py +0 -0
  19. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/enum.py +0 -0
  20. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/flag.py +0 -0
  21. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/int.py +0 -0
  22. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/leb128.py +0 -0
  23. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/packed.py +0 -0
  24. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/pointer.py +0 -0
  25. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/structure.py +0 -0
  26. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/void.py +0 -0
  27. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/types/wchar.py +0 -0
  28. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect/cstruct/utils.py +0 -0
  29. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect.cstruct.egg-info/SOURCES.txt +0 -0
  30. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect.cstruct.egg-info/dependency_links.txt +0 -0
  31. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/dissect.cstruct.egg-info/top_level.txt +0 -0
  32. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/examples/disk.py +0 -0
  33. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/examples/mirai.py +0 -0
  34. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/examples/pe.py +0 -0
  35. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/examples/protobuf.py +0 -0
  36. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/examples/secdesc.py +0 -0
  37. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/pyproject.toml +0 -0
  38. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/setup.cfg +0 -0
  39. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/__init__.py +0 -0
  40. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/conftest.py +0 -0
  41. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/data/testdef.txt +0 -0
  42. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/docs/Makefile +0 -0
  43. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/docs/conf.py +0 -0
  44. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/docs/index.rst +0 -0
  45. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_align.py +0 -0
  46. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_basic.py +0 -0
  47. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_bitbuffer.py +0 -0
  48. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_ctypes.py +0 -0
  49. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_expression.py +0 -0
  50. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_parser.py +0 -0
  51. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_base.py +0 -0
  52. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_char.py +0 -0
  53. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_custom.py +0 -0
  54. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_enum.py +0 -0
  55. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_flag.py +0 -0
  56. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_int.py +0 -0
  57. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_leb128.py +0 -0
  58. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_packed.py +0 -0
  59. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_pointer.py +0 -0
  60. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_structure.py +0 -0
  61. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_union.py +0 -0
  62. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_void.py +0 -0
  63. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_types_wchar.py +0 -0
  64. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/test_utils.py +0 -0
  65. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tests/utils.py +0 -0
  66. {dissect_cstruct-4.0.dev3 → dissect_cstruct-4.0.dev5}/tox.ini +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.cstruct
3
- Version: 4.0.dev3
3
+ Version: 4.0.dev5
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -84,12 +84,11 @@ refer to [the development guide](https://docs.dissect.tools/en/latest/contributi
84
84
  All you need to do is instantiate a new cstruct instance and load some structure definitions in there. After that you can start using them from your Python code.
85
85
 
86
86
  ```python
87
- from dissect import cstruct
87
+ from dissect.cstruct import cstruct
88
88
 
89
89
  # Default endianness is LE, but can be configured using a kwarg or setting the 'endian' attribute
90
- # e.g. cstruct.cstruct(endian='>') or cparser.endian = '>'
91
- cparser = cstruct.cstruct()
92
- cparser.load("""
90
+ # e.g. cstruct(endian='>') or c_parser.endian = '>'
91
+ parser_def = """
93
92
  #define SOME_CONSTANT 5
94
93
 
95
94
  enum Example : uint16 {
@@ -99,31 +98,35 @@ enum Example : uint16 {
99
98
  struct some_struct {
100
99
  uint8 field_1;
101
100
  char field_2[SOME_CONSTANT];
102
- char field_3[field_1 & 1 * 5]; // Some random expression to calculate array length
101
+ char field_3[(field_1 & 1) * 5]; // Some random expression to calculate array length
103
102
  Example field_4[2];
104
103
  };
105
- """)
104
+ """
105
+ c_parser = cstruct().load(parser_def)
106
106
 
107
- data = b'\x01helloworld\x00\x00\x06\x00'
108
- result = cparser.some_struct(data) # Also accepts file-like objects
107
+ data = b"\x01helloworld\x00\x00\x06\x00"
108
+ result = c_parser.some_struct(data) # Also accepts file-like objects
109
109
  assert result.field_1 == 0x01
110
- assert result.field_2 == b'hello'
111
- assert result.field_3 == b'world'
112
- assert result.field_4 == [cparser.Example.A, cparser.Example.C]
110
+ assert result.field_2 == b"hello"
111
+ assert result.field_3 == b"world"
112
+ assert result.field_4 == [c_parser.Example.A, c_parser.Example.C]
113
113
 
114
- assert cparser.Example.A == 0
115
- assert cparser.Example.C == 6
116
- assert cparser.Example(5) == cparser.Example.B
114
+ assert c_parser.Example.A == 0
115
+ assert c_parser.Example.C == 6
116
+ assert c_parser.Example(5) == c_parser.Example.B
117
117
 
118
118
  assert result.dumps() == data
119
119
 
120
120
  # You can also instantiate structures from Python by using kwargs
121
121
  # Note that array sizes are not enforced
122
- instance = cparser.some_struct(field_1=5, field_2='lorem', field_3='ipsum', field_4=[cparser.Example.B, cparser.Example.A])
123
- assert instance.dumps() == b'\x05loremipsum\x05\x00\x00\x00'
122
+ instance = c_parser.some_struct(
123
+ field_1=5, field_2="lorem", field_3="ipsum", field_4=[c_parser.Example.B, c_parser.Example.A]
124
+ )
125
+ assert instance.dumps() == b"\x05loremipsum\x05\x00\x00\x00"
126
+
124
127
  ```
125
128
 
126
- By default, all structures are compiled into classes that provide optimised performance. You can disable this by passing a `compiled=False` keyword argument to the `.load()` call. You can also inspect the resulting source code by accessing the source attribute of the structure: `print(cparser.some_struct.source)`.
129
+ By default, all structures are compiled into classes that provide optimised performance. You can disable this by passing a `compiled=False` keyword argument to the `.load()` call. You can also inspect the resulting source code by accessing the source attribute of the structure: `print(c_parser.some_struct.source)`.
127
130
 
128
131
  More examples can be found in the `examples` directory.
129
132
 
@@ -135,20 +138,23 @@ Write simple C-like structures and use them to parse binary data, as can be seen
135
138
  Aside from loading structure definitions, any of the supported types can be used individually for parsing data. For example, the following is all supported:
136
139
 
137
140
  ```python
138
- from dissect import cstruct
139
- cs = cstruct.cstruct()
141
+ from dissect.cstruct import cstruct
142
+
143
+ cs = cstruct()
140
144
  # Default endianness is LE, but can be configured using a kwarg or setting the attribute
141
- # e.g. cstruct.cstruct(endian='>') or cs.endian = '>'
142
- assert cs.uint32(b'\x05\x00\x00\x00') == 5
143
- assert cs.uint24[2](b'\x01\x00\x00\x02\x00\x00') == [1, 2] # You can also parse arrays using list indexing
144
- assert cs.char[None](b'hello world!\x00') == b'hello world!' # A list index of None means null terminated
145
+ # e.g. cstruct(endian='>') or cs.endian = '>'
146
+ assert cs.uint32(b"\x05\x00\x00\x00") == 5
147
+ assert cs.uint24[2](b"\x01\x00\x00\x02\x00\x00") == [1, 2] # You can also parse arrays using list indexing
148
+ assert cs.char[None](b"hello world!\x00") == b"hello world!" # A list index of None means null terminated
145
149
  ```
146
150
 
147
151
  ### Unions and nested structures
148
152
  Unions and nested structures are supported, both anonymous and named.
149
153
 
150
154
  ```python
151
- cdef = """
155
+ from dissect.cstruct import cstruct
156
+
157
+ c_def = """
152
158
  struct test_union {
153
159
  char magic[4];
154
160
  union {
@@ -173,58 +179,61 @@ struct test_anonymous {
173
179
  };
174
180
  };
175
181
  """
176
- c = cstruct.cstruct()
177
- c.load(cdef)
182
+ # Default endianness is LE, but can be configured using a kwarg or setting the attribute
183
+ # e.g. cstruct(endian='>') or cs.endian = '>'
184
+ c = cstruct().load(c_def)
178
185
 
179
186
  assert len(c.test_union) == 12
180
187
 
181
- a = c.test_union(b'ohaideadbeef')
182
- assert a.magic == b'ohai'
188
+ a = c.test_union(b"ohaideadbeef")
189
+ assert a.magic == b"ohai"
183
190
  assert a.c.a.a == 0x64616564
184
191
  assert a.c.a.b == 0x66656562
185
- assert a.c.b.b == b'deadbeef'
192
+ assert a.c.b.b == b"deadbeef"
186
193
 
187
- assert a.dumps() == b'ohaideadbeef'
194
+ assert a.dumps() == b"ohaideadbeef"
188
195
 
189
- b = c.test_anonymous(b'ohai\x39\x05\x00\x00\x28\x23\x00\x00deadbeef')
190
- assert b.magic == b'ohai'
196
+ b = c.test_anonymous(b"ohai\x39\x05\x00\x00\x28\x23\x00\x00deadbeef")
197
+ assert b.magic == b"ohai"
191
198
  assert b.a == 1337
192
199
  assert b.b == 9000
193
- assert b.c == b'deadbeef'
200
+ assert b.c == b"deadbeef"
201
+
194
202
  ```
195
203
 
196
204
  ### Parse bit fields
197
205
  Bit fields are supported as part of structures. They are properly aligned to their boundaries.
198
206
 
199
207
  ```python
200
- bitdef = """
208
+ from dissect.cstruct import cstruct
209
+
210
+ bit_def = """
201
211
  struct test {
202
212
  uint16 a:1;
203
- uint16 b:1; # Read 2 bits from an uint16
204
- uint32 c; # The next field is properly aligned
213
+ uint16 b:1; // Read 2 bits from an uint16
214
+ uint32 c; // The next field is properly aligned
205
215
  uint16 d:2;
206
216
  uint16 e:3;
207
217
  };
208
218
  """
209
- bitfields = cstruct.cstruct()
210
- bitfields.load(bitdef)
219
+ bitfields = cstruct().load(bit_def)
211
220
 
212
- d = b'\x03\x00\xff\x00\x00\x00\x1f\x00'
221
+ d = b"\x03\x00\xff\x00\x00\x00\x1f\x00"
213
222
  a = bitfields.test(d)
214
223
 
215
224
  assert a.a == 0b1
216
225
  assert a.b == 0b1
217
- assert a.c == 0xff
226
+ assert a.c == 0xFF
218
227
  assert a.d == 0b11
219
228
  assert a.e == 0b111
220
229
  assert a.dumps() == d
221
230
  ```
222
231
 
223
- ### Enums
224
- The API to access enum members and their values is similar to that of the native Enum type in Python 3. Functionally, it's best comparable to the IntEnum type.
232
+ ### Enums and Flags
233
+ The API to access enum and flag members and their values in the same way as the native Enum types in Python 3. Functionally, it's best comparable to the IntEnum or IntFlag type.
225
234
 
226
235
  ### Custom types
227
- You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `add_type(name, type)`
236
+ You can implement your own types by subclassing `BaseType`, and adding them to your cstruct instance with `add_custom_type(name, type, size, alignment, ...)`
228
237
 
229
238
  ### Custom definition parsers
230
239
  Don't like the C-like definition syntax? Write your own syntax parser!
@@ -59,12 +59,11 @@ refer to [the development guide](https://docs.dissect.tools/en/latest/contributi
59
59
  All you need to do is instantiate a new cstruct instance and load some structure definitions in there. After that you can start using them from your Python code.
60
60
 
61
61
  ```python
62
- from dissect import cstruct
62
+ from dissect.cstruct import cstruct
63
63
 
64
64
  # Default endianness is LE, but can be configured using a kwarg or setting the 'endian' attribute
65
- # e.g. cstruct.cstruct(endian='>') or cparser.endian = '>'
66
- cparser = cstruct.cstruct()
67
- cparser.load("""
65
+ # e.g. cstruct(endian='>') or c_parser.endian = '>'
66
+ parser_def = """
68
67
  #define SOME_CONSTANT 5
69
68
 
70
69
  enum Example : uint16 {
@@ -74,31 +73,35 @@ enum Example : uint16 {
74
73
  struct some_struct {
75
74
  uint8 field_1;
76
75
  char field_2[SOME_CONSTANT];
77
- char field_3[field_1 & 1 * 5]; // Some random expression to calculate array length
76
+ char field_3[(field_1 & 1) * 5]; // Some random expression to calculate array length
78
77
  Example field_4[2];
79
78
  };
80
- """)
79
+ """
80
+ c_parser = cstruct().load(parser_def)
81
81
 
82
- data = b'\x01helloworld\x00\x00\x06\x00'
83
- result = cparser.some_struct(data) # Also accepts file-like objects
82
+ data = b"\x01helloworld\x00\x00\x06\x00"
83
+ result = c_parser.some_struct(data) # Also accepts file-like objects
84
84
  assert result.field_1 == 0x01
85
- assert result.field_2 == b'hello'
86
- assert result.field_3 == b'world'
87
- assert result.field_4 == [cparser.Example.A, cparser.Example.C]
85
+ assert result.field_2 == b"hello"
86
+ assert result.field_3 == b"world"
87
+ assert result.field_4 == [c_parser.Example.A, c_parser.Example.C]
88
88
 
89
- assert cparser.Example.A == 0
90
- assert cparser.Example.C == 6
91
- assert cparser.Example(5) == cparser.Example.B
89
+ assert c_parser.Example.A == 0
90
+ assert c_parser.Example.C == 6
91
+ assert c_parser.Example(5) == c_parser.Example.B
92
92
 
93
93
  assert result.dumps() == data
94
94
 
95
95
  # You can also instantiate structures from Python by using kwargs
96
96
  # Note that array sizes are not enforced
97
- instance = cparser.some_struct(field_1=5, field_2='lorem', field_3='ipsum', field_4=[cparser.Example.B, cparser.Example.A])
98
- assert instance.dumps() == b'\x05loremipsum\x05\x00\x00\x00'
97
+ instance = c_parser.some_struct(
98
+ field_1=5, field_2="lorem", field_3="ipsum", field_4=[c_parser.Example.B, c_parser.Example.A]
99
+ )
100
+ assert instance.dumps() == b"\x05loremipsum\x05\x00\x00\x00"
101
+
99
102
  ```
100
103
 
101
- By default, all structures are compiled into classes that provide optimised performance. You can disable this by passing a `compiled=False` keyword argument to the `.load()` call. You can also inspect the resulting source code by accessing the source attribute of the structure: `print(cparser.some_struct.source)`.
104
+ By default, all structures are compiled into classes that provide optimised performance. You can disable this by passing a `compiled=False` keyword argument to the `.load()` call. You can also inspect the resulting source code by accessing the source attribute of the structure: `print(c_parser.some_struct.source)`.
102
105
 
103
106
  More examples can be found in the `examples` directory.
104
107
 
@@ -110,20 +113,23 @@ Write simple C-like structures and use them to parse binary data, as can be seen
110
113
  Aside from loading structure definitions, any of the supported types can be used individually for parsing data. For example, the following is all supported:
111
114
 
112
115
  ```python
113
- from dissect import cstruct
114
- cs = cstruct.cstruct()
116
+ from dissect.cstruct import cstruct
117
+
118
+ cs = cstruct()
115
119
  # Default endianness is LE, but can be configured using a kwarg or setting the attribute
116
- # e.g. cstruct.cstruct(endian='>') or cs.endian = '>'
117
- assert cs.uint32(b'\x05\x00\x00\x00') == 5
118
- assert cs.uint24[2](b'\x01\x00\x00\x02\x00\x00') == [1, 2] # You can also parse arrays using list indexing
119
- assert cs.char[None](b'hello world!\x00') == b'hello world!' # A list index of None means null terminated
120
+ # e.g. cstruct(endian='>') or cs.endian = '>'
121
+ assert cs.uint32(b"\x05\x00\x00\x00") == 5
122
+ assert cs.uint24[2](b"\x01\x00\x00\x02\x00\x00") == [1, 2] # You can also parse arrays using list indexing
123
+ assert cs.char[None](b"hello world!\x00") == b"hello world!" # A list index of None means null terminated
120
124
  ```
121
125
 
122
126
  ### Unions and nested structures
123
127
  Unions and nested structures are supported, both anonymous and named.
124
128
 
125
129
  ```python
126
- cdef = """
130
+ from dissect.cstruct import cstruct
131
+
132
+ c_def = """
127
133
  struct test_union {
128
134
  char magic[4];
129
135
  union {
@@ -148,58 +154,61 @@ struct test_anonymous {
148
154
  };
149
155
  };
150
156
  """
151
- c = cstruct.cstruct()
152
- c.load(cdef)
157
+ # Default endianness is LE, but can be configured using a kwarg or setting the attribute
158
+ # e.g. cstruct(endian='>') or cs.endian = '>'
159
+ c = cstruct().load(c_def)
153
160
 
154
161
  assert len(c.test_union) == 12
155
162
 
156
- a = c.test_union(b'ohaideadbeef')
157
- assert a.magic == b'ohai'
163
+ a = c.test_union(b"ohaideadbeef")
164
+ assert a.magic == b"ohai"
158
165
  assert a.c.a.a == 0x64616564
159
166
  assert a.c.a.b == 0x66656562
160
- assert a.c.b.b == b'deadbeef'
167
+ assert a.c.b.b == b"deadbeef"
161
168
 
162
- assert a.dumps() == b'ohaideadbeef'
169
+ assert a.dumps() == b"ohaideadbeef"
163
170
 
164
- b = c.test_anonymous(b'ohai\x39\x05\x00\x00\x28\x23\x00\x00deadbeef')
165
- assert b.magic == b'ohai'
171
+ b = c.test_anonymous(b"ohai\x39\x05\x00\x00\x28\x23\x00\x00deadbeef")
172
+ assert b.magic == b"ohai"
166
173
  assert b.a == 1337
167
174
  assert b.b == 9000
168
- assert b.c == b'deadbeef'
175
+ assert b.c == b"deadbeef"
176
+
169
177
  ```
170
178
 
171
179
  ### Parse bit fields
172
180
  Bit fields are supported as part of structures. They are properly aligned to their boundaries.
173
181
 
174
182
  ```python
175
- bitdef = """
183
+ from dissect.cstruct import cstruct
184
+
185
+ bit_def = """
176
186
  struct test {
177
187
  uint16 a:1;
178
- uint16 b:1; # Read 2 bits from an uint16
179
- uint32 c; # The next field is properly aligned
188
+ uint16 b:1; // Read 2 bits from an uint16
189
+ uint32 c; // The next field is properly aligned
180
190
  uint16 d:2;
181
191
  uint16 e:3;
182
192
  };
183
193
  """
184
- bitfields = cstruct.cstruct()
185
- bitfields.load(bitdef)
194
+ bitfields = cstruct().load(bit_def)
186
195
 
187
- d = b'\x03\x00\xff\x00\x00\x00\x1f\x00'
196
+ d = b"\x03\x00\xff\x00\x00\x00\x1f\x00"
188
197
  a = bitfields.test(d)
189
198
 
190
199
  assert a.a == 0b1
191
200
  assert a.b == 0b1
192
- assert a.c == 0xff
201
+ assert a.c == 0xFF
193
202
  assert a.d == 0b11
194
203
  assert a.e == 0b111
195
204
  assert a.dumps() == d
196
205
  ```
197
206
 
198
- ### Enums
199
- The API to access enum members and their values is similar to that of the native Enum type in Python 3. Functionally, it's best comparable to the IntEnum type.
207
+ ### Enums and Flags
208
+ The API to access enum and flag members and their values in the same way as the native Enum types in Python 3. Functionally, it's best comparable to the IntEnum or IntFlag type.
200
209
 
201
210
  ### Custom types
202
- You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `add_type(name, type)`
211
+ You can implement your own types by subclassing `BaseType`, and adding them to your cstruct instance with `add_custom_type(name, type, size, alignment, ...)`
203
212
 
204
213
  ### Custom definition parsers
205
214
  Don't like the C-like definition syntax? Write your own syntax parser!
@@ -26,6 +26,7 @@ from dissect.cstruct.types import (
26
26
  Wchar,
27
27
  WcharArray,
28
28
  )
29
+ from dissect.cstruct.types.enum import EnumMetaType
29
30
  from dissect.cstruct.types.packed import _struct
30
31
 
31
32
  if TYPE_CHECKING:
@@ -108,6 +109,7 @@ class _ReadSourceGenerator:
108
109
  preamble = """
109
110
  r = {}
110
111
  s = {}
112
+ o = stream.tell()
111
113
  """
112
114
 
113
115
  if any(field.bits for field in self.fields):
@@ -149,7 +151,7 @@ class _ReadSourceGenerator:
149
151
 
150
152
  if field.offset is not None and field.offset != current_offset:
151
153
  # If a field has a set offset and it's not the same as the current tracked offset, seek to it
152
- yield f"stream.seek({field.offset})"
154
+ yield f"stream.seek(o + {field.offset})"
153
155
  current_offset = field.offset
154
156
 
155
157
  if self.align and field.offset is None:
@@ -158,6 +160,9 @@ class _ReadSourceGenerator:
158
160
  for field in self.fields:
159
161
  field_type = self.cs.resolve(field.type)
160
162
 
163
+ if isinstance(field_type, EnumMetaType):
164
+ field_type = field_type.type
165
+
161
166
  if not issubclass(field_type, SUPPORTED_TYPES):
162
167
  raise TypeError(f"Unsupported type for compiler: {field_type}")
163
168
 
@@ -190,10 +195,10 @@ class _ReadSourceGenerator:
190
195
  # Bit fields
191
196
  elif field.bits:
192
197
  if not prev_was_bits:
193
- prev_bits_type = field.type
198
+ prev_bits_type = field_type
194
199
  prev_was_bits = True
195
200
 
196
- if bits_remaining == 0 or prev_bits_type != field.type:
201
+ if bits_remaining == 0 or prev_bits_type != field_type:
197
202
  bits_remaining = (size * 8) - field.bits
198
203
  bits_rollover = True
199
204
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dissect.cstruct
3
- Version: 4.0.dev3
3
+ Version: 4.0.dev5
4
4
  Summary: A Dissect module implementing a parser for C-like structures: structure parsing in Python made easy
5
5
  Author-email: Dissect Team <dissect@fox-it.com>
6
6
  License: Apache License 2.0
@@ -84,12 +84,11 @@ refer to [the development guide](https://docs.dissect.tools/en/latest/contributi
84
84
  All you need to do is instantiate a new cstruct instance and load some structure definitions in there. After that you can start using them from your Python code.
85
85
 
86
86
  ```python
87
- from dissect import cstruct
87
+ from dissect.cstruct import cstruct
88
88
 
89
89
  # Default endianness is LE, but can be configured using a kwarg or setting the 'endian' attribute
90
- # e.g. cstruct.cstruct(endian='>') or cparser.endian = '>'
91
- cparser = cstruct.cstruct()
92
- cparser.load("""
90
+ # e.g. cstruct(endian='>') or c_parser.endian = '>'
91
+ parser_def = """
93
92
  #define SOME_CONSTANT 5
94
93
 
95
94
  enum Example : uint16 {
@@ -99,31 +98,35 @@ enum Example : uint16 {
99
98
  struct some_struct {
100
99
  uint8 field_1;
101
100
  char field_2[SOME_CONSTANT];
102
- char field_3[field_1 & 1 * 5]; // Some random expression to calculate array length
101
+ char field_3[(field_1 & 1) * 5]; // Some random expression to calculate array length
103
102
  Example field_4[2];
104
103
  };
105
- """)
104
+ """
105
+ c_parser = cstruct().load(parser_def)
106
106
 
107
- data = b'\x01helloworld\x00\x00\x06\x00'
108
- result = cparser.some_struct(data) # Also accepts file-like objects
107
+ data = b"\x01helloworld\x00\x00\x06\x00"
108
+ result = c_parser.some_struct(data) # Also accepts file-like objects
109
109
  assert result.field_1 == 0x01
110
- assert result.field_2 == b'hello'
111
- assert result.field_3 == b'world'
112
- assert result.field_4 == [cparser.Example.A, cparser.Example.C]
110
+ assert result.field_2 == b"hello"
111
+ assert result.field_3 == b"world"
112
+ assert result.field_4 == [c_parser.Example.A, c_parser.Example.C]
113
113
 
114
- assert cparser.Example.A == 0
115
- assert cparser.Example.C == 6
116
- assert cparser.Example(5) == cparser.Example.B
114
+ assert c_parser.Example.A == 0
115
+ assert c_parser.Example.C == 6
116
+ assert c_parser.Example(5) == c_parser.Example.B
117
117
 
118
118
  assert result.dumps() == data
119
119
 
120
120
  # You can also instantiate structures from Python by using kwargs
121
121
  # Note that array sizes are not enforced
122
- instance = cparser.some_struct(field_1=5, field_2='lorem', field_3='ipsum', field_4=[cparser.Example.B, cparser.Example.A])
123
- assert instance.dumps() == b'\x05loremipsum\x05\x00\x00\x00'
122
+ instance = c_parser.some_struct(
123
+ field_1=5, field_2="lorem", field_3="ipsum", field_4=[c_parser.Example.B, c_parser.Example.A]
124
+ )
125
+ assert instance.dumps() == b"\x05loremipsum\x05\x00\x00\x00"
126
+
124
127
  ```
125
128
 
126
- By default, all structures are compiled into classes that provide optimised performance. You can disable this by passing a `compiled=False` keyword argument to the `.load()` call. You can also inspect the resulting source code by accessing the source attribute of the structure: `print(cparser.some_struct.source)`.
129
+ By default, all structures are compiled into classes that provide optimised performance. You can disable this by passing a `compiled=False` keyword argument to the `.load()` call. You can also inspect the resulting source code by accessing the source attribute of the structure: `print(c_parser.some_struct.source)`.
127
130
 
128
131
  More examples can be found in the `examples` directory.
129
132
 
@@ -135,20 +138,23 @@ Write simple C-like structures and use them to parse binary data, as can be seen
135
138
  Aside from loading structure definitions, any of the supported types can be used individually for parsing data. For example, the following is all supported:
136
139
 
137
140
  ```python
138
- from dissect import cstruct
139
- cs = cstruct.cstruct()
141
+ from dissect.cstruct import cstruct
142
+
143
+ cs = cstruct()
140
144
  # Default endianness is LE, but can be configured using a kwarg or setting the attribute
141
- # e.g. cstruct.cstruct(endian='>') or cs.endian = '>'
142
- assert cs.uint32(b'\x05\x00\x00\x00') == 5
143
- assert cs.uint24[2](b'\x01\x00\x00\x02\x00\x00') == [1, 2] # You can also parse arrays using list indexing
144
- assert cs.char[None](b'hello world!\x00') == b'hello world!' # A list index of None means null terminated
145
+ # e.g. cstruct(endian='>') or cs.endian = '>'
146
+ assert cs.uint32(b"\x05\x00\x00\x00") == 5
147
+ assert cs.uint24[2](b"\x01\x00\x00\x02\x00\x00") == [1, 2] # You can also parse arrays using list indexing
148
+ assert cs.char[None](b"hello world!\x00") == b"hello world!" # A list index of None means null terminated
145
149
  ```
146
150
 
147
151
  ### Unions and nested structures
148
152
  Unions and nested structures are supported, both anonymous and named.
149
153
 
150
154
  ```python
151
- cdef = """
155
+ from dissect.cstruct import cstruct
156
+
157
+ c_def = """
152
158
  struct test_union {
153
159
  char magic[4];
154
160
  union {
@@ -173,58 +179,61 @@ struct test_anonymous {
173
179
  };
174
180
  };
175
181
  """
176
- c = cstruct.cstruct()
177
- c.load(cdef)
182
+ # Default endianness is LE, but can be configured using a kwarg or setting the attribute
183
+ # e.g. cstruct(endian='>') or cs.endian = '>'
184
+ c = cstruct().load(c_def)
178
185
 
179
186
  assert len(c.test_union) == 12
180
187
 
181
- a = c.test_union(b'ohaideadbeef')
182
- assert a.magic == b'ohai'
188
+ a = c.test_union(b"ohaideadbeef")
189
+ assert a.magic == b"ohai"
183
190
  assert a.c.a.a == 0x64616564
184
191
  assert a.c.a.b == 0x66656562
185
- assert a.c.b.b == b'deadbeef'
192
+ assert a.c.b.b == b"deadbeef"
186
193
 
187
- assert a.dumps() == b'ohaideadbeef'
194
+ assert a.dumps() == b"ohaideadbeef"
188
195
 
189
- b = c.test_anonymous(b'ohai\x39\x05\x00\x00\x28\x23\x00\x00deadbeef')
190
- assert b.magic == b'ohai'
196
+ b = c.test_anonymous(b"ohai\x39\x05\x00\x00\x28\x23\x00\x00deadbeef")
197
+ assert b.magic == b"ohai"
191
198
  assert b.a == 1337
192
199
  assert b.b == 9000
193
- assert b.c == b'deadbeef'
200
+ assert b.c == b"deadbeef"
201
+
194
202
  ```
195
203
 
196
204
  ### Parse bit fields
197
205
  Bit fields are supported as part of structures. They are properly aligned to their boundaries.
198
206
 
199
207
  ```python
200
- bitdef = """
208
+ from dissect.cstruct import cstruct
209
+
210
+ bit_def = """
201
211
  struct test {
202
212
  uint16 a:1;
203
- uint16 b:1; # Read 2 bits from an uint16
204
- uint32 c; # The next field is properly aligned
213
+ uint16 b:1; // Read 2 bits from an uint16
214
+ uint32 c; // The next field is properly aligned
205
215
  uint16 d:2;
206
216
  uint16 e:3;
207
217
  };
208
218
  """
209
- bitfields = cstruct.cstruct()
210
- bitfields.load(bitdef)
219
+ bitfields = cstruct().load(bit_def)
211
220
 
212
- d = b'\x03\x00\xff\x00\x00\x00\x1f\x00'
221
+ d = b"\x03\x00\xff\x00\x00\x00\x1f\x00"
213
222
  a = bitfields.test(d)
214
223
 
215
224
  assert a.a == 0b1
216
225
  assert a.b == 0b1
217
- assert a.c == 0xff
226
+ assert a.c == 0xFF
218
227
  assert a.d == 0b11
219
228
  assert a.e == 0b111
220
229
  assert a.dumps() == d
221
230
  ```
222
231
 
223
- ### Enums
224
- The API to access enum members and their values is similar to that of the native Enum type in Python 3. Functionally, it's best comparable to the IntEnum type.
232
+ ### Enums and Flags
233
+ The API to access enum and flag members and their values in the same way as the native Enum types in Python 3. Functionally, it's best comparable to the IntEnum or IntFlag type.
225
234
 
226
235
  ### Custom types
227
- You can implement your own types by subclassing `BaseType` or `RawType`, and adding them to your cstruct instance with `add_type(name, type)`
236
+ You can implement your own types by subclassing `BaseType`, and adding them to your cstruct instance with `add_custom_type(name, type, size, alignment, ...)`
228
237
 
229
238
  ### Custom definition parsers
230
239
  Don't like the C-like definition syntax? Write your own syntax parser!
@@ -1,3 +1,5 @@
1
+ import io
2
+
1
3
  import pytest
2
4
 
3
5
  from dissect.cstruct.cstruct import cstruct
@@ -265,3 +267,31 @@ def test_bitfield_char(cs: cstruct, compiled: bool) -> None:
265
267
  assert obj.d == b"i420"
266
268
 
267
269
  assert obj.dumps() == buf
270
+
271
+
272
+ def test_bitfield_dynamic(cs: cstruct, compiled: bool) -> None:
273
+ cdef = """
274
+ enum A : uint16 {
275
+ A = 0x0
276
+ };
277
+
278
+ struct test {
279
+ uint16 size : 4;
280
+ A b : 4;
281
+ char d[size];
282
+ };
283
+ """
284
+
285
+ cs.load(cdef, compiled=compiled)
286
+ assert verify_compiled(cs.test, compiled)
287
+
288
+ buf = io.BytesIO(b"\x00\x00\xF4\x00help")
289
+ buf.seek(2)
290
+ obj = cs.test(buf)
291
+
292
+ assert obj.size == 4
293
+ assert obj.b == 0xF
294
+ assert obj.d == b"help"
295
+
296
+ buf.seek(2)
297
+ assert obj.dumps() == buf.read()
@@ -323,4 +323,81 @@ def test_generate_bits_read(cs: cstruct, TestEnum: type[Enum]) -> None:
323
323
  assert code == dedent(expected)
324
324
 
325
325
 
326
- # TODO: the rest of the compiler
326
+ @pytest.mark.parametrize("other_type", ["int8", "uint64"])
327
+ def test_generate_fields_dynamic_after_bitfield(cs: cstruct, TestEnum: Enum, other_type: str) -> None:
328
+ _type = getattr(cs, other_type)
329
+
330
+ fields = [
331
+ Field("size", cs.uint16, offset=0),
332
+ Field("a", TestEnum, 4, offset=2),
333
+ Field("b", _type, 4),
334
+ Field("c", cs.char["size"], offset=3),
335
+ ]
336
+
337
+ output = "\n".join(compiler._ReadSourceGenerator(cs, fields)._generate_fields())
338
+
339
+ expected = """
340
+ buf = stream.read(2)
341
+ if len(buf) != 2: raise EOFError()
342
+ data = _struct(cls.cs.endian, "H").unpack(buf)
343
+
344
+ r["size"] = type.__call__(_0, data[0])
345
+ s["size"] = 2
346
+
347
+
348
+ _t = _1
349
+ r["a"] = type.__call__(_t, bit_reader.read(_t.type, 4))
350
+
351
+
352
+ _t = _2
353
+ r["b"] = type.__call__(_t, bit_reader.read(_t, 4))
354
+
355
+ bit_reader.reset()
356
+ stream.seek(o + 3)
357
+
358
+ _s = stream.tell()
359
+ r["c"] = _3._read(stream, context=r)
360
+ s["c"] = stream.tell() - _s
361
+ """
362
+
363
+ assert output.strip() == dedent(expected).strip()
364
+
365
+
366
+ @pytest.mark.parametrize("other_type", ["int8", "uint64"])
367
+ def test_generate_fields_dynamic_before_bitfield(cs: cstruct, TestEnum: Enum, other_type: str) -> None:
368
+ _type = getattr(cs, other_type)
369
+
370
+ fields = [
371
+ Field("size", cs.uint16, offset=0),
372
+ Field("a", _type, 4, offset=2),
373
+ Field("b", TestEnum, 4),
374
+ Field("c", cs.char["size"], offset=3),
375
+ ]
376
+
377
+ output = "\n".join(compiler._ReadSourceGenerator(cs, fields)._generate_fields())
378
+
379
+ expected = """
380
+ buf = stream.read(2)
381
+ if len(buf) != 2: raise EOFError()
382
+ data = _struct(cls.cs.endian, "H").unpack(buf)
383
+
384
+ r["size"] = type.__call__(_0, data[0])
385
+ s["size"] = 2
386
+
387
+
388
+ _t = _1
389
+ r["a"] = type.__call__(_t, bit_reader.read(_t, 4))
390
+
391
+
392
+ _t = _2
393
+ r["b"] = type.__call__(_t, bit_reader.read(_t.type, 4))
394
+
395
+ bit_reader.reset()
396
+ stream.seek(o + 3)
397
+
398
+ _s = stream.tell()
399
+ r["c"] = _3._read(stream, context=r)
400
+ s["c"] = stream.tell() - _s
401
+ """
402
+
403
+ assert output.strip() == dedent(expected).strip()