ReticulumTelemetryHub 0.1.0__py3-none-any.whl → 0.143.0__py3-none-any.whl

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 (108) hide show
  1. reticulum_telemetry_hub/api/__init__.py +23 -0
  2. reticulum_telemetry_hub/api/models.py +323 -0
  3. reticulum_telemetry_hub/api/service.py +836 -0
  4. reticulum_telemetry_hub/api/storage.py +528 -0
  5. reticulum_telemetry_hub/api/storage_base.py +156 -0
  6. reticulum_telemetry_hub/api/storage_models.py +118 -0
  7. reticulum_telemetry_hub/atak_cot/__init__.py +49 -0
  8. reticulum_telemetry_hub/atak_cot/base.py +277 -0
  9. reticulum_telemetry_hub/atak_cot/chat.py +506 -0
  10. reticulum_telemetry_hub/atak_cot/detail.py +235 -0
  11. reticulum_telemetry_hub/atak_cot/event.py +181 -0
  12. reticulum_telemetry_hub/atak_cot/pytak_client.py +569 -0
  13. reticulum_telemetry_hub/atak_cot/tak_connector.py +848 -0
  14. reticulum_telemetry_hub/config/__init__.py +25 -0
  15. reticulum_telemetry_hub/config/constants.py +7 -0
  16. reticulum_telemetry_hub/config/manager.py +515 -0
  17. reticulum_telemetry_hub/config/models.py +215 -0
  18. reticulum_telemetry_hub/embedded_lxmd/__init__.py +5 -0
  19. reticulum_telemetry_hub/embedded_lxmd/embedded.py +418 -0
  20. reticulum_telemetry_hub/internal_api/__init__.py +21 -0
  21. reticulum_telemetry_hub/internal_api/bus.py +344 -0
  22. reticulum_telemetry_hub/internal_api/core.py +690 -0
  23. reticulum_telemetry_hub/internal_api/v1/__init__.py +74 -0
  24. reticulum_telemetry_hub/internal_api/v1/enums.py +109 -0
  25. reticulum_telemetry_hub/internal_api/v1/manifest.json +8 -0
  26. reticulum_telemetry_hub/internal_api/v1/schemas.py +478 -0
  27. reticulum_telemetry_hub/internal_api/versioning.py +63 -0
  28. reticulum_telemetry_hub/lxmf_daemon/Handlers.py +122 -0
  29. reticulum_telemetry_hub/lxmf_daemon/LXMF.py +252 -0
  30. reticulum_telemetry_hub/lxmf_daemon/LXMPeer.py +898 -0
  31. reticulum_telemetry_hub/lxmf_daemon/LXMRouter.py +4227 -0
  32. reticulum_telemetry_hub/lxmf_daemon/LXMessage.py +1006 -0
  33. reticulum_telemetry_hub/lxmf_daemon/LXStamper.py +490 -0
  34. reticulum_telemetry_hub/lxmf_daemon/__init__.py +10 -0
  35. reticulum_telemetry_hub/lxmf_daemon/_version.py +1 -0
  36. reticulum_telemetry_hub/lxmf_daemon/lxmd.py +1655 -0
  37. reticulum_telemetry_hub/lxmf_telemetry/model/fields/field_telemetry_stream.py +6 -0
  38. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/__init__.py +3 -0
  39. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/appearance.py +19 -19
  40. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/peer.py +17 -13
  41. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/__init__.py +65 -0
  42. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/acceleration.py +68 -0
  43. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/ambient_light.py +37 -0
  44. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/angular_velocity.py +68 -0
  45. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/battery.py +68 -0
  46. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/connection_map.py +258 -0
  47. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/generic.py +841 -0
  48. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/gravity.py +68 -0
  49. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/humidity.py +37 -0
  50. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/information.py +42 -0
  51. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/location.py +110 -0
  52. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/lxmf_propagation.py +429 -0
  53. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/magnetic_field.py +68 -0
  54. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/physical_link.py +53 -0
  55. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/pressure.py +37 -0
  56. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/proximity.py +37 -0
  57. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/received.py +75 -0
  58. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/rns_transport.py +209 -0
  59. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor.py +65 -0
  60. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_enum.py +27 -0
  61. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +58 -0
  62. reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/temperature.py +37 -0
  63. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/sensors/time.py +36 -32
  64. {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/telemeter.py +26 -23
  65. reticulum_telemetry_hub/lxmf_telemetry/sampler.py +229 -0
  66. reticulum_telemetry_hub/lxmf_telemetry/telemeter_manager.py +409 -0
  67. reticulum_telemetry_hub/lxmf_telemetry/telemetry_controller.py +804 -0
  68. reticulum_telemetry_hub/northbound/__init__.py +5 -0
  69. reticulum_telemetry_hub/northbound/app.py +195 -0
  70. reticulum_telemetry_hub/northbound/auth.py +119 -0
  71. reticulum_telemetry_hub/northbound/gateway.py +310 -0
  72. reticulum_telemetry_hub/northbound/internal_adapter.py +302 -0
  73. reticulum_telemetry_hub/northbound/models.py +213 -0
  74. reticulum_telemetry_hub/northbound/routes_chat.py +123 -0
  75. reticulum_telemetry_hub/northbound/routes_files.py +119 -0
  76. reticulum_telemetry_hub/northbound/routes_rest.py +345 -0
  77. reticulum_telemetry_hub/northbound/routes_subscribers.py +150 -0
  78. reticulum_telemetry_hub/northbound/routes_topics.py +178 -0
  79. reticulum_telemetry_hub/northbound/routes_ws.py +107 -0
  80. reticulum_telemetry_hub/northbound/serializers.py +72 -0
  81. reticulum_telemetry_hub/northbound/services.py +373 -0
  82. reticulum_telemetry_hub/northbound/websocket.py +855 -0
  83. reticulum_telemetry_hub/reticulum_server/__main__.py +2237 -0
  84. reticulum_telemetry_hub/reticulum_server/command_manager.py +1268 -0
  85. reticulum_telemetry_hub/reticulum_server/command_text.py +399 -0
  86. reticulum_telemetry_hub/reticulum_server/constants.py +1 -0
  87. reticulum_telemetry_hub/reticulum_server/event_log.py +357 -0
  88. reticulum_telemetry_hub/reticulum_server/internal_adapter.py +358 -0
  89. reticulum_telemetry_hub/reticulum_server/outbound_queue.py +312 -0
  90. reticulum_telemetry_hub/reticulum_server/services.py +422 -0
  91. reticulumtelemetryhub-0.143.0.dist-info/METADATA +181 -0
  92. reticulumtelemetryhub-0.143.0.dist-info/RECORD +97 -0
  93. {reticulumtelemetryhub-0.1.0.dist-info → reticulumtelemetryhub-0.143.0.dist-info}/WHEEL +1 -1
  94. reticulumtelemetryhub-0.143.0.dist-info/licenses/LICENSE +277 -0
  95. lxmf_telemetry/model/fields/field_telemetry_stream.py +0 -7
  96. lxmf_telemetry/model/persistance/__init__.py +0 -3
  97. lxmf_telemetry/model/persistance/sensors/location.py +0 -69
  98. lxmf_telemetry/model/persistance/sensors/magnetic_field.py +0 -36
  99. lxmf_telemetry/model/persistance/sensors/sensor.py +0 -44
  100. lxmf_telemetry/model/persistance/sensors/sensor_enum.py +0 -24
  101. lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +0 -9
  102. lxmf_telemetry/telemetry_controller.py +0 -124
  103. reticulum_server/main.py +0 -182
  104. reticulumtelemetryhub-0.1.0.dist-info/METADATA +0 -15
  105. reticulumtelemetryhub-0.1.0.dist-info/RECORD +0 -19
  106. {lxmf_telemetry → reticulum_telemetry_hub}/__init__.py +0 -0
  107. {lxmf_telemetry/model/persistance/sensors → reticulum_telemetry_hub/lxmf_telemetry}/__init__.py +0 -0
  108. {reticulum_server → reticulum_telemetry_hub/reticulum_server}/__init__.py +0 -0
@@ -0,0 +1,63 @@
1
+ """API version negotiation utilities for the internal contract."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from typing import Iterable
7
+ from typing import Optional
8
+
9
+ from reticulum_telemetry_hub.internal_api.v1.schemas import SUPPORTED_API_VERSION
10
+
11
+
12
+ _VERSION_PATTERN = re.compile(r"(\d+)\.(\d+)")
13
+
14
+
15
+ class ApiVersionError(ValueError):
16
+ """Raised when API version negotiation fails."""
17
+
18
+
19
+ def parse_api_version(value: str) -> tuple[int, int]:
20
+ """Parse a semantic API version string (major.minor)."""
21
+
22
+ match = _VERSION_PATTERN.fullmatch(value)
23
+ if not match:
24
+ raise ApiVersionError("Invalid API version format")
25
+ return int(match.group(1)), int(match.group(2))
26
+
27
+
28
+ def is_version_compatible(
29
+ candidate: str, *, supported: str = SUPPORTED_API_VERSION
30
+ ) -> bool:
31
+ """Return True when candidate is compatible with supported."""
32
+
33
+ try:
34
+ major, minor = parse_api_version(candidate)
35
+ supported_major, supported_minor = parse_api_version(supported)
36
+ except ApiVersionError:
37
+ return False
38
+ return major == supported_major and minor >= supported_minor
39
+
40
+
41
+ def negotiate_api_version(
42
+ peer_versions: Iterable[str], *, supported: str = SUPPORTED_API_VERSION
43
+ ) -> Optional[str]:
44
+ """Return the negotiated API version or None if incompatible."""
45
+
46
+ if any(is_version_compatible(version, supported=supported) for version in peer_versions):
47
+ return supported
48
+ return None
49
+
50
+
51
+ def select_api_version(
52
+ peer_versions: Optional[Iterable[str]] = None,
53
+ *,
54
+ supported: str = SUPPORTED_API_VERSION,
55
+ ) -> str:
56
+ """Select a compatible API version or raise ApiVersionError."""
57
+
58
+ if not peer_versions:
59
+ return supported
60
+ negotiated = negotiate_api_version(peer_versions, supported=supported)
61
+ if negotiated is None:
62
+ raise ApiVersionError("No compatible API version")
63
+ return negotiated
@@ -0,0 +1,122 @@
1
+ import time
2
+ import threading
3
+ import RNS
4
+ import RNS.vendor.umsgpack as msgpack
5
+
6
+ from .LXMF import APP_NAME, stamp_cost_from_app_data, pn_announce_data_is_valid
7
+ from .LXMessage import LXMessage
8
+
9
+
10
+ class LXMFDeliveryAnnounceHandler:
11
+ def __init__(self, lxmrouter):
12
+ self.aspect_filter = APP_NAME + ".delivery"
13
+ self.receive_path_responses = True
14
+ self.lxmrouter = lxmrouter
15
+
16
+ def received_announce(self, destination_hash, announced_identity, app_data):
17
+ for lxmessage in self.lxmrouter.pending_outbound:
18
+ if destination_hash == lxmessage.destination_hash:
19
+ if (
20
+ lxmessage.method == LXMessage.DIRECT
21
+ or lxmessage.method == LXMessage.OPPORTUNISTIC
22
+ ):
23
+ lxmessage.next_delivery_attempt = time.time()
24
+
25
+ def outbound_trigger():
26
+ while self.lxmrouter.processing_outbound:
27
+ time.sleep(0.1)
28
+ self.lxmrouter.process_outbound()
29
+
30
+ threading.Thread(target=outbound_trigger, daemon=True).start()
31
+
32
+ try:
33
+ stamp_cost = stamp_cost_from_app_data(app_data)
34
+ self.lxmrouter.update_stamp_cost(destination_hash, stamp_cost)
35
+
36
+ except Exception as e:
37
+ RNS.log(
38
+ f"An error occurred while trying to decode announced stamp cost. The contained exception was: {e}",
39
+ RNS.LOG_ERROR,
40
+ )
41
+
42
+
43
+ class LXMFPropagationAnnounceHandler:
44
+ def __init__(self, lxmrouter):
45
+ self.aspect_filter = APP_NAME + ".propagation"
46
+ self.receive_path_responses = True
47
+ self.lxmrouter = lxmrouter
48
+
49
+ def received_announce(
50
+ self,
51
+ destination_hash,
52
+ announced_identity,
53
+ app_data,
54
+ announce_packet_hash,
55
+ is_path_response,
56
+ ):
57
+ try:
58
+ if type(app_data) == bytes:
59
+ if self.lxmrouter.propagation_node:
60
+ if pn_announce_data_is_valid(app_data):
61
+ data = msgpack.unpackb(app_data)
62
+ node_timebase = int(data[1])
63
+ propagation_enabled = data[2]
64
+ propagation_transfer_limit = int(data[3])
65
+ propagation_sync_limit = int(data[4])
66
+ propagation_stamp_cost = int(data[5][0])
67
+ propagation_stamp_cost_flexibility = int(data[5][1])
68
+ peering_cost = int(data[5][2])
69
+ metadata = data[6]
70
+
71
+ if destination_hash in self.lxmrouter.static_peers:
72
+ static_peer = self.lxmrouter.peers[destination_hash]
73
+ if not is_path_response or static_peer.last_heard == 0:
74
+ self.lxmrouter.peer(
75
+ destination_hash=destination_hash,
76
+ timestamp=node_timebase,
77
+ propagation_transfer_limit=propagation_transfer_limit,
78
+ propagation_sync_limit=propagation_sync_limit,
79
+ propagation_stamp_cost=propagation_stamp_cost,
80
+ propagation_stamp_cost_flexibility=propagation_stamp_cost_flexibility,
81
+ peering_cost=peering_cost,
82
+ metadata=metadata,
83
+ )
84
+
85
+ else:
86
+ if self.lxmrouter.autopeer and not is_path_response:
87
+ if propagation_enabled == True:
88
+ if (
89
+ RNS.Transport.hops_to(destination_hash)
90
+ <= self.lxmrouter.autopeer_maxdepth
91
+ ):
92
+ self.lxmrouter.peer(
93
+ destination_hash=destination_hash,
94
+ timestamp=node_timebase,
95
+ propagation_transfer_limit=propagation_transfer_limit,
96
+ propagation_sync_limit=propagation_sync_limit,
97
+ propagation_stamp_cost=propagation_stamp_cost,
98
+ propagation_stamp_cost_flexibility=propagation_stamp_cost_flexibility,
99
+ peering_cost=peering_cost,
100
+ metadata=metadata,
101
+ )
102
+
103
+ else:
104
+ if destination_hash in self.lxmrouter.peers:
105
+ RNS.log(
106
+ f"Peer {self.lxmrouter.peers[destination_hash]} moved outside auto-peering range, breaking peering..."
107
+ )
108
+ self.lxmrouter.unpeer(
109
+ destination_hash, node_timebase
110
+ )
111
+
112
+ elif propagation_enabled == False:
113
+ self.lxmrouter.unpeer(
114
+ destination_hash, node_timebase
115
+ )
116
+
117
+ except Exception as e:
118
+ RNS.log(
119
+ "Error while evaluating propagation node announce, ignoring announce.",
120
+ RNS.LOG_DEBUG,
121
+ )
122
+ RNS.log(f"The contained exception was: {str(e)}", RNS.LOG_DEBUG)
@@ -0,0 +1,252 @@
1
+ import RNS
2
+ import RNS.vendor.umsgpack as msgpack
3
+
4
+ APP_NAME = "lxmf"
5
+
6
+ ##########################################################
7
+ # The following core fields are provided to facilitate #
8
+ # interoperability in data exchange between various LXMF #
9
+ # clients and systems. #
10
+ ##########################################################
11
+ FIELD_EMBEDDED_LXMS = 0x01
12
+ FIELD_TELEMETRY = 0x02
13
+ FIELD_TELEMETRY_STREAM = 0x03
14
+ FIELD_ICON_APPEARANCE = 0x04
15
+ FIELD_FILE_ATTACHMENTS = 0x05
16
+ FIELD_IMAGE = 0x06
17
+ FIELD_AUDIO = 0x07
18
+ FIELD_THREAD = 0x08
19
+ FIELD_COMMANDS = 0x09
20
+ FIELD_RESULTS = 0x0A
21
+ FIELD_GROUP = 0x0B
22
+ FIELD_TICKET = 0x0C
23
+ FIELD_EVENT = 0x0D
24
+ FIELD_RNR_REFS = 0x0E
25
+ FIELD_RENDERER = 0x0F
26
+
27
+ # For usecases such as including custom data structures,
28
+ # embedding or encapsulating other data types or protocols
29
+ # that are not native to LXMF, or bridging/tunneling
30
+ # external protocols or services over LXMF, the following
31
+ # fields are available. A format/type/protocol (or other)
32
+ # identifier can be included in the CUSTOM_TYPE field, and
33
+ # the embedded payload can be included in the CUSTOM_DATA
34
+ # field. It is up to the client application to correctly
35
+ # discern and potentially utilise any data embedded using
36
+ # this mechanism.
37
+ FIELD_CUSTOM_TYPE = 0xFB
38
+ FIELD_CUSTOM_DATA = 0xFC
39
+ FIELD_CUSTOM_META = 0xFD
40
+
41
+ # The non-specific and debug fields are intended for
42
+ # development, testing and debugging use.
43
+ FIELD_NON_SPECIFIC = 0xFE
44
+ FIELD_DEBUG = 0xFF
45
+
46
+ ##########################################################
47
+ # The following section lists field-specific specifiers, #
48
+ # modes and identifiers that are native to LXMF. It is #
49
+ # optional for any client or system to support any of #
50
+ # these, and they are provided as template for easing #
51
+ # interoperability without sacrificing expandability #
52
+ # and flexibility of the format. #
53
+ ##########################################################
54
+
55
+ # Audio modes for the data structure in FIELD_AUDIO
56
+
57
+ # Codec2 Audio Modes
58
+ AM_CODEC2_450PWB = 0x01
59
+ AM_CODEC2_450 = 0x02
60
+ AM_CODEC2_700C = 0x03
61
+ AM_CODEC2_1200 = 0x04
62
+ AM_CODEC2_1300 = 0x05
63
+ AM_CODEC2_1400 = 0x06
64
+ AM_CODEC2_1600 = 0x07
65
+ AM_CODEC2_2400 = 0x08
66
+ AM_CODEC2_3200 = 0x09
67
+
68
+ # Opus Audio Modes
69
+ AM_OPUS_OGG = 0x10
70
+ AM_OPUS_LBW = 0x11
71
+ AM_OPUS_MBW = 0x12
72
+ AM_OPUS_PTT = 0x13
73
+ AM_OPUS_RT_HDX = 0x14
74
+ AM_OPUS_RT_FDX = 0x15
75
+ AM_OPUS_STANDARD = 0x16
76
+ AM_OPUS_HQ = 0x17
77
+ AM_OPUS_BROADCAST = 0x18
78
+ AM_OPUS_LOSSLESS = 0x19
79
+
80
+ # Custom, unspecified audio mode, the client must
81
+ # determine it itself based on the included data.
82
+ AM_CUSTOM = 0xFF
83
+
84
+ # Message renderer specifications for FIELD_RENDERER.
85
+ # The renderer specification is completely optional,
86
+ # and only serves as an indication to the receiving
87
+ # client on how to render the message contents. It is
88
+ # not mandatory to implement, either on sending or
89
+ # receiving sides, but is the recommended way to
90
+ # signal how to render a message, if non-plaintext
91
+ # formatting is used.
92
+ RENDERER_PLAIN = 0x00
93
+ RENDERER_MICRON = 0x01
94
+ RENDERER_MARKDOWN = 0x02
95
+ RENDERER_BBCODE = 0x03
96
+
97
+ # Optional propagation node metadata fields. These
98
+ # fields may be highly unstable in allocation and
99
+ # availability until the version 1.0.0 release, so use
100
+ # at your own risk until then, and expect changes!
101
+ PN_META_VERSION = 0x00
102
+ PN_META_NAME = 0x01
103
+ PN_META_SYNC_STRATUM = 0x02
104
+ PN_META_SYNC_THROTTLE = 0x03
105
+ PN_META_AUTH_BAND = 0x04
106
+ PN_META_UTIL_PRESSURE = 0x05
107
+ PN_META_CUSTOM = 0xFF
108
+
109
+ ##########################################################
110
+ # The following helper functions makes it easier to #
111
+ # handle and operate on LXMF data in client programs #
112
+ ##########################################################
113
+
114
+
115
+ def display_name_from_app_data(app_data=None):
116
+ if app_data == None:
117
+ return None
118
+ elif len(app_data) == 0:
119
+ return None
120
+ else:
121
+ # Version 0.5.0+ announce format
122
+ if (app_data[0] >= 0x90 and app_data[0] <= 0x9F) or app_data[0] == 0xDC:
123
+ peer_data = msgpack.unpackb(app_data)
124
+ if type(peer_data) == list:
125
+ if len(peer_data) < 1:
126
+ return None
127
+ else:
128
+ dn = peer_data[0]
129
+ if dn == None:
130
+ return None
131
+ else:
132
+ try:
133
+ decoded = dn.decode("utf-8")
134
+ return decoded
135
+ except Exception as e:
136
+ RNS.log(
137
+ f"Could not decode display name in included announce data. The contained exception was: {e}",
138
+ RNS.LOG_ERROR,
139
+ )
140
+ return None
141
+
142
+ # Original announce format
143
+ else:
144
+ return app_data.decode("utf-8")
145
+
146
+
147
+ def stamp_cost_from_app_data(app_data=None):
148
+ if app_data == None or app_data == b"":
149
+ return None
150
+ else:
151
+ # Version 0.5.0+ announce format
152
+ if (app_data[0] >= 0x90 and app_data[0] <= 0x9F) or app_data[0] == 0xDC:
153
+ peer_data = msgpack.unpackb(app_data)
154
+ if type(peer_data) == list:
155
+ if len(peer_data) < 2:
156
+ return None
157
+ else:
158
+ return peer_data[1]
159
+
160
+ # Original announce format
161
+ else:
162
+ return None
163
+
164
+
165
+ def pn_name_from_app_data(app_data=None):
166
+ if app_data == None:
167
+ return None
168
+ else:
169
+ if pn_announce_data_is_valid(app_data):
170
+ data = msgpack.unpackb(app_data)
171
+ metadata = data[6]
172
+ if not PN_META_NAME in metadata:
173
+ return None
174
+ else:
175
+ try:
176
+ return metadata[PN_META_NAME].decode("utf-8")
177
+ except:
178
+ return None
179
+
180
+ return None
181
+
182
+
183
+ def pn_stamp_cost_from_app_data(app_data=None):
184
+ if app_data == None:
185
+ return None
186
+ else:
187
+ if pn_announce_data_is_valid(app_data):
188
+ data = msgpack.unpackb(app_data)
189
+ return data[5][0]
190
+ else:
191
+ return None
192
+
193
+
194
+ def pn_announce_data_is_valid(data):
195
+ try:
196
+ if type(data) != bytes:
197
+ return False
198
+ else:
199
+ data = msgpack.unpackb(data)
200
+ if len(data) < 7:
201
+ raise ValueError(
202
+ "Invalid announce data: Insufficient peer data, likely from deprecated LXMF version"
203
+ )
204
+ else:
205
+ try:
206
+ int(data[1])
207
+ except:
208
+ raise ValueError("Invalid announce data: Could not decode timebase")
209
+ if data[2] != True and data[2] != False:
210
+ raise ValueError(
211
+ "Invalid announce data: Indeterminate propagation node status"
212
+ )
213
+ try:
214
+ int(data[3])
215
+ except:
216
+ raise ValueError(
217
+ "Invalid announce data: Could not decode propagation transfer limit"
218
+ )
219
+ try:
220
+ int(data[4])
221
+ except:
222
+ raise ValueError(
223
+ "Invalid announce data: Could not decode propagation sync limit"
224
+ )
225
+ if type(data[5]) != list:
226
+ raise ValueError("Invalid announce data: Could not decode stamp costs")
227
+ try:
228
+ int(data[5][0])
229
+ except:
230
+ raise ValueError(
231
+ "Invalid announce data: Could not decode target stamp cost"
232
+ )
233
+ try:
234
+ int(data[5][1])
235
+ except:
236
+ raise ValueError(
237
+ "Invalid announce data: Could not decode stamp cost flexibility"
238
+ )
239
+ try:
240
+ int(data[5][2])
241
+ except:
242
+ raise ValueError("Invalid announce data: Could not decode peering cost")
243
+ if type(data[6]) != dict:
244
+ raise ValueError("Invalid announce data: Could not decode metadata")
245
+
246
+ except Exception as e:
247
+ RNS.log(
248
+ f"Could not validate propagation node announce data: {e}", RNS.LOG_DEBUG
249
+ )
250
+ return False
251
+
252
+ return True