|
1 | 1 | # |
2 | 2 | # This file is part of libdestruct (https://github.com/mrindeciso/libdestruct). |
3 | | -# Copyright (c) 2024 Roberto Alessandro Bertolini. All rights reserved. |
| 3 | +# Copyright (c) 2026 Roberto Alessandro Bertolini. All rights reserved. |
4 | 4 | # Licensed under the MIT license. See LICENSE file in the project root for details. |
5 | 5 | # |
6 | 6 |
|
7 | 7 | import unittest |
| 8 | +from enum import IntEnum |
8 | 9 |
|
9 | | -from libdebug import debugger |
10 | | -from libdestruct import array, array_of, inflater, c_int, c_long, struct |
| 10 | +from libdestruct import array, c_int, inflater, struct, array_of, enum, enum_of, size_of |
11 | 11 |
|
12 | | -class ArrayTest(unittest.TestCase): |
13 | | - def test_linear_arrays_1(self): |
14 | | - d = debugger("binaries/array_test") |
15 | 12 |
|
16 | | - d.run() |
| 13 | +class ArrayUnitTest(unittest.TestCase): |
| 14 | + """Array operations without debugger.""" |
17 | 15 |
|
18 | | - bp = d.bp("do_nothing") |
| 16 | + def test_array_value_property(self): |
| 17 | + """.value calls self.get() without args - should not raise TypeError.""" |
| 18 | + memory = b"".join((i).to_bytes(4, "little") for i in range(5)) |
| 19 | + lib = inflater(memory) |
| 20 | + arr = lib.inflate(array_of(c_int, 5), 0) |
19 | 21 |
|
20 | | - d.cont() |
| 22 | + val = arr.value |
| 23 | + self.assertIsNotNone(val) |
| 24 | + |
| 25 | + def test_array_repr(self): |
| 26 | + memory = b"".join((i).to_bytes(4, "little") for i in range(3)) |
| 27 | + lib = inflater(memory) |
| 28 | + arr = lib.inflate(array_of(c_int, 3), 0) |
21 | 29 |
|
22 | | - libdestruct = inflater(d.memory) |
| 30 | + r = repr(arr) |
| 31 | + self.assertIsInstance(r, str) |
23 | 32 |
|
24 | | - self.assertTrue(bp.hit_on(d)) |
| 33 | + def test_array_indexing(self): |
| 34 | + memory = b"".join((i).to_bytes(4, "little") for i in range(5)) |
| 35 | + lib = inflater(memory) |
| 36 | + arr = lib.inflate(array_of(c_int, 5), 0) |
25 | 37 |
|
26 | | - test1 = libdestruct.inflate(array_of(c_int, 10), d.regs.rdi) |
| 38 | + for i in range(5): |
| 39 | + self.assertEqual(arr[i].value, i) |
| 40 | + |
| 41 | + def test_array_iteration(self): |
| 42 | + memory = b"".join((i * 10).to_bytes(4, "little") for i in range(3)) |
| 43 | + lib = inflater(memory) |
| 44 | + arr = lib.inflate(array_of(c_int, 3), 0) |
27 | 45 |
|
28 | | - self.assertEqual(len(test1), 10) |
| 46 | + values = [x.value for x in arr] |
| 47 | + self.assertEqual(values, [0, 10, 20]) |
29 | 48 |
|
30 | | - for i in range(10): |
31 | | - self.assertEqual(test1[i].value, i ** 2) |
| 49 | + def test_array_len(self): |
| 50 | + memory = b"".join((0).to_bytes(4, "little") for _ in range(7)) |
| 51 | + lib = inflater(memory) |
| 52 | + arr = lib.inflate(array_of(c_int, 7), 0) |
32 | 53 |
|
33 | | - self.assertEqual(bytes(test1), b"".join((i ** 2).to_bytes(4, "little") for i in range(10))) |
| 54 | + self.assertEqual(len(arr), 7) |
34 | 55 |
|
35 | | - self.assertIn(test1[0], test1) |
| 56 | + def test_array_contains(self): |
| 57 | + memory = b"".join((i).to_bytes(4, "little") for i in range(5)) |
| 58 | + lib = inflater(memory) |
| 59 | + arr = lib.inflate(array_of(c_int, 5), 0) |
36 | 60 |
|
37 | | - d.cont() |
| 61 | + elem = arr[2] |
| 62 | + self.assertIn(elem, arr) |
38 | 63 |
|
39 | | - class test2_t(struct): |
40 | | - a: c_int |
| 64 | + def test_array_to_bytes(self): |
| 65 | + memory = b"".join((i).to_bytes(4, "little") for i in range(3)) |
| 66 | + lib = inflater(memory) |
| 67 | + arr = lib.inflate(array_of(c_int, 3), 0) |
41 | 68 |
|
42 | | - test2 = libdestruct.inflate(array_of(test2_t, 10), d.regs.rdi) |
| 69 | + result = arr.to_bytes() |
| 70 | + self.assertIsInstance(result, bytes) |
| 71 | + self.assertEqual(result, memory) |
43 | 72 |
|
44 | | - for i in range(10): |
45 | | - self.assertEqual(test2[i].a.value, i ** 3) |
| 73 | + def test_array_to_str(self): |
| 74 | + memory = b"".join((i).to_bytes(4, "little") for i in range(3)) |
| 75 | + lib = inflater(memory) |
| 76 | + arr = lib.inflate(array_of(c_int, 3), 0) |
46 | 77 |
|
47 | | - d.cont() |
| 78 | + result = arr.to_str() |
| 79 | + self.assertIn("0", result) |
| 80 | + self.assertIn("1", result) |
| 81 | + self.assertIn("2", result) |
48 | 82 |
|
49 | | - class test3_t(struct): |
50 | | - a: c_int |
51 | | - b: c_long |
| 83 | + def test_array_value_returns_all_elements(self): |
| 84 | + memory = b"".join((i).to_bytes(4, "little") for i in range(4)) |
| 85 | + lib = inflater(memory) |
| 86 | + arr = lib.inflate(array_of(c_int, 4), 0) |
52 | 87 |
|
53 | | - test3 = libdestruct.inflate(array_of(test3_t, 10), d.regs.rdi) |
| 88 | + val = arr.value |
| 89 | + self.assertIsInstance(val, list) |
| 90 | + self.assertEqual(len(val), 4) |
| 91 | + self.assertEqual([x.value for x in val], [0, 1, 2, 3]) |
54 | 92 |
|
55 | | - for i in range(10): |
56 | | - self.assertEqual(test3[i].a.value, i * 100) |
57 | | - self.assertEqual(test3[i].b.value, i * 1000) |
| 93 | + def test_bytes_on_bytearray_backed_array(self): |
| 94 | + memory = bytearray(b"".join((i).to_bytes(4, "little") for i in range(3))) |
| 95 | + lib = inflater(memory) |
| 96 | + arr = lib.inflate(array_of(c_int, 3), 0) |
58 | 97 |
|
59 | | - d.cont() |
| 98 | + result = bytes(arr) |
| 99 | + self.assertIsInstance(result, bytes) |
| 100 | + self.assertEqual(len(result), 12) |
60 | 101 |
|
61 | | - class test4_t(struct): |
62 | | - a: c_int |
63 | | - b: array = array_of(c_int, 10) |
64 | 102 |
|
65 | | - test4 = libdestruct.inflate(array_of(test4_t, 10), d.regs.rdi) |
| 103 | +class NegativeArrayCountTest(unittest.TestCase): |
| 104 | + """array[T, N] must reject non-positive counts at handler time.""" |
66 | 105 |
|
67 | | - for i in range(10): |
68 | | - self.assertEqual(test4[i].a.value, i ** 4) |
69 | | - for j in range(10): |
70 | | - self.assertEqual(test4[i].b[j].value, (i + 1) * j) |
| 106 | + def test_negative_count_raises(self): |
| 107 | + """array[c_int, -5] must raise ValueError.""" |
| 108 | + with self.assertRaises(ValueError): |
| 109 | + class s_t(struct): |
| 110 | + data: array[c_int, -5] |
| 111 | + size_of(s_t) |
71 | 112 |
|
72 | | - with self.assertRaises(NotImplementedError): |
73 | | - test4[i] = 4 |
| 113 | + def test_zero_count_raises(self): |
| 114 | + """array[c_int, 0] must raise ValueError.""" |
| 115 | + with self.assertRaises(ValueError): |
| 116 | + class s_t(struct): |
| 117 | + data: array[c_int, 0] |
| 118 | + size_of(s_t) |
74 | 119 |
|
75 | | - with self.assertRaises(NotImplementedError): |
76 | | - test4._set([1, 2, 3]) |
| 120 | + def test_positive_count_works(self): |
| 121 | + """array[c_int, 3] must work fine.""" |
| 122 | + class s_t(struct): |
| 123 | + data: array[c_int, 3] |
| 124 | + self.assertEqual(size_of(s_t), 12) |
77 | 125 |
|
78 | | - with self.assertRaises(NotImplementedError): |
79 | | - test4.set([1, 2, 3]) |
80 | 126 |
|
81 | | - d.kill() |
82 | | - d.terminate() |
| 127 | +class ArrayFreezeElementsTest(unittest.TestCase): |
| 128 | + """Freezing a struct with an array must freeze the array elements too.""" |
| 129 | + |
| 130 | + def test_array_element_access_returns_frozen_value(self): |
| 131 | + """s.data[0].value should return the frozen value, not live memory.""" |
| 132 | + class arr_struct_t(struct): |
| 133 | + count: c_int |
| 134 | + data: array_of(c_int, 3) |
| 135 | + |
| 136 | + memory = bytearray(16) |
| 137 | + memory[0:4] = (3).to_bytes(4, "little") |
| 138 | + memory[4:8] = (10).to_bytes(4, "little") |
| 139 | + memory[8:12] = (20).to_bytes(4, "little") |
| 140 | + memory[12:16] = (30).to_bytes(4, "little") |
| 141 | + |
| 142 | + lib = inflater(memory) |
| 143 | + s = lib.inflate(arr_struct_t, 0) |
| 144 | + s.freeze() |
| 145 | + |
| 146 | + memory[4:8] = (99).to_bytes(4, "little") |
| 147 | + |
| 148 | + self.assertEqual(s.count.value, 3) |
| 149 | + self.assertEqual(s.data[0].value, 10) |
| 150 | + |
| 151 | + def test_array_value_property_returns_frozen_elements(self): |
| 152 | + """s.data.value should contain frozen element values after memory mutation.""" |
| 153 | + class arr_struct_t(struct): |
| 154 | + data: array_of(c_int, 2) |
| 155 | + |
| 156 | + memory = bytearray(8) |
| 157 | + memory[0:4] = (10).to_bytes(4, "little") |
| 158 | + memory[4:8] = (20).to_bytes(4, "little") |
| 159 | + |
| 160 | + lib = inflater(memory) |
| 161 | + s = lib.inflate(arr_struct_t, 0) |
| 162 | + s.freeze() |
| 163 | + |
| 164 | + memory[0:4] = (99).to_bytes(4, "little") |
| 165 | + |
| 166 | + frozen_vals = [elem.value for elem in s.data.value] |
| 167 | + self.assertEqual(frozen_vals, [10, 20]) |
| 168 | + |
| 169 | + def test_array_to_bytes_returns_frozen_bytes(self): |
| 170 | + """s.data.to_bytes() should return frozen bytes, not live memory.""" |
| 171 | + class arr_struct_t(struct): |
| 172 | + data: array_of(c_int, 2) |
| 173 | + |
| 174 | + memory = bytearray(8) |
| 175 | + memory[0:4] = (10).to_bytes(4, "little") |
| 176 | + memory[4:8] = (20).to_bytes(4, "little") |
| 177 | + |
| 178 | + lib = inflater(memory) |
| 179 | + s = lib.inflate(arr_struct_t, 0) |
| 180 | + s.freeze() |
| 181 | + |
| 182 | + expected_bytes = bytes(memory) |
| 183 | + |
| 184 | + memory[0:4] = (99).to_bytes(4, "little") |
| 185 | + |
| 186 | + self.assertEqual(s.data.to_bytes(), expected_bytes) |
| 187 | + |
| 188 | + |
| 189 | +if __name__ == "__main__": |
| 190 | + unittest.main() |
0 commit comments