Skip to content

Commit 3032ad7

Browse files
authored
Add float helpers (#167)
1 parent 32253fe commit 3032ad7

6 files changed

Lines changed: 137 additions & 35 deletions

File tree

dissect/cstruct/__init__.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,18 @@
3030
)
3131
from dissect.cstruct.util import (
3232
dumpstruct,
33+
f16,
34+
f32,
35+
f64,
3336
hexdump,
3437
p8,
3538
p16,
3639
p32,
3740
p64,
3841
pack,
42+
pf16,
43+
pf32,
44+
pf64,
3945
swap,
4046
swap16,
4147
swap32,
@@ -75,12 +81,18 @@
7581
"ctypes",
7682
"ctypes_type",
7783
"dumpstruct",
84+
"f16",
85+
"f32",
86+
"f64",
7887
"hexdump",
7988
"p8",
8089
"p16",
8190
"p32",
8291
"p64",
8392
"pack",
93+
"pf16",
94+
"pf32",
95+
"pf64",
8496
"swap",
8597
"swap16",
8698
"swap32",

dissect/cstruct/compiler.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from dissect.cstruct.bitbuffer import BitBuffer
1212
from dissect.cstruct.types import (
1313
Array,
14+
BaseArray,
1415
Char,
1516
CharArray,
1617
Flag,
@@ -24,18 +25,15 @@
2425
Wchar,
2526
WcharArray,
2627
)
27-
from dissect.cstruct.types.base import BaseArray
2828
from dissect.cstruct.types.enum import EnumMetaType
29-
from dissect.cstruct.types.packed import _struct
29+
from dissect.cstruct.util import _struct
3030

3131
if TYPE_CHECKING:
3232
from collections.abc import Iterator
3333
from types import MethodType
3434

3535
from dissect.cstruct.cstruct import cstruct
36-
from dissect.cstruct.types import (
37-
BaseType,
38-
)
36+
from dissect.cstruct.types import BaseType
3937
from dissect.cstruct.types.structure import Field
4038

4139
SUPPORTED_TYPES = (

dissect/cstruct/types/base.py

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from dissect.cstruct.exception import ArraySizeError
88
from dissect.cstruct.expression import Expression
9+
from dissect.cstruct.util import normalize_endianness
910

1011
if TYPE_CHECKING:
1112
from collections.abc import Callable
@@ -328,27 +329,5 @@ def _is_eof(stream: BinaryIO) -> bool:
328329
return False
329330

330331

331-
ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
332-
"<": "<",
333-
">": ">",
334-
"!": "!",
335-
"@": "@",
336-
"=": "=",
337-
"network": "!",
338-
"little": "<",
339-
"big": ">",
340-
}
341-
342-
343-
def normalize_endianness(endian: AllowedEndianness) -> Endianness:
344-
"""Normalize an endianness string to one of the standard format characters."""
345-
try:
346-
return ENDIANNESS_MAP[endian]
347-
except KeyError:
348-
raise ValueError(
349-
f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
350-
) from None
351-
352-
353332
# As mentioned in the BaseType class, we correctly set the type here
354333
MetaType.ArrayType = Array

dissect/cstruct/types/packed.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
11
from __future__ import annotations
22

3-
from functools import lru_cache
4-
from struct import Struct
53
from typing import TYPE_CHECKING, Any, BinaryIO, Generic, TypeVar
64

75
from dissect.cstruct.types.base import EOF, BaseType
6+
from dissect.cstruct.util import _struct
87

98
if TYPE_CHECKING:
109
from typing_extensions import Self
1110

1211
from dissect.cstruct.cstruct import Endianness
1312

1413

15-
@lru_cache(1024)
16-
def _struct(endian: str, packchar: str) -> Struct:
17-
return Struct(f"{endian}{packchar}")
18-
19-
2014
T = TypeVar("T", int, float)
2115

2216

dissect/cstruct/util.py

Lines changed: 90 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,17 @@
33
import os
44
import pprint
55
import string
6+
import struct
67
import sys
78
from enum import Enum
9+
from functools import lru_cache
810
from typing import TYPE_CHECKING
911

1012
if TYPE_CHECKING:
1113
from collections.abc import Iterator
1214
from typing import Literal
1315

14-
from dissect.cstruct.cstruct import AllowedEndianness
16+
from dissect.cstruct.cstruct import AllowedEndianness, Endianness
1517
from dissect.cstruct.types.base import BaseType
1618
from dissect.cstruct.types.structure import Structure
1719

@@ -64,6 +66,28 @@
6466
"big": "big",
6567
}
6668

69+
ENDIANNESS_MAP: dict[AllowedEndianness, Endianness] = {
70+
"<": "<",
71+
">": ">",
72+
"!": "!",
73+
"@": "@",
74+
"=": "=",
75+
"network": "!",
76+
"little": "<",
77+
"big": ">",
78+
}
79+
80+
81+
def normalize_endianness(endian: AllowedEndianness) -> Endianness:
82+
"""Normalize an endianness string to one of the standard format characters."""
83+
try:
84+
return ENDIANNESS_MAP[endian]
85+
except KeyError:
86+
raise ValueError(
87+
f"Invalid endianness: {endian!r}, expected one of {', '.join(ENDIANNESS_MAP.keys())}"
88+
) from None
89+
90+
6791
Palette = list[tuple[int, str]]
6892

6993

@@ -327,6 +351,11 @@ def dumpstruct(
327351
return _dumpstruct(obj, obj.dumps(), offset, color, output, autoskip)
328352

329353

354+
@lru_cache(1024)
355+
def _struct(endian: str, packchar: str) -> struct.Struct:
356+
return struct.Struct(f"{endian}{packchar}")
357+
358+
330359
def pack(value: int, size: int | None = None, endian: AllowedEndianness = "little") -> bytes:
331360
"""Pack an integer value to a given bit size, endianness.
332361
@@ -400,6 +429,36 @@ def p64(value: int, endian: AllowedEndianness = "little") -> bytes:
400429
return pack(value, 64, endian)
401430

402431

432+
def pf16(value: float, endian: AllowedEndianness = "little") -> bytes:
433+
"""Pack a 16 bit float.
434+
435+
Arguments:
436+
value: Value to pack.
437+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
438+
"""
439+
return _struct(normalize_endianness(endian), "e").pack(value)
440+
441+
442+
def pf32(value: float, endian: AllowedEndianness = "little") -> bytes:
443+
"""Pack a 32 bit float.
444+
445+
Arguments:
446+
value: Value to pack.
447+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
448+
"""
449+
return _struct(normalize_endianness(endian), "f").pack(value)
450+
451+
452+
def pf64(value: float, endian: AllowedEndianness = "little") -> bytes:
453+
"""Pack a 64 bit float.
454+
455+
Arguments:
456+
value: Value to pack.
457+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
458+
"""
459+
return _struct(normalize_endianness(endian), "d").pack(value)
460+
461+
403462
def u8(value: bytes, endian: AllowedEndianness = "little", sign: bool = False) -> int:
404463
"""Unpack an 8 bit integer.
405464
@@ -444,6 +503,36 @@ def u64(value: bytes, endian: AllowedEndianness = "little", sign: bool = False)
444503
return unpack(value, 64, endian, sign)
445504

446505

506+
def f16(value: bytes, endian: AllowedEndianness = "little") -> float:
507+
"""Unpack a 16 bit float.
508+
509+
Arguments:
510+
value: Value to unpack.
511+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
512+
"""
513+
return _struct(normalize_endianness(endian), "e").unpack(value)[0]
514+
515+
516+
def f32(value: bytes, endian: AllowedEndianness = "little") -> float:
517+
"""Unpack a 32 bit float.
518+
519+
Arguments:
520+
value: Value to unpack.
521+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
522+
"""
523+
return _struct(normalize_endianness(endian), "f").unpack(value)[0]
524+
525+
526+
def f64(value: bytes, endian: AllowedEndianness = "little") -> float:
527+
"""Unpack a 64 bit float.
528+
529+
Arguments:
530+
value: Value to unpack.
531+
endian: Endianness to use (little, big, network, <, >, !, @ or =).
532+
"""
533+
return _struct(normalize_endianness(endian), "d").unpack(value)[0]
534+
535+
447536
def swap(value: int, size: int) -> int:
448537
"""Swap the endianness of an integer with a given bit size.
449538

tests/test_utils.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,54 +259,84 @@ def test_pack_unpack() -> None:
259259
assert util.p16(1, endian) == b"\x01\x00"
260260
assert util.p32(1, endian) == b"\x01\x00\x00\x00"
261261
assert util.p64(1, endian) == b"\x01\x00\x00\x00\x00\x00\x00\x00"
262+
assert util.pf16(1.0, endian) == b"\x00\x3c"
263+
assert util.pf32(1.0, endian) == b"\x00\x00\x80\x3f"
264+
assert util.pf64(1.0, endian) == b"\x00\x00\x00\x00\x00\x00\xf0\x3f"
262265
assert util.u8(b"\x01", endian, sign) == 1
263266
assert util.u16(b"\x01\x00", endian, sign) == 1
264267
assert util.u32(b"\x01\x00\x00\x00", endian, sign) == 1
265268
assert util.u64(b"\x01\x00\x00\x00\x00\x00\x00\x00", endian, sign) == 1
269+
assert util.f16(b"\x00\x3c", endian) == 1.0
270+
assert util.f32(b"\x00\x00\x80\x3f", endian) == 1.0
271+
assert util.f64(b"\x00\x00\x00\x00\x00\x00\xf0\x3f", endian) == 1.0
266272

267273
endian = "big"
268274
sign = False
269275
assert util.p8(1, endian) == b"\x01"
270276
assert util.p16(1, endian) == b"\x00\x01"
271277
assert util.p32(1, endian) == b"\x00\x00\x00\x01"
272278
assert util.p64(1, endian) == b"\x00\x00\x00\x00\x00\x00\x00\x01"
279+
assert util.pf16(1.0, endian) == b"\x3c\x00"
280+
assert util.pf32(1.0, endian) == b"\x3f\x80\x00\x00"
281+
assert util.pf64(1.0, endian) == b"\x3f\xf0\x00\x00\x00\x00\x00\x00"
273282
assert util.u8(b"\x01", endian, sign) == 1
274283
assert util.u16(b"\x00\x01", endian, sign) == 1
275284
assert util.u32(b"\x00\x00\x00\x01", endian, sign) == 1
276285
assert util.u64(b"\x00\x00\x00\x00\x00\x00\x00\x01", endian, sign) == 1
286+
assert util.f16(b"\x3c\x00", endian) == 1.0
287+
assert util.f32(b"\x3f\x80\x00\x00", endian) == 1.0
288+
assert util.f64(b"\x3f\xf0\x00\x00\x00\x00\x00\x00", endian) == 1.0
277289

278290
endian = "network"
279291
sign = False
280292
assert util.p8(1, endian) == b"\x01"
281293
assert util.p16(1, endian) == b"\x00\x01"
282294
assert util.p32(1, endian) == b"\x00\x00\x00\x01"
283295
assert util.p64(1, endian) == b"\x00\x00\x00\x00\x00\x00\x00\x01"
296+
assert util.pf16(1.0, endian) == b"\x3c\x00"
297+
assert util.pf32(1.0, endian) == b"\x3f\x80\x00\x00"
298+
assert util.pf64(1.0, endian) == b"\x3f\xf0\x00\x00\x00\x00\x00\x00"
284299
assert util.u8(b"\x01", endian, sign) == 1
285300
assert util.u16(b"\x00\x01", endian, sign) == 1
286301
assert util.u32(b"\x00\x00\x00\x01", endian, sign) == 1
287302
assert util.u64(b"\x00\x00\x00\x00\x00\x00\x00\x01", endian, sign) == 1
303+
assert util.f16(b"\x3c\x00", endian) == 1.0
304+
assert util.f32(b"\x3f\x80\x00\x00", endian) == 1.0
305+
assert util.f64(b"\x3f\xf0\x00\x00\x00\x00\x00\x00", endian) == 1.0
288306

289307
endian = "little"
290308
sign = True
291309
assert util.p8(-120, endian) == b"\x88"
292310
assert util.p16(-120, endian) == b"\x88\xff"
293311
assert util.p32(-120, endian) == b"\x88\xff\xff\xff"
294312
assert util.p64(-120, endian) == b"\x88\xff\xff\xff\xff\xff\xff\xff"
313+
assert util.pf16(-120.0, endian) == b"\x80\xd7"
314+
assert util.pf32(-120.0, endian) == b"\x00\x00\xf0\xc2"
315+
assert util.pf64(-120.0, endian) == b"\x00\x00\x00\x00\x00\x00\x5e\xc0"
295316
assert util.u8(b"\x88", endian, sign) == -120
296317
assert util.u16(b"\x88\xff", endian, sign) == -120
297318
assert util.u32(b"\x88\xff\xff\xff", endian, sign) == -120
298319
assert util.u64(b"\x88\xff\xff\xff\xff\xff\xff\xff", endian, sign) == -120
320+
assert util.f16(b"\x80\xd7", endian) == -120.0
321+
assert util.f32(b"\x00\x00\xf0\xc2", endian) == -120.0
322+
assert util.f64(b"\x00\x00\x00\x00\x00\x00\x5e\xc0", endian) == -120.0
299323

300324
endian = "big"
301325
sign = True
302326
assert util.p8(-120, endian) == b"\x88"
303327
assert util.p16(-120, endian) == b"\xff\x88"
304328
assert util.p32(-120, endian) == b"\xff\xff\xff\x88"
305329
assert util.p64(-120, endian) == b"\xff\xff\xff\xff\xff\xff\xff\x88"
330+
assert util.pf16(-120.0, endian) == b"\xd7\x80"
331+
assert util.pf32(-120.0, endian) == b"\xc2\xf0\x00\x00"
332+
assert util.pf64(-120.0, endian) == b"\xc0\x5e\x00\x00\x00\x00\x00\x00"
306333
assert util.u8(b"\x88", endian, sign) == -120
307334
assert util.u16(b"\xff\x88", endian, sign) == -120
308335
assert util.u32(b"\xff\xff\xff\x88", endian, sign) == -120
309336
assert util.u64(b"\xff\xff\xff\xff\xff\xff\xff\x88", endian, sign) == -120
337+
assert util.f16(b"\xd7\x80", endian) == -120.0
338+
assert util.f32(b"\xc2\xf0\x00\x00", endian) == -120.0
339+
assert util.f64(b"\xc0\x5e\x00\x00\x00\x00\x00\x00", endian) == -120.0
310340

311341
assert util.pack(1, 24) == b"\x01\x00\x00"
312342
assert util.unpack(b"\x01\x00\x00", 24) == 1

0 commit comments

Comments
 (0)