Skip to content

Commit f973998

Browse files
committed
FrameDetector: Implement load_script
1 parent 8205282 commit f973998

File tree

2 files changed

+261
-0
lines changed

2 files changed

+261
-0
lines changed

zbnt/Encoding.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
"""
1818

19+
import struct
20+
1921
# encode_x : number to bytes
2022

2123
def encode_bool(value):
@@ -45,6 +47,12 @@ def encode_s32(value):
4547
def encode_s64(value):
4648
return value.to_bytes(8, byteorder="little", signed=True)
4749

50+
def encode_float(value):
51+
return struct.pack("<f", value)
52+
53+
def encode_double(value):
54+
return struct.pack("<d", value)
55+
4856
def encode_str(value):
4957
return value.encode("UTF-8")
5058

@@ -83,6 +91,12 @@ def decode_s32(value):
8391
def decode_s64(value):
8492
return int.from_bytes(value[:8], byteorder="little", signed=True)
8593

94+
def decode_float(value):
95+
return struct.unpack("<f", value)[0]
96+
97+
def decode_double(value):
98+
return struct.unpack("<d", value)[0]
99+
86100
def decode_str(value):
87101
return value.encode("UTF-8")
88102

zbnt/FrameDetector.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
along with this program. If not, see <https://www.gnu.org/licenses/>.
1717
"""
1818

19+
import re
20+
import math
1921
from enum import IntFlag
2022

2123
from .Enums import *
@@ -44,6 +46,24 @@ class FrameDetector(AxiDevice):
4446
])
4547
}
4648

49+
_regex_comp_instr = re.compile("^(?:nop|(s?[lg]tq?|eq|or|and)(8|16|24|32|40|48|56|64|[fd])(l?)|eof)$")
50+
_regex_edit_instr = re.compile("^(?:nop|setr|(set|(?:xn)?or|and|add|s?mul)(8|16|32|64|[fd])(l?)|drop|corrupt)$")
51+
52+
_edit_opcode_dict = {
53+
"nop": 0b00_00_000_0,
54+
"set": 0b00_00_001_0,
55+
"setr": 0b00_01_001_0,
56+
"and": 0b00_00_010_0,
57+
"or": 0b00_01_010_0,
58+
"xor": 0b00_10_010_0,
59+
"xnor": 0b00_11_010_0,
60+
"add": 0b00_00_011_0,
61+
"mul": 0b00_00_100_0,
62+
"smul": 0b00_10_100_0,
63+
"drop": 0b00_10_000_0,
64+
"corrupt": 0b00_01_000_0
65+
}
66+
4767
class FeatureBits(IntFlag):
4868
HAS_CMP_UNIT = 1
4969
HAS_EDIT_UNIT = 2
@@ -96,3 +116,230 @@ def receive_measurement(self, data):
96116
ext_data = ext_data[:-log_width] + ext_data[-log_width + ext_count % log_width:]
97117

98118
self.measurement_handler(self.id, (time, match_dir, match_mask, ext_data))
119+
120+
def load_script(self, path):
121+
comparator_instr = [(0, 0)] * self.max_script_size
122+
editor_instr = [(0, 0)] * self.max_script_size
123+
extr_instr = [0] * self.max_script_size
124+
125+
i = 0
126+
offset = 0
127+
in_section = 0
128+
129+
with open(path) as file_handle:
130+
for line in file_handle:
131+
try:
132+
line = line[:line.index("#")]
133+
except ValueError:
134+
pass
135+
136+
line = line.strip().lower().split()
137+
i += 1
138+
139+
if len(line) > 2:
140+
raise ValueError("line {0}: Invalid syntax".format(i))
141+
142+
if len(line) == 0:
143+
continue
144+
145+
instr = line[0]
146+
param = line[1] if len(line) == 2 else ""
147+
148+
if instr[0] == ".":
149+
try:
150+
in_section = ["", ".comp", ".extr", ".edit"].index(instr)
151+
except ValueError:
152+
raise ValueError("line {0}: Invalid section type: '{1}'".format(i, instr))
153+
154+
if len(line) == 2:
155+
try:
156+
offset = int(param)
157+
except ValueError:
158+
raise ValueError("line {0}: Invalid section offset: '{1}'".format(i, param))
159+
else:
160+
offset = 0
161+
162+
continue
163+
164+
if in_section == 0:
165+
raise ValueError("line {0}: Invalid instruction, not inside a section".format(i))
166+
167+
if in_section == 1:
168+
for index, opcode, param in self.__parse_comparator_instr(i, instr, param, offset):
169+
if isinstance(index, tuple):
170+
for j in range(index[0], index[1]):
171+
offset = j + 1
172+
comparator_instr[j] = (opcode, param)
173+
else:
174+
offset = index + 1
175+
comparator_instr[index] = (opcode, param)
176+
elif in_section == 2:
177+
begin, end, value = self.__parse_extractor_instr(i, instr, param, offset)
178+
179+
offset = end
180+
181+
for j in range(begin, end):
182+
extr_instr[j] = value
183+
else:
184+
for index, opcode, param in self.__parse_editor_instr(i, instr, param, offset):
185+
if isinstance(index, tuple):
186+
for j in range(index[0], index[1]):
187+
offset = j + 1
188+
editor_instr[j] = (opcode, param)
189+
else:
190+
offset = index + 1
191+
editor_instr[index] = (opcode, param)
192+
193+
res = b""
194+
195+
for i in range(self.max_script_size):
196+
res += encode_u8(comparator_instr[i][0])
197+
res += encode_u8(((editor_instr[i][0] >> 1) << 1) | (extr_instr[i] & 0b1))
198+
res += encode_u8(comparator_instr[i][1])
199+
res += encode_u8(editor_instr[i][1])
200+
201+
return res
202+
203+
def __parse_comparator_instr(self, i, instr, param, offset):
204+
match = FrameDetector._regex_comp_instr.match(instr)
205+
206+
if match == None:
207+
raise ValueError("line {0}: Unknown comparator instruction: '{1}'".format(i, instr))
208+
209+
if instr == "nop":
210+
return [ ((offset, offset + self.__parse_instr_len(i, offset, param)), 0, 0) ]
211+
elif instr == "eof":
212+
return [ (offset, 0b11110010, 0) ]
213+
else:
214+
if len(param) == 0:
215+
raise ValueError("line {0}: Instruction requires a parameter: '{1}'".format(i, instr))
216+
217+
base = match[1]
218+
op_size = match[2]
219+
endianness = match[3]
220+
221+
instr_param = b""
222+
instr_size = 0
223+
opcode = 0
224+
225+
if len(endianness) == 0:
226+
# Big-endian
227+
opcode |= 0b100
228+
229+
if base[0] == "s":
230+
# Signed
231+
opcode |= 0b1000
232+
base = base[1:]
233+
234+
try:
235+
if op_size == "f":
236+
instr_size = 4
237+
instr_param = encode_float(float(param))
238+
elif op_size == "d":
239+
instr_size = 8
240+
instr_param = encode_double(float(param))
241+
else:
242+
instr_size = int(op_size) // 8
243+
instr_param = int(param, 0).to_bytes(instr_size, byteorder="little", signed=bool(opcode & 0b1000))
244+
except ValueError:
245+
raise ValueError("line {0}: Invalid parameter for instruction: '{1}'".format(i, param))
246+
247+
opcode |= ["eq", "gt", "lt", "gtq", "ltq", "or", "and"].index(base) << 4
248+
249+
if offset + instr_size > self.max_script_size:
250+
raise ValueError("line {0}: Instruction ends beyond the script size limit: {1} + {2} > {3}".format(i, offset, instr_size, self.max_script_size))
251+
252+
if opcode & 0b100:
253+
instr_param = instr_param[::-1]
254+
255+
res = []
256+
257+
for b in instr_param[:-1]:
258+
res.append( (offset, opcode | 0b01, b) )
259+
offset += 1
260+
261+
res.append( (offset, opcode | 0b11, instr_param[-1]) )
262+
return res
263+
264+
def __parse_extractor_instr(self, i, instr, param, offset):
265+
if instr not in ["nop", "ext"]:
266+
raise ValueError("line {0}: Unknown extractor instruction: '{1}'".format(i, instr))
267+
268+
return (offset, offset + self.__parse_instr_len(i, offset, param), int(instr == "ext"))
269+
270+
def __parse_editor_instr(self, i, instr, param, offset):
271+
match = FrameDetector._regex_edit_instr.match(instr)
272+
273+
if match == None:
274+
raise ValueError("line {0}: Unknown editor instruction: '{1}'".format(i, instr))
275+
276+
if instr == "nop" or instr == "setr":
277+
return [ ((offset, offset + self.__parse_instr_len(i, offset, param)), FrameDetector._edit_opcode_dict[instr], 0) ]
278+
elif instr == "drop" or instr == "corrupt":
279+
return [ (offset, FrameDetector._edit_opcode_dict[instr], 0) ]
280+
else:
281+
if len(param) == 0:
282+
raise ValueError("line {0}: Instruction requires a parameter: '{1}'".format(i, instr))
283+
284+
base = match[1]
285+
op_size = match[2]
286+
endianness = match[3]
287+
288+
instr_param = b""
289+
instr_size = 0
290+
opcode = FrameDetector._edit_opcode_dict[base]
291+
292+
if len(endianness) == 0 and base != "set":
293+
# Big-endian
294+
opcode |= 0b10000
295+
296+
if base == "add" and param[0] == "-":
297+
# Set as signed, allows using negative parameters
298+
opcode |= 0b100000
299+
300+
try:
301+
if op_size == "f":
302+
instr_size = 4
303+
instr_param = encode_float(float(param))
304+
elif op_size == "d":
305+
instr_size = 8
306+
instr_param = encode_double(float(param))
307+
else:
308+
instr_size = int(op_size) // 8
309+
instr_param = int(param, 0).to_bytes(instr_size, byteorder="little", signed=bool(opcode & 0b100000))
310+
except ValueError:
311+
raise ValueError("line {0}: Invalid parameter for instruction: '{1}'".format(i, param))
312+
313+
if offset + instr_size > self.max_script_size:
314+
raise ValueError("line {0}: Instruction ends beyond the script size limit: {1} + {2} > {3}".format(i, offset, instr_size, self.max_script_size))
315+
316+
opcode |= int(math.log2(instr_size)) << 6
317+
318+
if opcode & 0b10000:
319+
instr_param = instr_param[::-1]
320+
321+
res = []
322+
323+
for b in instr_param[:-1]:
324+
res.append( (offset, 0, b) )
325+
offset += 1
326+
327+
res.append( (offset, opcode, instr_param[-1]) )
328+
return res
329+
330+
def __parse_instr_len(self, i, offset, param):
331+
if len(param):
332+
try:
333+
param = int(param, 0)
334+
except ValueError:
335+
raise ValueError("line {0}: Invalid parameter, must be an integer: '{1}'".format(i, param))
336+
else:
337+
param = 1
338+
339+
if param < 0:
340+
raise ValueError("line {0}: Invalid parameter, must be a positive integer: {1}".format(i, param))
341+
342+
if offset + param > self.max_script_size:
343+
raise ValueError("line {0}: Instruction ends beyond the script size limit: {1} + {2} > {3}".format(i, offset, param, self.max_script_size))
344+
345+
return param

0 commit comments

Comments
 (0)