-
Notifications
You must be signed in to change notification settings - Fork 4
/
bytetree.py
executable file
·147 lines (114 loc) · 4.02 KB
/
bytetree.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#!/usr/bin/env python
from collections.abc import Sequence
from typing import ByteString, Iterator, List, Tuple, Union
BYTEORDER = "big"
# ByteTreeValue = Union[ByteString, Sequence["ByteTree"]]
class ByteTree:
"""
Class representing a byte tree, as defined by Verificatum.
For the exact spec, see Appendix A of the "User Manual for the Verificatum Mix-Net".
https://www.verificatum.org/files/vmnum-3.0.4.pdf
"""
NODE = 0
LEAF = 1
def __init__(self, value) -> None:
if not isinstance(value, Sequence):
raise TypeError("value should be of type Sequence")
self.type: int
if isinstance(value[0], int):
self.type = ByteTree.LEAF
else:
self.type = ByteTree.NODE
self.value = value
def is_node(self) -> bool:
return self.type == ByteTree.NODE
def is_leaf(self) -> bool:
return self.type == ByteTree.LEAF
@classmethod
def from_byte_array(cls, source: ByteString) -> "ByteTree":
"""
Read a byte tree from a byte array
"""
return cls._from_byte_array(source, 0)[0]
@classmethod
def _from_byte_array(cls, source: ByteString, index=0) -> Tuple["ByteTree", int]:
original_index = index
tpe = source[index]
assert tpe in (cls.NODE, cls.LEAF)
index += 1
length = int.from_bytes(bytes(source[index : index + 4]), BYTEORDER)
assert length >= 0
index += 4
if tpe == cls.LEAF:
if index + length > len(source):
raise ValueError("Length larger than source")
byte_tree = cls(source[index : index + length])
index += length
else:
children = []
for _ in range(length):
child, offset = cls._from_byte_array(source, index)
children.append(child)
index += offset
byte_tree = cls(children)
return byte_tree, index - original_index
def to_byte_array(self) -> ByteString:
"""
Convert byte tree to its continuous byte array representation.
"""
byte_array = self.type.to_bytes(1, BYTEORDER)
byte_array += len(self.value).to_bytes(4, BYTEORDER)
# index = 0 + 1 + 4
if self.is_leaf():
byte_array += bytes(self.value)
# index += len(self.value)
else:
for child in self.value:
child: "ByteTree"
byte_array += child.to_byte_array()
# index += offset
return byte_array
def pretty_str(self, indent: int = 0) -> str:
"""
Writes formatted string illustrating the byte tree.
See `ByteTreeBasic::prettyWriteTo` in verificatum-vcr.
"""
return "".join(self._pretty_str(indent))
def _pretty_str(self, indent: int) -> Iterator[str]:
s = 2 * indent * " "
if self.is_leaf():
data = "".join(_byte_to_hex(x) for x in self.value)
yield f'{s}"{data}"'
else:
yield f"{s}[\n"
for idx, child in enumerate(self.value):
child: "ByteTree"
is_last = idx == len(self.value) - 1
yield from child.pretty_str(indent + 1)
if not is_last:
yield ","
yield "\n"
yield f"{s}]"
def _byte_to_hex(x: int) -> str:
x = hex(x)[2:]
assert len(x) in (1, 2)
return x.rjust(2, "0")
def _main(args: List[str]) -> int:
"""
Port of `vbt`'s base functionality in python.
"""
if len(args) > 2:
print("Usage:", args[0], "<filename>", file=sys.stderr)
return 1
elif len(args) == 2:
with open(args[1], "rb") as f:
inp = f.read()
else:
inp = sys.stdin.read()
print(byte_array_byte_tree_to_json(bytearray(inp)))
return 0
def byte_array_byte_tree_to_json(ba: ByteString):
return ByteTree.from_byte_array(ba).pretty_str()
if __name__ == "__main__":
import sys
sys.exit(_main(sys.argv))