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.
Files changed (69) hide show
  1. {meshcore-2.2.31 → meshcore-2.3.0}/PKG-INFO +1 -1
  2. {meshcore-2.2.31 → meshcore-2.3.0}/pyproject.toml +1 -1
  3. meshcore-2.3.0/src/meshcore/meshcore_parser.py +190 -0
  4. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/reader.py +23 -168
  5. {meshcore-2.2.31 → meshcore-2.3.0}/.github/python-test.yml +0 -0
  6. {meshcore-2.2.31 → meshcore-2.3.0}/.gitignore +0 -0
  7. {meshcore-2.2.31 → meshcore-2.3.0}/LICENSE +0 -0
  8. {meshcore-2.2.31 → meshcore-2.3.0}/README.md +0 -0
  9. {meshcore-2.2.31 → meshcore-2.3.0}/examples/.pubsub_example.py.swp +0 -0
  10. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_chat.py +0 -0
  11. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_pin_pairing_example.py +0 -0
  12. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_private_key_export.py +0 -0
  13. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_sign_example.py +0 -0
  14. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_stats.py +0 -0
  15. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_chan_msg.py +0 -0
  16. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_custom_vars.py +0 -0
  17. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_infos.py +0 -0
  18. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_msg.py +0 -0
  19. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_msg_retries.py +0 -0
  20. {meshcore-2.2.31 → meshcore-2.3.0}/examples/ble_t1000_set_cv.py +0 -0
  21. {meshcore-2.2.31 → meshcore-2.3.0}/examples/chan_recv_with_path.py +0 -0
  22. {meshcore-2.2.31 → meshcore-2.3.0}/examples/connection_events_example.py +0 -0
  23. {meshcore-2.2.31 → meshcore-2.3.0}/examples/mepo_mc_gps.py +0 -0
  24. {meshcore-2.2.31 → meshcore-2.3.0}/examples/pubsub_example.py +0 -0
  25. {meshcore-2.2.31 → meshcore-2.3.0}/examples/rf_packet_monitor.py +0 -0
  26. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_battery_monitor.py +0 -0
  27. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_channel_manager.py +0 -0
  28. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_chat.py +0 -0
  29. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_contacts.py +0 -0
  30. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_infos.py +0 -0
  31. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_meshcore_ollama.py +0 -0
  32. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_msg.py +0 -0
  33. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_pingbot.py +0 -0
  34. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_repeater_status.py +0 -0
  35. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_repeater_telemetry.py +0 -0
  36. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_rss_bot.py +0 -0
  37. {meshcore-2.2.31 → meshcore-2.3.0}/examples/serial_trace.py +0 -0
  38. {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_chat.py +0 -0
  39. {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_login_status.py +0 -0
  40. {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_contacts.py +0 -0
  41. {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_infos.py +0 -0
  42. {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_msg.py +0 -0
  43. {meshcore-2.2.31 → meshcore-2.3.0}/examples/tcp_mchome_readmsgs.py +0 -0
  44. {meshcore-2.2.31 → meshcore-2.3.0}/pytest.ini +0 -0
  45. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/__init__.py +0 -0
  46. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/ble_cx.py +0 -0
  47. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/__init__.py +0 -0
  48. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/base.py +0 -0
  49. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/binary.py +0 -0
  50. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/contact.py +0 -0
  51. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/control_data.py +0 -0
  52. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/device.py +0 -0
  53. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/commands/messaging.py +0 -0
  54. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/connection_manager.py +0 -0
  55. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/events.py +0 -0
  56. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/lpp_json_encoder.py +0 -0
  57. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/meshcore.py +0 -0
  58. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/packets.py +0 -0
  59. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/parsing.py +0 -0
  60. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/serial_cx.py +0 -0
  61. {meshcore-2.2.31 → meshcore-2.3.0}/src/meshcore/tcp_cx.py +0 -0
  62. {meshcore-2.2.31 → meshcore-2.3.0}/tests/README.md +0 -0
  63. {meshcore-2.2.31 → meshcore-2.3.0}/tests/test_ble_connection.py +0 -0
  64. {meshcore-2.2.31 → meshcore-2.3.0}/tests/test_ble_pin_pairing.py +0 -0
  65. {meshcore-2.2.31 → meshcore-2.3.0}/tests/test_meshcore_ble_pin.py +0 -0
  66. {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_commands.py +0 -0
  67. {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_events.py +0 -0
  68. {meshcore-2.2.31 → meshcore-2.3.0}/tests/unit/test_private_key_export.py +0 -0
  69. {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.2.31
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
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meshcore"
7
- version = "2.2.31"
7
+ version = "2.3.0"
8
8
  authors = [
9
9
  { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
10
  { name="Alex Wolden", email="awolden@gmail.com" },
@@ -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.Cipher import AES
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.channels = [{} for _ in range(40)] # keep our own copy of channels, 40 elements by default
28
+ self.packet_parser = MeshcorePacketParser()
33
29
  self.decrypt_channels = True
34
- self.channels_log = [] # stores the channel msg events
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 = next((l for l in reversed(self.channels_log) if 'msg_hash' in l and l['msg_hash'] == txt_hash), None)
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.channels[idx] = res
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
- # Parse payload and get some info from it
611
- if not payload is None:
612
- pbuf = io.BytesIO(payload)
613
-
614
- header = pbuf.read(1)[0]
615
- route_type = header & 0x03
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