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.
- reticulum_telemetry_hub/api/__init__.py +23 -0
- reticulum_telemetry_hub/api/models.py +323 -0
- reticulum_telemetry_hub/api/service.py +836 -0
- reticulum_telemetry_hub/api/storage.py +528 -0
- reticulum_telemetry_hub/api/storage_base.py +156 -0
- reticulum_telemetry_hub/api/storage_models.py +118 -0
- reticulum_telemetry_hub/atak_cot/__init__.py +49 -0
- reticulum_telemetry_hub/atak_cot/base.py +277 -0
- reticulum_telemetry_hub/atak_cot/chat.py +506 -0
- reticulum_telemetry_hub/atak_cot/detail.py +235 -0
- reticulum_telemetry_hub/atak_cot/event.py +181 -0
- reticulum_telemetry_hub/atak_cot/pytak_client.py +569 -0
- reticulum_telemetry_hub/atak_cot/tak_connector.py +848 -0
- reticulum_telemetry_hub/config/__init__.py +25 -0
- reticulum_telemetry_hub/config/constants.py +7 -0
- reticulum_telemetry_hub/config/manager.py +515 -0
- reticulum_telemetry_hub/config/models.py +215 -0
- reticulum_telemetry_hub/embedded_lxmd/__init__.py +5 -0
- reticulum_telemetry_hub/embedded_lxmd/embedded.py +418 -0
- reticulum_telemetry_hub/internal_api/__init__.py +21 -0
- reticulum_telemetry_hub/internal_api/bus.py +344 -0
- reticulum_telemetry_hub/internal_api/core.py +690 -0
- reticulum_telemetry_hub/internal_api/v1/__init__.py +74 -0
- reticulum_telemetry_hub/internal_api/v1/enums.py +109 -0
- reticulum_telemetry_hub/internal_api/v1/manifest.json +8 -0
- reticulum_telemetry_hub/internal_api/v1/schemas.py +478 -0
- reticulum_telemetry_hub/internal_api/versioning.py +63 -0
- reticulum_telemetry_hub/lxmf_daemon/Handlers.py +122 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMF.py +252 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMPeer.py +898 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMRouter.py +4227 -0
- reticulum_telemetry_hub/lxmf_daemon/LXMessage.py +1006 -0
- reticulum_telemetry_hub/lxmf_daemon/LXStamper.py +490 -0
- reticulum_telemetry_hub/lxmf_daemon/__init__.py +10 -0
- reticulum_telemetry_hub/lxmf_daemon/_version.py +1 -0
- reticulum_telemetry_hub/lxmf_daemon/lxmd.py +1655 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/fields/field_telemetry_stream.py +6 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/__init__.py +3 -0
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/appearance.py +19 -19
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/peer.py +17 -13
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/__init__.py +65 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/acceleration.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/ambient_light.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/angular_velocity.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/battery.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/connection_map.py +258 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/generic.py +841 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/gravity.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/humidity.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/information.py +42 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/location.py +110 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/lxmf_propagation.py +429 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/magnetic_field.py +68 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/physical_link.py +53 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/pressure.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/proximity.py +37 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/received.py +75 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/rns_transport.py +209 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor.py +65 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_enum.py +27 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +58 -0
- reticulum_telemetry_hub/lxmf_telemetry/model/persistance/sensors/temperature.py +37 -0
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/sensors/time.py +36 -32
- {lxmf_telemetry → reticulum_telemetry_hub/lxmf_telemetry}/model/persistance/telemeter.py +26 -23
- reticulum_telemetry_hub/lxmf_telemetry/sampler.py +229 -0
- reticulum_telemetry_hub/lxmf_telemetry/telemeter_manager.py +409 -0
- reticulum_telemetry_hub/lxmf_telemetry/telemetry_controller.py +804 -0
- reticulum_telemetry_hub/northbound/__init__.py +5 -0
- reticulum_telemetry_hub/northbound/app.py +195 -0
- reticulum_telemetry_hub/northbound/auth.py +119 -0
- reticulum_telemetry_hub/northbound/gateway.py +310 -0
- reticulum_telemetry_hub/northbound/internal_adapter.py +302 -0
- reticulum_telemetry_hub/northbound/models.py +213 -0
- reticulum_telemetry_hub/northbound/routes_chat.py +123 -0
- reticulum_telemetry_hub/northbound/routes_files.py +119 -0
- reticulum_telemetry_hub/northbound/routes_rest.py +345 -0
- reticulum_telemetry_hub/northbound/routes_subscribers.py +150 -0
- reticulum_telemetry_hub/northbound/routes_topics.py +178 -0
- reticulum_telemetry_hub/northbound/routes_ws.py +107 -0
- reticulum_telemetry_hub/northbound/serializers.py +72 -0
- reticulum_telemetry_hub/northbound/services.py +373 -0
- reticulum_telemetry_hub/northbound/websocket.py +855 -0
- reticulum_telemetry_hub/reticulum_server/__main__.py +2237 -0
- reticulum_telemetry_hub/reticulum_server/command_manager.py +1268 -0
- reticulum_telemetry_hub/reticulum_server/command_text.py +399 -0
- reticulum_telemetry_hub/reticulum_server/constants.py +1 -0
- reticulum_telemetry_hub/reticulum_server/event_log.py +357 -0
- reticulum_telemetry_hub/reticulum_server/internal_adapter.py +358 -0
- reticulum_telemetry_hub/reticulum_server/outbound_queue.py +312 -0
- reticulum_telemetry_hub/reticulum_server/services.py +422 -0
- reticulumtelemetryhub-0.143.0.dist-info/METADATA +181 -0
- reticulumtelemetryhub-0.143.0.dist-info/RECORD +97 -0
- {reticulumtelemetryhub-0.1.0.dist-info → reticulumtelemetryhub-0.143.0.dist-info}/WHEEL +1 -1
- reticulumtelemetryhub-0.143.0.dist-info/licenses/LICENSE +277 -0
- lxmf_telemetry/model/fields/field_telemetry_stream.py +0 -7
- lxmf_telemetry/model/persistance/__init__.py +0 -3
- lxmf_telemetry/model/persistance/sensors/location.py +0 -69
- lxmf_telemetry/model/persistance/sensors/magnetic_field.py +0 -36
- lxmf_telemetry/model/persistance/sensors/sensor.py +0 -44
- lxmf_telemetry/model/persistance/sensors/sensor_enum.py +0 -24
- lxmf_telemetry/model/persistance/sensors/sensor_mapping.py +0 -9
- lxmf_telemetry/telemetry_controller.py +0 -124
- reticulum_server/main.py +0 -182
- reticulumtelemetryhub-0.1.0.dist-info/METADATA +0 -15
- reticulumtelemetryhub-0.1.0.dist-info/RECORD +0 -19
- {lxmf_telemetry → reticulum_telemetry_hub}/__init__.py +0 -0
- {lxmf_telemetry/model/persistance/sensors → reticulum_telemetry_hub/lxmf_telemetry}/__init__.py +0 -0
- {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
|