Skip to content

Commit ab8cd55

Browse files
committed
[reassembler] add 'libacars' mode to parse with external lib
1 parent 5fd01de commit ab8cd55

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed

iridiumtk/reassembler/sbd.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,55 @@ def parity7(data):
432432

433433
print(out, file=outfile)
434434

435+
436+
class ReassembleIDASBDlibACARS(ReassembleIDASBD):
437+
def __init__(self):
438+
global libacars, la_msg_dir
439+
from libacars import libacars, la_msg_dir
440+
super().__init__()
441+
442+
def consume_l2(self, q):
443+
if len(q.data) <= 2: # No contents
444+
return
445+
446+
if q.data[0] != 1: # prelim. check for ACARS
447+
return
448+
449+
if q.ul:
450+
d = la_msg_dir.LA_MSG_DIR_AIR2GND
451+
else:
452+
d = la_msg_dir.LA_MSG_DIR_GND2AIR
453+
454+
o = libacars(q.data, d, q.time)
455+
456+
if o.is_err() and 'showerrs' not in config.args:
457+
return
458+
459+
if o.is_ping() and 'nopings' in config.args:
460+
return
461+
462+
if 'json' in config.args:
463+
print(o.json())
464+
return
465+
466+
q.timestamp = dt.epoch(q.time).isoformat(timespec='seconds')
467+
print(q.timestamp, end=" ")
468+
469+
if q.ul:
470+
print("UL", end=" ")
471+
else:
472+
print("DL", end=" ")
473+
474+
if q.data[1] == 0x3: # header of unknown meaning
475+
print("[hdr: %s]"%q.data[1:9].hex(), end=" ")
476+
477+
if o.is_interesting():
478+
print("INTERESTING", end=" ")
479+
print(o)
480+
481+
435482
modes=[
436483
["sbd", ReassembleIDASBD, ('perfect', 'debug') ],
437484
["acars", ReassembleIDASBDACARS, ('json', 'perfect', 'showerrs', 'nopings') ],
485+
["libacars", ReassembleIDASBDlibACARS, ('json', 'perfect', 'showerrs', 'nopings') ],
438486
]

libacars.py

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
#!/usr/bin/env python3
2+
3+
from ctypes import c_void_p, c_char_p, c_char, c_int, c_size_t, c_bool, c_long
4+
from ctypes import Structure, POINTER, CDLL, cast
5+
from enum import IntEnum, auto
6+
7+
8+
class CtypesEnum(IntEnum):
9+
"""A ctypes-compatible IntEnum superclass."""
10+
@classmethod
11+
def from_param(cls, obj):
12+
return int(obj)
13+
14+
15+
class la_msg_dir(CtypesEnum):
16+
LA_MSG_DIR_UNKNOWN = 0
17+
LA_MSG_DIR_GND2AIR = auto()
18+
LA_MSG_DIR_AIR2GND = auto()
19+
20+
21+
class la_reasm_status(CtypesEnum):
22+
LA_REASM_UNKNOWN = 0
23+
LA_REASM_COMPLETE = auto()
24+
LA_REASM_IN_PROGRESS = auto()
25+
LA_REASM_SKIPPED = auto()
26+
LA_REASM_DUPLICATE = auto()
27+
LA_REASM_FRAG_OUT_OF_SEQUENCE = auto()
28+
LA_REASM_ARGS_INVALID = auto()
29+
30+
31+
class timeval(Structure):
32+
_fields_ = [
33+
("tv_sec", c_long),
34+
("tv_usec", c_long)
35+
]
36+
37+
38+
class la_type_descriptor(Structure):
39+
_fields_ = [
40+
("format_text", c_void_p),
41+
("destroy", c_void_p),
42+
("format_json", c_void_p),
43+
("json_key", c_char_p),
44+
]
45+
46+
47+
class la_acars_msg(Structure):
48+
_fields_ = [
49+
("crc_ok", c_bool),
50+
("err", c_bool),
51+
("final_block", c_bool),
52+
("mode", c_char),
53+
("reg", c_char * 8),
54+
("ack", c_char),
55+
("label", c_char * 3),
56+
("sublabel", c_char * 3),
57+
("mfi", c_char * 3),
58+
("block_id", c_char),
59+
("msg_num", c_char * 4),
60+
("msg_num_seq", c_char),
61+
("flight_id", c_char * 7),
62+
("reasm_status", c_int),
63+
# ...
64+
]
65+
66+
67+
class la_proto_node(Structure):
68+
pass
69+
70+
71+
la_proto_node._fields_ = [
72+
("td", POINTER(la_type_descriptor)),
73+
("data", c_void_p),
74+
("next", POINTER(la_proto_node)),
75+
# ...
76+
]
77+
78+
79+
class la_vstr(Structure):
80+
_fields_ = [
81+
("str", c_char_p),
82+
("len", c_size_t),
83+
("allocated_size", c_size_t),
84+
]
85+
86+
87+
class libacars:
88+
lib = CDLL("libacars-2.so")
89+
90+
version = cast(lib.LA_VERSION, POINTER(c_char_p)).contents.value.decode("ascii")
91+
92+
lib.la_acars_parse.restype = POINTER(la_proto_node)
93+
lib.la_acars_parse.argtypes = [c_char_p, c_size_t, la_msg_dir]
94+
95+
lib.la_acars_parse_and_reassemble.restype = POINTER(la_proto_node)
96+
lib.la_acars_parse_and_reassemble.argtypes = [c_char_p, c_size_t, la_msg_dir, c_void_p, timeval]
97+
98+
lib.la_proto_tree_format_text.restype = POINTER(la_vstr)
99+
lib.la_proto_tree_format_text.argtypes = [c_void_p, POINTER(la_proto_node)]
100+
101+
lib.la_proto_tree_format_json.restype = POINTER(la_vstr)
102+
lib.la_proto_tree_format_json.argtypes = [c_void_p, POINTER(la_proto_node)]
103+
104+
lib.la_vstring_destroy.restype = None
105+
lib.la_vstring_destroy.argtypes = [POINTER(la_vstr), c_bool]
106+
107+
lib.la_proto_tree_destroy.restype = None
108+
lib.la_proto_tree_destroy.argtypes = [POINTER(la_proto_node)]
109+
110+
lib.la_reasm_ctx_new.restype = c_void_p
111+
lib.la_reasm_ctx_new.argtypes = []
112+
113+
ctx = lib.la_reasm_ctx_new()
114+
115+
def __init__(self, data, direction=la_msg_dir.LA_MSG_DIR_UNKNOWN, time=None):
116+
x = data[1:]
117+
if x[0] == 3: # Cut (unknown) iridium header
118+
x = x[8:]
119+
if time is None:
120+
self.p = libacars.lib.la_acars_parse(x, len(x), direction)
121+
else:
122+
self.p = libacars.lib.la_acars_parse_and_reassemble(x, len(x), direction, libacars.ctx, timeval(int(time), int(time-int(time))*1000000))
123+
124+
def json(self):
125+
vstr = libacars.lib.la_proto_tree_format_json(None, self.p)
126+
rv = vstr.contents.str.decode("ascii")
127+
libacars.lib.la_vstring_destroy(vstr, True)
128+
return rv
129+
130+
def is_err(self):
131+
if self.p.contents.td.contents.json_key != b"acars":
132+
return False
133+
return cast(self.p.contents.data, POINTER(la_acars_msg)).contents.err
134+
135+
def is_ping(self):
136+
if self.p.contents.td.contents.json_key != b"acars":
137+
return False
138+
if cast(self.p.contents.data, POINTER(la_acars_msg)).contents.err:
139+
return False
140+
return cast(self.p.contents.data, POINTER(la_acars_msg)).contents.label in (b'_d', b'Q0')
141+
142+
def is_reasm(self):
143+
if self.p.contents.td.contents.json_key != b"acars":
144+
return False
145+
if cast(self.p.contents.data, POINTER(la_acars_msg)).contents.err:
146+
return False
147+
return cast(self.p.contents.data, POINTER(la_acars_msg)).contents.reasm_status == la_reasm_status.LA_REASM_IN_PROGRESS
148+
149+
def is_interesting(self):
150+
if self.p.contents.td.contents.json_key != b"acars":
151+
return True
152+
if self.p.contents.next:
153+
return True
154+
else:
155+
return False
156+
157+
def debug(self):
158+
print(self.p.contents.td, ":", self.p.contents.td.contents.json_key)
159+
print("err:", cast(self.p.contents.data, POINTER(la_acars_msg)).contents.err)
160+
if self.p.contents.next:
161+
print("More contents")
162+
print(self.p.contents.next.contents)
163+
else:
164+
print("No more contents")
165+
166+
def __str__(self):
167+
vstr = libacars.lib.la_proto_tree_format_text(None, self.p)
168+
rv = vstr.contents.str.decode("ascii")
169+
libacars.lib.la_vstring_destroy(vstr, True)
170+
return rv
171+
172+
def __del__(self):
173+
libacars.lib.la_proto_tree_destroy(self.p)
174+
175+
176+
if __name__ == '__main__':
177+
import sys
178+
print(f"LibACARS version {libacars.version}\n")
179+
180+
if len(sys.argv) <= 1:
181+
print("No arguments.", file=sys.stderr)
182+
exit(1)
183+
184+
if len(sys.argv) == 2:
185+
data = bytes.fromhex(sys.argv[1])
186+
o = libacars(data, la_msg_dir.LA_MSG_DIR_UNKNOWN)
187+
print(o)
188+
print("JSON:")
189+
print(o.json())
190+
191+
else:
192+
print("With reassembly:")
193+
194+
for i, d in enumerate(sys.argv[1:]):
195+
data = bytes.fromhex(d)
196+
o = libacars(data, la_msg_dir.LA_MSG_DIR_UNKNOWN, i)
197+
if o.is_err():
198+
print(f"Couldn't parse #{1+i}")
199+
continue
200+
201+
if o.is_reasm():
202+
continue
203+
204+
print(f"#{1+i}", o)
205+
206+
print("All done")

0 commit comments

Comments
 (0)