meshcore 2.2.31__tar.gz → 2.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {meshcore-2.2.31 → meshcore-2.3.0}/PKG-INFO +1 -1
- {meshcore-2.2.31 → meshcore-2.3.0}/pyproject.toml +1 -1
- meshcore-2.3.0/src/meshcore/meshcore_parser.py +190 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/reader.py +23 -168
- {meshcore-2.2.31 → meshcore-2.3.0}/.github/python-test.yml +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/.gitignore +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/LICENSE +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/README.md +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/.pubsub_example.py.swp +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_chat.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_pin_pairing_example.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_private_key_export.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_sign_example.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_stats.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_chan_msg.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_custom_vars.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_infos.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_msg.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_msg_retries.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_set_cv.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/chan_recv_with_path.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/connection_events_example.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/mepo_mc_gps.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/pubsub_example.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/rf_packet_monitor.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_battery_monitor.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_channel_manager.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_chat.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_contacts.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_infos.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_meshcore_ollama.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_msg.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_pingbot.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_repeater_status.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_repeater_telemetry.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_rss_bot.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_trace.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_chat.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_login_status.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_contacts.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_infos.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_msg.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_readmsgs.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/pytest.ini +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/__init__.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/ble_cx.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/__init__.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/base.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/binary.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/contact.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/control_data.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/device.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/messaging.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/connection_manager.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/events.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/lpp_json_encoder.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/meshcore.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/packets.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/parsing.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/serial_cx.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/tcp_cx.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/README.md +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/test_ble_connection.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/test_ble_pin_pairing.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/test_meshcore_ble_pin.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_commands.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_events.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_private_key_export.py +0 -0
- {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_reader.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Base classes for communicating with meshcore companion radios
|
|
5
5
|
Project-URL: Homepage, https://github.com/fdlamotte/meshcore_py
|
|
6
6
|
Project-URL: Issues, https://github.com/fdlamotte/meshcore_py/issues
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import io
|
|
5
|
+
from typing import Any, Dict
|
|
6
|
+
from Crypto.Cipher import AES
|
|
7
|
+
from Crypto.Hash import HMAC, SHA256
|
|
8
|
+
|
|
9
|
+
logger = logging.getLogger("meshcore")
|
|
10
|
+
|
|
11
|
+
PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
|
|
12
|
+
ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
|
|
13
|
+
CONTACT_TYPENAMES = ["NONE","CLI","REP","ROOM","SENS"]
|
|
14
|
+
|
|
15
|
+
class MeshcorePacketParser:
|
|
16
|
+
def __init__(self):
|
|
17
|
+
self.channels = [{} for _ in range(40)] # keep our own copy of channels, 40 elements by default
|
|
18
|
+
self.channels_log = [] # stores the channel msg events
|
|
19
|
+
self.decrypt_channels = False
|
|
20
|
+
|
|
21
|
+
async def newChannel(self, chan):
|
|
22
|
+
idx = chan['channel_idx']
|
|
23
|
+
if len(self.channels) <= idx:
|
|
24
|
+
self.channels.extend([{} for _ in range(1 + idx - len(self.channels))])
|
|
25
|
+
self.channels[idx] = chan
|
|
26
|
+
|
|
27
|
+
async def findLogChannelMsg(self, msg_hash):
|
|
28
|
+
# search for the same packet
|
|
29
|
+
return next((l for l in reversed(self.channels_log) if 'msg_hash' in l and l['msg_hash'] == msg_hash), None)
|
|
30
|
+
|
|
31
|
+
async def findLogChannelPkt(self, pkt_hash):
|
|
32
|
+
# search for the same packet
|
|
33
|
+
return next((l for l in reversed(self.channels_log) if 'pkt_hash' in l and l['pkt_hash'] == pkt_hash), None)
|
|
34
|
+
|
|
35
|
+
async def parsePacketPayload(self, payload, log_data={}):
|
|
36
|
+
""" Parses the payload of a log_rx packet
|
|
37
|
+
|
|
38
|
+
Parameters :
|
|
39
|
+
log_data: already gathered log_data we'll expand
|
|
40
|
+
payload: payload of the packet to analyze
|
|
41
|
+
|
|
42
|
+
Returns :
|
|
43
|
+
completed log_data
|
|
44
|
+
"""
|
|
45
|
+
pbuf = io.BytesIO(payload)
|
|
46
|
+
|
|
47
|
+
header = pbuf.read(1)[0]
|
|
48
|
+
route_type = header & 0x03
|
|
49
|
+
payload_type = (header & 0x3c) >> 2
|
|
50
|
+
payload_ver = (header & 0xc0) >> 6
|
|
51
|
+
|
|
52
|
+
transport_code = None
|
|
53
|
+
if route_type == 0x00 or route_type == 0x03: # has transport code
|
|
54
|
+
transport_code = pbuf.read(4) # discard transport code
|
|
55
|
+
|
|
56
|
+
path_byte = pbuf.read(1)[0]
|
|
57
|
+
path_hash_size = ((path_byte & 0xC0) >> 6) + 1
|
|
58
|
+
path_len = (path_byte & 0x3F)
|
|
59
|
+
# here path_len is number of hops, not number of bytes
|
|
60
|
+
|
|
61
|
+
path = pbuf.read(path_len*path_hash_size).hex() # Beware of traces where pathes are mixed
|
|
62
|
+
|
|
63
|
+
try :
|
|
64
|
+
route_typename = ROUTE_TYPENAMES[route_type]
|
|
65
|
+
except IndexError:
|
|
66
|
+
logger.debug(f"Unknown route type {route_type}")
|
|
67
|
+
route_typename = "UNK"
|
|
68
|
+
|
|
69
|
+
try :
|
|
70
|
+
payload_typename = PAYLOAD_TYPENAMES[payload_type]
|
|
71
|
+
except IndexError:
|
|
72
|
+
logger.debug(f"Unknown payload type {payload_type}")
|
|
73
|
+
payload_typename = "UNK"
|
|
74
|
+
|
|
75
|
+
pkt_payload = pbuf.read()
|
|
76
|
+
pkt_hash = int.from_bytes(SHA256.new(pkt_payload).digest()[0:4], "little", signed=False)
|
|
77
|
+
|
|
78
|
+
log_data["header"] = header
|
|
79
|
+
log_data["route_type"] = route_type
|
|
80
|
+
log_data["route_typename"] = route_typename
|
|
81
|
+
log_data["payload_type"] = payload_type
|
|
82
|
+
log_data["payload_typename"]= payload_typename
|
|
83
|
+
|
|
84
|
+
log_data["payload_ver"] = payload_ver
|
|
85
|
+
|
|
86
|
+
if not transport_code is None:
|
|
87
|
+
log_data["transport_code"] = transport_code.hex()
|
|
88
|
+
|
|
89
|
+
log_data["path_len"] = path_len
|
|
90
|
+
log_data["path_hash_size"] = path_hash_size
|
|
91
|
+
log_data["path"] = path
|
|
92
|
+
|
|
93
|
+
log_data["pkt_payload"] = pkt_payload
|
|
94
|
+
log_data["pkt_hash"] = pkt_hash
|
|
95
|
+
|
|
96
|
+
if not payload is None and payload_type == 0x05: # flood msg / channel
|
|
97
|
+
pk_buf = io.BytesIO(pkt_payload)
|
|
98
|
+
chan_hash = pk_buf.read(1).hex()
|
|
99
|
+
cipher_mac = pk_buf.read(2)
|
|
100
|
+
msg = pk_buf.read() # until the end of buffer
|
|
101
|
+
|
|
102
|
+
channel = None
|
|
103
|
+
for c in self.channels:
|
|
104
|
+
if "channel_hash" in c and c["channel_hash"] == chan_hash : # validate against MAC
|
|
105
|
+
h = HMAC.new(c["channel_secret"], digestmod=SHA256)
|
|
106
|
+
h.update(msg)
|
|
107
|
+
if h.digest()[0:2] == cipher_mac:
|
|
108
|
+
channel = c
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
chan_name = ""
|
|
112
|
+
|
|
113
|
+
if channel is None :
|
|
114
|
+
chan_name = chan_hash
|
|
115
|
+
else:
|
|
116
|
+
chan_name = channel["channel_name"]
|
|
117
|
+
|
|
118
|
+
log_data["chan_hash"] = chan_hash
|
|
119
|
+
log_data["cipher_mac"] = cipher_mac.hex()
|
|
120
|
+
log_data["crypted"] = msg.hex()
|
|
121
|
+
log_data["chan_name"] = chan_name
|
|
122
|
+
|
|
123
|
+
if not channel is None and self.decrypt_channels:
|
|
124
|
+
|
|
125
|
+
logged = await self.findLogChannelPkt(pkt_hash)
|
|
126
|
+
|
|
127
|
+
if logged is None:
|
|
128
|
+
# not found: decrypt the text and hash it
|
|
129
|
+
aes_key = channel["channel_secret"]
|
|
130
|
+
cipher = AES.new(aes_key, AES.MODE_ECB)
|
|
131
|
+
uncrypted = cipher.decrypt(msg)
|
|
132
|
+
timestamp = int.from_bytes(uncrypted[0:4], "little", signed=False)
|
|
133
|
+
attempt = uncrypted[4] & 3
|
|
134
|
+
txt_type = int.from_bytes(uncrypted[4:4], "little", signed=False) >> 2
|
|
135
|
+
message = uncrypted[5:].strip(b"\0")
|
|
136
|
+
msg_hash = int.from_bytes(SHA256.new(timestamp.to_bytes(4, "little", signed=False) + message).digest()[0:4], "little", signed=False)
|
|
137
|
+
log_data["message"] = message.decode("utf-8", "ignore")
|
|
138
|
+
log_data["msg_hash"] = msg_hash
|
|
139
|
+
log_data["sender_timestamp"] = timestamp
|
|
140
|
+
log_data["attempt"] = attempt
|
|
141
|
+
log_data["txt_type"] = txt_type
|
|
142
|
+
else:
|
|
143
|
+
# found: copy
|
|
144
|
+
log_data["message"] = logged["message"]
|
|
145
|
+
log_data["msg_hash"] = logged["msg_hash"]
|
|
146
|
+
log_data["sender_timestamp"] = logged["sender_timestamp"]
|
|
147
|
+
log_data["attempt"] = logged["attempt"]
|
|
148
|
+
log_data["txt_type"] = logged["txt_type"]
|
|
149
|
+
|
|
150
|
+
self.channels_log.append(log_data)
|
|
151
|
+
if len(self.channels_log) > 100:
|
|
152
|
+
del self.channels_log[:25]
|
|
153
|
+
|
|
154
|
+
elif not payload is None and payload_type == 0x04: # Advert
|
|
155
|
+
pk_buf = io.BytesIO(pkt_payload)
|
|
156
|
+
adv_key = pk_buf.read(32).hex()
|
|
157
|
+
adv_timestamp = int.from_bytes(pk_buf.read(4), "little", signed=False)
|
|
158
|
+
signature = pk_buf.read(64).hex()
|
|
159
|
+
flags = pk_buf.read(1)[0]
|
|
160
|
+
adv_type = flags & 0x0F
|
|
161
|
+
adv_lat = None
|
|
162
|
+
adv_lon = None
|
|
163
|
+
adv_feat1 = None
|
|
164
|
+
adv_feat2 = None
|
|
165
|
+
if flags & 0x10 > 0: #has location
|
|
166
|
+
adv_lat = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
|
|
167
|
+
adv_lon = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
|
|
168
|
+
if flags & 0x20 > 0: #has feature1
|
|
169
|
+
adv_feat1 = pk_buf.read(2).hex()
|
|
170
|
+
if flags & 0x40 > 0: #has feature2
|
|
171
|
+
adv_feat2 = pk_buf.read(2).hex()
|
|
172
|
+
if flags & 0x80 > 0: #has name
|
|
173
|
+
adv_name = pk_buf.read().decode("utf-8", "ignore").strip("\x00")
|
|
174
|
+
log_data["adv_name"] = adv_name
|
|
175
|
+
|
|
176
|
+
log_data["adv_key"] = adv_key
|
|
177
|
+
log_data["adv_timestamp"] = adv_timestamp
|
|
178
|
+
log_data["signature"] = signature
|
|
179
|
+
log_data["adv_flags"] = flags
|
|
180
|
+
log_data["adv_type"] = adv_type
|
|
181
|
+
if not adv_lat is None :
|
|
182
|
+
log_data["adv_lat"] = adv_lat
|
|
183
|
+
if not adv_lon is None :
|
|
184
|
+
log_data["adv_lon"] = adv_lon
|
|
185
|
+
if not adv_feat1 is None:
|
|
186
|
+
log_data["adv_feat1"] = adv_feat1
|
|
187
|
+
if not adv_feat2 is None:
|
|
188
|
+
log_data["adv_feat2"] = adv_feat2
|
|
189
|
+
|
|
190
|
+
return log_data
|
|
@@ -5,19 +5,15 @@ import time
|
|
|
5
5
|
import io
|
|
6
6
|
from typing import Any, Dict
|
|
7
7
|
from .events import Event, EventType, EventDispatcher, ErrorMessages
|
|
8
|
+
from .meshcore_parser import MeshcorePacketParser
|
|
8
9
|
from .packets import BinaryReqType, PacketType, ControlType
|
|
9
10
|
from .parsing import lpp_parse, lpp_parse_mma, parse_acl, parse_status
|
|
10
11
|
from cayennelpp import LppFrame, LppData
|
|
11
12
|
from meshcore.lpp_json_encoder import lpp_json_encoder
|
|
12
|
-
from Crypto.
|
|
13
|
-
from Crypto.Hash import HMAC, SHA256
|
|
13
|
+
from Crypto.Hash import SHA256
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger("meshcore")
|
|
16
16
|
|
|
17
|
-
PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
|
|
18
|
-
ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
|
|
19
|
-
CONTACT_TYPENAMES = ["NONE","CLI","REP","ROOM","SENS"]
|
|
20
|
-
|
|
21
17
|
class MessageReader:
|
|
22
18
|
def __init__(self, dispatcher: EventDispatcher):
|
|
23
19
|
self.dispatcher = dispatcher
|
|
@@ -29,9 +25,16 @@ class MessageReader:
|
|
|
29
25
|
# Track pending binary requests by tag for proper response parsing
|
|
30
26
|
self.pending_binary_requests: Dict[str, Dict[str, Any]] = {} # tag -> {request_type, expires_at}
|
|
31
27
|
|
|
32
|
-
self.
|
|
28
|
+
self.packet_parser = MeshcorePacketParser()
|
|
33
29
|
self.decrypt_channels = True
|
|
34
|
-
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def decrypt_channels(self):
|
|
33
|
+
return self.packet_parser.decrypt_channels
|
|
34
|
+
|
|
35
|
+
@decrypt_channels.setter
|
|
36
|
+
def decrypt_channels(self, value):
|
|
37
|
+
self.packet_parser.decrypt_channels = value
|
|
35
38
|
|
|
36
39
|
def register_binary_request(self, prefix: str, tag: str, request_type: BinaryReqType, timeout_seconds: float, context={}, is_anon=False):
|
|
37
40
|
"""Register a pending binary request for proper response parsing"""
|
|
@@ -266,7 +269,7 @@ class MessageReader:
|
|
|
266
269
|
# search for text in log_channels
|
|
267
270
|
txt_hash = int.from_bytes(SHA256.new(res["sender_timestamp"].to_bytes(4, "little", signed=False)+text).digest()[0:4], "little", signed=False)
|
|
268
271
|
if self.decrypt_channels:
|
|
269
|
-
logged =
|
|
272
|
+
logged = await self.packet_parser.findLogChannelMsg(txt_hash)
|
|
270
273
|
if not logged is None:
|
|
271
274
|
res["path"] = logged["path"]
|
|
272
275
|
res["RSSI"] = logged["rssi"]
|
|
@@ -302,11 +305,11 @@ class MessageReader:
|
|
|
302
305
|
res["text"] = text.decode("utf-8", "ignore")
|
|
303
306
|
|
|
304
307
|
# search for text in log_channels
|
|
305
|
-
txt_hash = int.from_bytes(SHA256.new(res["sender_timestamp"].to_bytes(4, "little", signed=False)+text).digest()[0:4], "little", signed=False)
|
|
306
|
-
res["txt_hash"] = txt_hash
|
|
307
|
-
logged = next((l for l in reversed(self.channels_log) if 'msg_hash' in l and l['msg_hash'] == txt_hash), None)
|
|
308
|
-
|
|
309
308
|
if self.decrypt_channels:
|
|
309
|
+
txt_hash = int.from_bytes(SHA256.new(res["sender_timestamp"].to_bytes(4, "little", signed=False)+text).digest()[0:4], "little", signed=False)
|
|
310
|
+
res["txt_hash"] = txt_hash
|
|
311
|
+
logged = await self.packet_parser.findLogChannelMsg(txt_hash)
|
|
312
|
+
|
|
310
313
|
if not logged is None:
|
|
311
314
|
res["path"] = logged["path"]
|
|
312
315
|
res["RSSI"] = logged["rssi"]
|
|
@@ -476,9 +479,6 @@ class MessageReader:
|
|
|
476
479
|
idx = dbuf.read(1)[0]
|
|
477
480
|
res["channel_idx"] = idx
|
|
478
481
|
|
|
479
|
-
if len(self.channels) <= idx:
|
|
480
|
-
self.channels.extend([{} for _ in range(1 + idx - len(self.channels))])
|
|
481
|
-
|
|
482
482
|
# Channel name is null-terminated, so find the first null byte
|
|
483
483
|
name_bytes = dbuf.read(32)
|
|
484
484
|
null_pos = name_bytes.find(0)
|
|
@@ -490,7 +490,7 @@ class MessageReader:
|
|
|
490
490
|
res["channel_secret"] = dbuf.read(16)
|
|
491
491
|
res["channel_hash"] = SHA256.new(res["channel_secret"]).hexdigest()[0:2]
|
|
492
492
|
|
|
493
|
-
self.
|
|
493
|
+
await self.packet_parser.newChannel(res)
|
|
494
494
|
|
|
495
495
|
await self.dispatcher.dispatch(Event(EventType.CHANNEL_INFO, res, res))
|
|
496
496
|
|
|
@@ -607,157 +607,12 @@ class MessageReader:
|
|
|
607
607
|
log_data["payload"] = payload.hex()
|
|
608
608
|
log_data["payload_length"] = len(payload)
|
|
609
609
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
payload_type = (header & 0x3c) >> 2
|
|
617
|
-
payload_ver = (header & 0xc0) >> 6
|
|
618
|
-
|
|
619
|
-
transport_code = None
|
|
620
|
-
if route_type == 0x00 or route_type == 0x03: # has transport code
|
|
621
|
-
transport_code = pbuf.read(4) # discard transport code
|
|
622
|
-
|
|
623
|
-
path_byte = pbuf.read(1)[0]
|
|
624
|
-
path_hash_size = ((path_byte & 0xC0) >> 6) + 1
|
|
625
|
-
path_len = (path_byte & 0x3F)
|
|
626
|
-
# here path_len is number of hops, not number of bytes
|
|
627
|
-
|
|
628
|
-
path = pbuf.read(path_len*path_hash_size).hex() # Beware of traces where pathes are mixed
|
|
629
|
-
|
|
630
|
-
try :
|
|
631
|
-
route_typename = ROUTE_TYPENAMES[route_type]
|
|
632
|
-
except IndexError:
|
|
633
|
-
logger.debug(f"Unknown route type {route_type}")
|
|
634
|
-
route_typename = "UNK"
|
|
635
|
-
|
|
636
|
-
try :
|
|
637
|
-
payload_typename = PAYLOAD_TYPENAMES[payload_type]
|
|
638
|
-
except IndexError:
|
|
639
|
-
logger.debug(f"Unknown payload type {payload_type}")
|
|
640
|
-
payload_typename = "UNK"
|
|
641
|
-
|
|
642
|
-
pkt_payload = pbuf.read()
|
|
643
|
-
pkt_hash = int.from_bytes(SHA256.new(pkt_payload).digest()[0:4], "little", signed=False)
|
|
644
|
-
|
|
645
|
-
log_data["header"] = header
|
|
646
|
-
log_data["route_type"] = route_type
|
|
647
|
-
attributes["route_type"] = route_type
|
|
648
|
-
log_data["route_typename"] = route_typename
|
|
649
|
-
log_data["payload_type"] = payload_type
|
|
650
|
-
attributes["payload_type"] = payload_type
|
|
651
|
-
log_data["payload_typename"]= payload_typename
|
|
652
|
-
|
|
653
|
-
log_data["payload_ver"] = payload_ver
|
|
654
|
-
|
|
655
|
-
if not transport_code is None:
|
|
656
|
-
log_data["transport_code"] = transport_code.hex()
|
|
657
|
-
|
|
658
|
-
log_data["path_len"] = path_len
|
|
659
|
-
attributes["path_len"] = path_len
|
|
660
|
-
log_data["path_hash_size"] = path_hash_size
|
|
661
|
-
log_data["path"] = path
|
|
662
|
-
attributes["path"] = path
|
|
663
|
-
|
|
664
|
-
log_data["pkt_payload"] = pkt_payload
|
|
665
|
-
log_data["pkt_hash"] = pkt_hash
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
if not payload is None and payload_type == 0x05: # flood msg / channel
|
|
669
|
-
pk_buf = io.BytesIO(pkt_payload)
|
|
670
|
-
chan_hash = pk_buf.read(1).hex()
|
|
671
|
-
cipher_mac = pk_buf.read(2)
|
|
672
|
-
msg = pk_buf.read() # until the end of buffer
|
|
673
|
-
|
|
674
|
-
channel = None
|
|
675
|
-
for c in self.channels:
|
|
676
|
-
if "channel_hash" in c and c["channel_hash"] == chan_hash : # validate against MAC
|
|
677
|
-
h = HMAC.new(c["channel_secret"], digestmod=SHA256)
|
|
678
|
-
h.update(msg)
|
|
679
|
-
if h.digest()[0:2] == cipher_mac:
|
|
680
|
-
channel = c
|
|
681
|
-
break
|
|
682
|
-
|
|
683
|
-
chan_name = ""
|
|
684
|
-
|
|
685
|
-
if channel is None :
|
|
686
|
-
chan_name = chan_hash
|
|
687
|
-
else:
|
|
688
|
-
chan_name = channel["channel_name"]
|
|
689
|
-
|
|
690
|
-
log_data["chan_hash"] = chan_hash
|
|
691
|
-
log_data["cipher_mac"] = cipher_mac.hex()
|
|
692
|
-
log_data["crypted"] = msg.hex()
|
|
693
|
-
log_data["chan_name"] = chan_name
|
|
694
|
-
|
|
695
|
-
if not channel is None and self.decrypt_channels:
|
|
696
|
-
# search for the same packet
|
|
697
|
-
logged = next((l for l in reversed(self.channels_log) if 'pkt_hash' in l and l['pkt_hash'] == pkt_hash), None)
|
|
698
|
-
|
|
699
|
-
if logged is None:
|
|
700
|
-
# not found: decrypt the text and hash it
|
|
701
|
-
aes_key = channel["channel_secret"]
|
|
702
|
-
cipher = AES.new(aes_key, AES.MODE_ECB)
|
|
703
|
-
uncrypted = cipher.decrypt(msg)
|
|
704
|
-
timestamp = int.from_bytes(uncrypted[0:4], "little", signed=False)
|
|
705
|
-
attempt = uncrypted[4] & 3
|
|
706
|
-
txt_type = int.from_bytes(uncrypted[4:4], "little", signed=False) >> 2
|
|
707
|
-
message = uncrypted[5:].strip(b"\0")
|
|
708
|
-
msg_hash = int.from_bytes(SHA256.new(timestamp.to_bytes(4, "little", signed=False) + message).digest()[0:4], "little", signed=False)
|
|
709
|
-
log_data["message"] = message.decode("utf-8", "ignore")
|
|
710
|
-
log_data["msg_hash"] = msg_hash
|
|
711
|
-
log_data["sender_timestamp"] = timestamp
|
|
712
|
-
log_data["attempt"] = attempt
|
|
713
|
-
log_data["txt_type"] = txt_type
|
|
714
|
-
else:
|
|
715
|
-
# found: copy
|
|
716
|
-
log_data["message"] = logged["message"]
|
|
717
|
-
log_data["msg_hash"] = logged["msg_hash"]
|
|
718
|
-
log_data["sender_timestamp"] = logged["sender_timestamp"]
|
|
719
|
-
log_data["attempt"] = logged["attempt"]
|
|
720
|
-
log_data["txt_type"] = logged["txt_type"]
|
|
721
|
-
|
|
722
|
-
self.channels_log.append(log_data)
|
|
723
|
-
if len(self.channels_log) > 100:
|
|
724
|
-
del self.channels_log[:25]
|
|
725
|
-
|
|
726
|
-
elif not payload is None and payload_type == 0x04: # Advert
|
|
727
|
-
pk_buf = io.BytesIO(pkt_payload)
|
|
728
|
-
adv_key = pk_buf.read(32).hex()
|
|
729
|
-
adv_timestamp = int.from_bytes(pk_buf.read(4), "little", signed=False)
|
|
730
|
-
signature = pk_buf.read(64).hex()
|
|
731
|
-
flags = pk_buf.read(1)[0]
|
|
732
|
-
adv_type = flags & 0x0F
|
|
733
|
-
adv_lat = None
|
|
734
|
-
adv_lon = None
|
|
735
|
-
adv_feat1 = None
|
|
736
|
-
adv_feat2 = None
|
|
737
|
-
if flags & 0x10 > 0: #has location
|
|
738
|
-
adv_lat = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
|
|
739
|
-
adv_lon = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
|
|
740
|
-
if flags & 0x20 > 0: #has feature1
|
|
741
|
-
adv_feat1 = pk_buf.read(2).hex()
|
|
742
|
-
if flags & 0x40 > 0: #has feature2
|
|
743
|
-
adv_feat2 = pk_buf.read(2).hex()
|
|
744
|
-
if flags & 0x80 > 0: #has name
|
|
745
|
-
adv_name = pk_buf.read().decode("utf-8", "ignore").strip("\x00")
|
|
746
|
-
log_data["adv_name"] = adv_name
|
|
747
|
-
|
|
748
|
-
log_data["adv_key"] = adv_key
|
|
749
|
-
log_data["adv_timestamp"] = adv_timestamp
|
|
750
|
-
log_data["signature"] = signature
|
|
751
|
-
log_data["adv_flags"] = flags
|
|
752
|
-
log_data["adv_type"] = adv_type
|
|
753
|
-
if not adv_lat is None :
|
|
754
|
-
log_data["adv_lat"] = adv_lat
|
|
755
|
-
if not adv_lon is None :
|
|
756
|
-
log_data["adv_lon"] = adv_lon
|
|
757
|
-
if not adv_feat1 is None:
|
|
758
|
-
log_data["adv_feat1"] = adv_feat1
|
|
759
|
-
if not adv_feat2 is None:
|
|
760
|
-
log_data["adv_feat2"] = adv_feat2
|
|
610
|
+
# Parse payload and get some info from it
|
|
611
|
+
log_data = await self.packet_parser.parsePacketPayload(payload, log_data)
|
|
612
|
+
attributes['route_type'] = log_data['route_type']
|
|
613
|
+
attributes['payload_type'] = log_data['payload_type']
|
|
614
|
+
attributes['path_len'] = log_data['path_len']
|
|
615
|
+
attributes['path'] = log_data['path']
|
|
761
616
|
|
|
762
617
|
# Dispatch as RF log data
|
|
763
618
|
await self.dispatcher.dispatch(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|