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,1006 @@
|
|
|
1
|
+
import RNS
|
|
2
|
+
import RNS.vendor.umsgpack as msgpack
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
import base64
|
|
7
|
+
import multiprocessing
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
from . import LXStamper
|
|
11
|
+
from .LXMF import APP_NAME
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LXMessage:
|
|
15
|
+
GENERATING = 0x00
|
|
16
|
+
OUTBOUND = 0x01
|
|
17
|
+
SENDING = 0x02
|
|
18
|
+
SENT = 0x04
|
|
19
|
+
DELIVERED = 0x08
|
|
20
|
+
REJECTED = 0xFD
|
|
21
|
+
CANCELLED = 0xFE
|
|
22
|
+
FAILED = 0xFF
|
|
23
|
+
states = [
|
|
24
|
+
GENERATING,
|
|
25
|
+
OUTBOUND,
|
|
26
|
+
SENDING,
|
|
27
|
+
SENT,
|
|
28
|
+
DELIVERED,
|
|
29
|
+
REJECTED,
|
|
30
|
+
CANCELLED,
|
|
31
|
+
FAILED,
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
UNKNOWN = 0x00
|
|
35
|
+
PACKET = 0x01
|
|
36
|
+
RESOURCE = 0x02
|
|
37
|
+
representations = [UNKNOWN, PACKET, RESOURCE]
|
|
38
|
+
|
|
39
|
+
OPPORTUNISTIC = 0x01
|
|
40
|
+
DIRECT = 0x02
|
|
41
|
+
PROPAGATED = 0x03
|
|
42
|
+
PAPER = 0x05
|
|
43
|
+
valid_methods = [OPPORTUNISTIC, DIRECT, PROPAGATED, PAPER]
|
|
44
|
+
|
|
45
|
+
SOURCE_UNKNOWN = 0x01
|
|
46
|
+
SIGNATURE_INVALID = 0x02
|
|
47
|
+
unverified_reasons = [SOURCE_UNKNOWN, SIGNATURE_INVALID]
|
|
48
|
+
|
|
49
|
+
DESTINATION_LENGTH = RNS.Identity.TRUNCATED_HASHLENGTH // 8
|
|
50
|
+
SIGNATURE_LENGTH = RNS.Identity.SIGLENGTH // 8
|
|
51
|
+
TICKET_LENGTH = RNS.Identity.TRUNCATED_HASHLENGTH // 8
|
|
52
|
+
|
|
53
|
+
# Default ticket expiry is 3 weeks, with an
|
|
54
|
+
# additional grace period of 5 days, allowing
|
|
55
|
+
# for timekeeping inaccuracies. Tickets will
|
|
56
|
+
# automatically renew when there is less than
|
|
57
|
+
# 14 days to expiry.
|
|
58
|
+
TICKET_EXPIRY = 21 * 24 * 60 * 60
|
|
59
|
+
TICKET_GRACE = 5 * 24 * 60 * 60
|
|
60
|
+
TICKET_RENEW = 14 * 24 * 60 * 60
|
|
61
|
+
TICKET_INTERVAL = 1 * 24 * 60 * 60
|
|
62
|
+
COST_TICKET = 0x100
|
|
63
|
+
|
|
64
|
+
# LXMF overhead is 112 bytes per message:
|
|
65
|
+
# 16 bytes for destination hash
|
|
66
|
+
# 16 bytes for source hash
|
|
67
|
+
# 64 bytes for Ed25519 signature
|
|
68
|
+
# 8 bytes for timestamp
|
|
69
|
+
# 8 bytes for msgpack structure
|
|
70
|
+
TIMESTAMP_SIZE = 8
|
|
71
|
+
STRUCT_OVERHEAD = 8
|
|
72
|
+
LXMF_OVERHEAD = (
|
|
73
|
+
2 * DESTINATION_LENGTH + SIGNATURE_LENGTH + TIMESTAMP_SIZE + STRUCT_OVERHEAD
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# With an MTU of 500, the maximum amount of data
|
|
77
|
+
# we can send in a single encrypted packet is
|
|
78
|
+
# 391 bytes.
|
|
79
|
+
ENCRYPTED_PACKET_MDU = RNS.Packet.ENCRYPTED_MDU + TIMESTAMP_SIZE
|
|
80
|
+
|
|
81
|
+
# The max content length we can fit in LXMF message
|
|
82
|
+
# inside a single RNS packet is the encrypted MDU, minus
|
|
83
|
+
# the LXMF overhead. We can optimise a bit though, by
|
|
84
|
+
# inferring the destination hash from the destination
|
|
85
|
+
# field of the packet, therefore we also add the length
|
|
86
|
+
# of a destination hash to the calculation. With default
|
|
87
|
+
# RNS and LXMF parameters, the largest single-packet
|
|
88
|
+
# LXMF message we can send is 295 bytes. If a message
|
|
89
|
+
# is larger than that, a Reticulum link will be used.
|
|
90
|
+
ENCRYPTED_PACKET_MAX_CONTENT = (
|
|
91
|
+
ENCRYPTED_PACKET_MDU - LXMF_OVERHEAD + DESTINATION_LENGTH
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
# Links can carry a larger MDU, due to less overhead per
|
|
95
|
+
# packet. The link MDU with default Reticulum parameters
|
|
96
|
+
# is 431 bytes.
|
|
97
|
+
LINK_PACKET_MDU = RNS.Link.MDU
|
|
98
|
+
|
|
99
|
+
# Which means that we can deliver single-packet LXMF
|
|
100
|
+
# messages with content of up to 319 bytes over a link.
|
|
101
|
+
# If a message is larger than that, LXMF will sequence
|
|
102
|
+
# and transfer it as a RNS resource over the link instead.
|
|
103
|
+
LINK_PACKET_MAX_CONTENT = LINK_PACKET_MDU - LXMF_OVERHEAD
|
|
104
|
+
|
|
105
|
+
# For plain packets without encryption, we can
|
|
106
|
+
# fit up to 368 bytes of content.
|
|
107
|
+
PLAIN_PACKET_MDU = RNS.Packet.PLAIN_MDU
|
|
108
|
+
PLAIN_PACKET_MAX_CONTENT = PLAIN_PACKET_MDU - LXMF_OVERHEAD + DESTINATION_LENGTH
|
|
109
|
+
|
|
110
|
+
# Descriptive strings regarding transport encryption
|
|
111
|
+
ENCRYPTION_DESCRIPTION_AES = "AES-128"
|
|
112
|
+
ENCRYPTION_DESCRIPTION_EC = "Curve25519"
|
|
113
|
+
ENCRYPTION_DESCRIPTION_UNENCRYPTED = "Unencrypted"
|
|
114
|
+
|
|
115
|
+
# Constants for QR/URI encoding LXMs
|
|
116
|
+
URI_SCHEMA = "lxm"
|
|
117
|
+
QR_ERROR_CORRECTION = "ERROR_CORRECT_L"
|
|
118
|
+
QR_MAX_STORAGE = 2953
|
|
119
|
+
PAPER_MDU = ((QR_MAX_STORAGE - (len(URI_SCHEMA) + len("://"))) * 6) // 8
|
|
120
|
+
|
|
121
|
+
def __str__(self):
|
|
122
|
+
if self.hash != None:
|
|
123
|
+
return "<LXMessage " + RNS.hexrep(self.hash, delimit=False) + ">"
|
|
124
|
+
else:
|
|
125
|
+
return "<LXMessage>"
|
|
126
|
+
|
|
127
|
+
def __init__(
|
|
128
|
+
self,
|
|
129
|
+
destination,
|
|
130
|
+
source,
|
|
131
|
+
content="",
|
|
132
|
+
title="",
|
|
133
|
+
fields=None,
|
|
134
|
+
desired_method=None,
|
|
135
|
+
destination_hash=None,
|
|
136
|
+
source_hash=None,
|
|
137
|
+
stamp_cost=None,
|
|
138
|
+
include_ticket=False,
|
|
139
|
+
):
|
|
140
|
+
|
|
141
|
+
if isinstance(destination, RNS.Destination) or destination == None:
|
|
142
|
+
self.__destination = destination
|
|
143
|
+
if destination != None:
|
|
144
|
+
self.destination_hash = destination.hash
|
|
145
|
+
else:
|
|
146
|
+
self.destination_hash = destination_hash
|
|
147
|
+
else:
|
|
148
|
+
raise ValueError("LXMessage initialised with invalid destination")
|
|
149
|
+
|
|
150
|
+
if isinstance(source, RNS.Destination) or source == None:
|
|
151
|
+
self.__source = source
|
|
152
|
+
if source != None:
|
|
153
|
+
self.source_hash = source.hash
|
|
154
|
+
else:
|
|
155
|
+
self.source_hash = source_hash
|
|
156
|
+
else:
|
|
157
|
+
raise ValueError("LXMessage initialised with invalid source")
|
|
158
|
+
|
|
159
|
+
if title == None:
|
|
160
|
+
title = ""
|
|
161
|
+
|
|
162
|
+
if type(title) == bytes:
|
|
163
|
+
self.set_title_from_bytes(title)
|
|
164
|
+
else:
|
|
165
|
+
self.set_title_from_string(title)
|
|
166
|
+
|
|
167
|
+
if type(content) == bytes:
|
|
168
|
+
self.set_content_from_bytes(content)
|
|
169
|
+
else:
|
|
170
|
+
self.set_content_from_string(content)
|
|
171
|
+
|
|
172
|
+
self.set_fields(fields)
|
|
173
|
+
|
|
174
|
+
self.payload = None
|
|
175
|
+
self.timestamp = None
|
|
176
|
+
self.signature = None
|
|
177
|
+
self.hash = None
|
|
178
|
+
self.transient_id = None
|
|
179
|
+
self.packed = None
|
|
180
|
+
self.state = LXMessage.GENERATING
|
|
181
|
+
self.method = LXMessage.UNKNOWN
|
|
182
|
+
self.progress = 0.0
|
|
183
|
+
self.rssi = None
|
|
184
|
+
self.snr = None
|
|
185
|
+
self.q = None
|
|
186
|
+
|
|
187
|
+
self.stamp = None
|
|
188
|
+
self.stamp_cost = stamp_cost
|
|
189
|
+
self.stamp_value = None
|
|
190
|
+
self.stamp_valid = False
|
|
191
|
+
self.stamp_checked = False
|
|
192
|
+
self.propagation_stamp = None
|
|
193
|
+
self.propagation_stamp_value = None
|
|
194
|
+
self.propagation_stamp_valid = False
|
|
195
|
+
self.propagation_target_cost = None
|
|
196
|
+
self.defer_stamp = True
|
|
197
|
+
self.defer_propagation_stamp = True
|
|
198
|
+
self.outbound_ticket = None
|
|
199
|
+
self.include_ticket = include_ticket
|
|
200
|
+
|
|
201
|
+
self.propagation_packed = None
|
|
202
|
+
self.paper_packed = None
|
|
203
|
+
|
|
204
|
+
self.incoming = False
|
|
205
|
+
self.signature_validated = False
|
|
206
|
+
self.unverified_reason = None
|
|
207
|
+
self.ratchet_id = None
|
|
208
|
+
|
|
209
|
+
self.representation = LXMessage.UNKNOWN
|
|
210
|
+
self.desired_method = desired_method
|
|
211
|
+
self.delivery_attempts = 0
|
|
212
|
+
self.transport_encrypted = False
|
|
213
|
+
self.transport_encryption = None
|
|
214
|
+
self.ratchet_id = None
|
|
215
|
+
self.packet_representation = None
|
|
216
|
+
self.resource_representation = None
|
|
217
|
+
self.__delivery_destination = None
|
|
218
|
+
self.__delivery_callback = None
|
|
219
|
+
self.__pn_encrypted_data = None
|
|
220
|
+
self.failed_callback = None
|
|
221
|
+
|
|
222
|
+
self.deferred_stamp_generating = False
|
|
223
|
+
|
|
224
|
+
def set_title_from_string(self, title_string):
|
|
225
|
+
self.title = title_string.encode("utf-8")
|
|
226
|
+
|
|
227
|
+
def set_title_from_bytes(self, title_bytes):
|
|
228
|
+
self.title = title_bytes
|
|
229
|
+
|
|
230
|
+
def title_as_string(self):
|
|
231
|
+
return self.title.decode("utf-8")
|
|
232
|
+
|
|
233
|
+
def set_content_from_string(self, content_string):
|
|
234
|
+
self.content = content_string.encode("utf-8")
|
|
235
|
+
|
|
236
|
+
def set_content_from_bytes(self, content_bytes):
|
|
237
|
+
self.content = content_bytes
|
|
238
|
+
|
|
239
|
+
def content_as_string(self):
|
|
240
|
+
try:
|
|
241
|
+
return self.content.decode("utf-8")
|
|
242
|
+
except Exception as e:
|
|
243
|
+
RNS.log(f"{self} could not decode message content as string: {e}")
|
|
244
|
+
return None
|
|
245
|
+
|
|
246
|
+
def set_fields(self, fields):
|
|
247
|
+
if isinstance(fields, dict) or fields == None:
|
|
248
|
+
self.fields = fields or {}
|
|
249
|
+
else:
|
|
250
|
+
raise ValueError('LXMessage property "fields" can only be dict or None')
|
|
251
|
+
|
|
252
|
+
def get_fields(self):
|
|
253
|
+
return self.fields
|
|
254
|
+
|
|
255
|
+
@property
|
|
256
|
+
def destination(self):
|
|
257
|
+
return self.__destination
|
|
258
|
+
|
|
259
|
+
@destination.setter
|
|
260
|
+
def destination(self, destination):
|
|
261
|
+
self.set_destination(destination)
|
|
262
|
+
|
|
263
|
+
def get_destination(self):
|
|
264
|
+
return self.destination
|
|
265
|
+
|
|
266
|
+
def set_destination(self, destination):
|
|
267
|
+
if self.destination == None:
|
|
268
|
+
if isinstance(destination, RNS.Destination):
|
|
269
|
+
self.__destination = destination
|
|
270
|
+
else:
|
|
271
|
+
raise ValueError("Invalid destination set on LXMessage")
|
|
272
|
+
else:
|
|
273
|
+
raise ValueError("Cannot reassign destination on LXMessage")
|
|
274
|
+
|
|
275
|
+
@property
|
|
276
|
+
def source(self):
|
|
277
|
+
return self.__source
|
|
278
|
+
|
|
279
|
+
@source.setter
|
|
280
|
+
def source(self, source):
|
|
281
|
+
self.set_source(source)
|
|
282
|
+
|
|
283
|
+
def get_source(self):
|
|
284
|
+
return self.source
|
|
285
|
+
|
|
286
|
+
def set_source(self, source):
|
|
287
|
+
if self.source == None:
|
|
288
|
+
if isinstance(source, RNS.Destination):
|
|
289
|
+
self.__source = source
|
|
290
|
+
else:
|
|
291
|
+
raise ValueError("Invalid source set on LXMessage")
|
|
292
|
+
else:
|
|
293
|
+
raise ValueError("Cannot reassign source on LXMessage")
|
|
294
|
+
|
|
295
|
+
def set_delivery_destination(self, delivery_destination):
|
|
296
|
+
self.__delivery_destination = delivery_destination
|
|
297
|
+
|
|
298
|
+
def register_delivery_callback(self, callback):
|
|
299
|
+
self.__delivery_callback = callback
|
|
300
|
+
|
|
301
|
+
def register_failed_callback(self, callback):
|
|
302
|
+
self.failed_callback = callback
|
|
303
|
+
|
|
304
|
+
def validate_stamp(self, target_cost, tickets=None):
|
|
305
|
+
if tickets != None:
|
|
306
|
+
for ticket in tickets:
|
|
307
|
+
try:
|
|
308
|
+
if self.stamp == RNS.Identity.truncated_hash(
|
|
309
|
+
ticket + self.message_id
|
|
310
|
+
):
|
|
311
|
+
RNS.log(
|
|
312
|
+
f"Stamp on {self} validated by inbound ticket",
|
|
313
|
+
RNS.LOG_DEBUG,
|
|
314
|
+
) # TODO: Remove at some point
|
|
315
|
+
self.stamp_value = LXMessage.COST_TICKET
|
|
316
|
+
return True
|
|
317
|
+
except Exception as e:
|
|
318
|
+
RNS.log(f"Error while validating ticket: {e}", RNS.LOG_ERROR)
|
|
319
|
+
RNS.trace_exception(e)
|
|
320
|
+
|
|
321
|
+
if self.stamp == None:
|
|
322
|
+
return False
|
|
323
|
+
else:
|
|
324
|
+
workblock = LXStamper.stamp_workblock(self.message_id)
|
|
325
|
+
if LXStamper.stamp_valid(self.stamp, target_cost, workblock):
|
|
326
|
+
RNS.log(
|
|
327
|
+
f"Stamp on {self} validated", RNS.LOG_DEBUG
|
|
328
|
+
) # TODO: Remove at some point
|
|
329
|
+
self.stamp_value = LXStamper.stamp_value(workblock, self.stamp)
|
|
330
|
+
return True
|
|
331
|
+
else:
|
|
332
|
+
return False
|
|
333
|
+
|
|
334
|
+
def get_stamp(self, timeout=None):
|
|
335
|
+
# If an outbound ticket exists, use this for
|
|
336
|
+
# generating a valid stamp.
|
|
337
|
+
if (
|
|
338
|
+
self.outbound_ticket != None
|
|
339
|
+
and type(self.outbound_ticket) == bytes
|
|
340
|
+
and len(self.outbound_ticket) == LXMessage.TICKET_LENGTH
|
|
341
|
+
):
|
|
342
|
+
generated_stamp = RNS.Identity.truncated_hash(
|
|
343
|
+
self.outbound_ticket + self.message_id
|
|
344
|
+
)
|
|
345
|
+
self.stamp_value = LXMessage.COST_TICKET
|
|
346
|
+
RNS.log(
|
|
347
|
+
f"Generated stamp with outbound ticket {RNS.hexrep(self.outbound_ticket)} for {self}",
|
|
348
|
+
RNS.LOG_DEBUG,
|
|
349
|
+
) # TODO: Remove at some point
|
|
350
|
+
return generated_stamp
|
|
351
|
+
|
|
352
|
+
# If no stamp cost is required, we can just
|
|
353
|
+
# return immediately.
|
|
354
|
+
elif self.stamp_cost == None:
|
|
355
|
+
self.stamp_value = None
|
|
356
|
+
return None
|
|
357
|
+
|
|
358
|
+
# If a stamp was already generated, return
|
|
359
|
+
# it immediately.
|
|
360
|
+
elif self.stamp != None:
|
|
361
|
+
return self.stamp
|
|
362
|
+
|
|
363
|
+
# Otherwise, we will need to generate a
|
|
364
|
+
# valid stamp according to the cost that
|
|
365
|
+
# the receiver has specified.
|
|
366
|
+
else:
|
|
367
|
+
generated_stamp, value = LXStamper.generate_stamp(
|
|
368
|
+
self.message_id, self.stamp_cost
|
|
369
|
+
)
|
|
370
|
+
if generated_stamp:
|
|
371
|
+
self.stamp_value = value
|
|
372
|
+
self.stamp_valid = True
|
|
373
|
+
return generated_stamp
|
|
374
|
+
|
|
375
|
+
else:
|
|
376
|
+
return None
|
|
377
|
+
|
|
378
|
+
def get_propagation_stamp(self, target_cost, timeout=None):
|
|
379
|
+
# If a stamp was already generated, return
|
|
380
|
+
# it immediately.
|
|
381
|
+
if self.propagation_stamp != None:
|
|
382
|
+
return self.propagation_stamp
|
|
383
|
+
|
|
384
|
+
# Otherwise, we will need to generate a
|
|
385
|
+
# valid stamp according to the cost that
|
|
386
|
+
# the propagation node has specified.
|
|
387
|
+
else:
|
|
388
|
+
self.propagation_target_cost = target_cost
|
|
389
|
+
if self.propagation_target_cost == None:
|
|
390
|
+
raise ValueError(
|
|
391
|
+
"Cannot generate propagation stamp without configured target propagation cost"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
if not self.transient_id:
|
|
395
|
+
self.pack()
|
|
396
|
+
generated_stamp, value = LXStamper.generate_stamp(
|
|
397
|
+
self.transient_id,
|
|
398
|
+
target_cost,
|
|
399
|
+
expand_rounds=LXStamper.WORKBLOCK_EXPAND_ROUNDS_PN,
|
|
400
|
+
)
|
|
401
|
+
if generated_stamp:
|
|
402
|
+
self.propagation_stamp = generated_stamp
|
|
403
|
+
self.propagation_stamp_value = value
|
|
404
|
+
self.propagation_stamp_valid = True
|
|
405
|
+
return generated_stamp
|
|
406
|
+
|
|
407
|
+
else:
|
|
408
|
+
return None
|
|
409
|
+
|
|
410
|
+
def pack(self, payload_updated=False):
|
|
411
|
+
if not self.packed:
|
|
412
|
+
if self.timestamp == None:
|
|
413
|
+
self.timestamp = time.time()
|
|
414
|
+
|
|
415
|
+
self.propagation_packed = None
|
|
416
|
+
self.paper_packed = None
|
|
417
|
+
|
|
418
|
+
self.payload = [self.timestamp, self.title, self.content, self.fields]
|
|
419
|
+
|
|
420
|
+
hashed_part = b""
|
|
421
|
+
hashed_part += self.__destination.hash
|
|
422
|
+
hashed_part += self.__source.hash
|
|
423
|
+
hashed_part += msgpack.packb(self.payload)
|
|
424
|
+
self.hash = RNS.Identity.full_hash(hashed_part)
|
|
425
|
+
self.message_id = self.hash
|
|
426
|
+
|
|
427
|
+
if not self.defer_stamp:
|
|
428
|
+
self.stamp = self.get_stamp()
|
|
429
|
+
if self.stamp != None:
|
|
430
|
+
self.payload.append(self.stamp)
|
|
431
|
+
|
|
432
|
+
signed_part = b""
|
|
433
|
+
signed_part += hashed_part
|
|
434
|
+
signed_part += self.hash
|
|
435
|
+
self.signature = self.__source.sign(signed_part)
|
|
436
|
+
self.signature_validated = True
|
|
437
|
+
|
|
438
|
+
packed_payload = msgpack.packb(self.payload)
|
|
439
|
+
self.packed = b""
|
|
440
|
+
self.packed += self.__destination.hash
|
|
441
|
+
self.packed += self.__source.hash
|
|
442
|
+
self.packed += self.signature
|
|
443
|
+
self.packed += packed_payload
|
|
444
|
+
self.packed_size = len(self.packed)
|
|
445
|
+
content_size = (
|
|
446
|
+
len(packed_payload)
|
|
447
|
+
- LXMessage.TIMESTAMP_SIZE
|
|
448
|
+
- LXMessage.STRUCT_OVERHEAD
|
|
449
|
+
)
|
|
450
|
+
|
|
451
|
+
# If no desired delivery method has been defined,
|
|
452
|
+
# one will be chosen according to these rules:
|
|
453
|
+
if self.desired_method == None:
|
|
454
|
+
self.desired_method = LXMessage.DIRECT
|
|
455
|
+
|
|
456
|
+
# If opportunistic delivery was requested, check
|
|
457
|
+
# that message will fit within packet size limits
|
|
458
|
+
if self.desired_method == LXMessage.OPPORTUNISTIC:
|
|
459
|
+
if self.__destination.type == RNS.Destination.SINGLE:
|
|
460
|
+
if content_size > LXMessage.ENCRYPTED_PACKET_MAX_CONTENT:
|
|
461
|
+
RNS.log(
|
|
462
|
+
"Opportunistic delivery was requested for "
|
|
463
|
+
f"{self}, but content of length {content_size} exceeds packet size limit. "
|
|
464
|
+
"Falling back to link-based delivery.",
|
|
465
|
+
RNS.LOG_DEBUG,
|
|
466
|
+
)
|
|
467
|
+
self.desired_method = LXMessage.DIRECT
|
|
468
|
+
|
|
469
|
+
# Set delivery parameters according to delivery method
|
|
470
|
+
if self.desired_method == LXMessage.OPPORTUNISTIC:
|
|
471
|
+
if self.__destination.type == RNS.Destination.SINGLE:
|
|
472
|
+
single_packet_content_limit = LXMessage.ENCRYPTED_PACKET_MAX_CONTENT
|
|
473
|
+
elif self.__destination.type == RNS.Destination.PLAIN:
|
|
474
|
+
single_packet_content_limit = LXMessage.PLAIN_PACKET_MAX_CONTENT
|
|
475
|
+
else:
|
|
476
|
+
raise TypeError(
|
|
477
|
+
"LXMessage desired opportunistic delivery method, but "
|
|
478
|
+
f"destination type {self.__destination.type} is unsupported."
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
if content_size > single_packet_content_limit:
|
|
482
|
+
raise TypeError(
|
|
483
|
+
"LXMessage desired opportunistic delivery method, but "
|
|
484
|
+
f"content of length {content_size} exceeds single-packet content limit of "
|
|
485
|
+
f"{single_packet_content_limit}."
|
|
486
|
+
)
|
|
487
|
+
else:
|
|
488
|
+
self.method = LXMessage.OPPORTUNISTIC
|
|
489
|
+
self.representation = LXMessage.PACKET
|
|
490
|
+
self.__delivery_destination = self.__destination
|
|
491
|
+
|
|
492
|
+
elif self.desired_method == LXMessage.DIRECT:
|
|
493
|
+
single_packet_content_limit = LXMessage.LINK_PACKET_MAX_CONTENT
|
|
494
|
+
if content_size <= single_packet_content_limit:
|
|
495
|
+
self.method = self.desired_method
|
|
496
|
+
self.representation = LXMessage.PACKET
|
|
497
|
+
else:
|
|
498
|
+
self.method = self.desired_method
|
|
499
|
+
self.representation = LXMessage.RESOURCE
|
|
500
|
+
|
|
501
|
+
elif self.desired_method == LXMessage.PROPAGATED:
|
|
502
|
+
single_packet_content_limit = LXMessage.LINK_PACKET_MAX_CONTENT
|
|
503
|
+
|
|
504
|
+
if self.__pn_encrypted_data == None or payload_updated:
|
|
505
|
+
self.__pn_encrypted_data = self.__destination.encrypt(
|
|
506
|
+
self.packed[LXMessage.DESTINATION_LENGTH :]
|
|
507
|
+
)
|
|
508
|
+
self.ratchet_id = self.__destination.latest_ratchet_id
|
|
509
|
+
|
|
510
|
+
lxmf_data = (
|
|
511
|
+
self.packed[: LXMessage.DESTINATION_LENGTH]
|
|
512
|
+
+ self.__pn_encrypted_data
|
|
513
|
+
)
|
|
514
|
+
self.transient_id = RNS.Identity.full_hash(lxmf_data)
|
|
515
|
+
if self.propagation_stamp != None:
|
|
516
|
+
lxmf_data += self.propagation_stamp
|
|
517
|
+
self.propagation_packed = msgpack.packb([time.time(), [lxmf_data]])
|
|
518
|
+
|
|
519
|
+
content_size = len(self.propagation_packed)
|
|
520
|
+
if content_size <= single_packet_content_limit:
|
|
521
|
+
self.method = self.desired_method
|
|
522
|
+
self.representation = LXMessage.PACKET
|
|
523
|
+
else:
|
|
524
|
+
self.method = self.desired_method
|
|
525
|
+
self.representation = LXMessage.RESOURCE
|
|
526
|
+
|
|
527
|
+
elif self.desired_method == LXMessage.PAPER:
|
|
528
|
+
paper_content_limit = LXMessage.PAPER_MDU
|
|
529
|
+
|
|
530
|
+
encrypted_data = self.__destination.encrypt(
|
|
531
|
+
self.packed[LXMessage.DESTINATION_LENGTH :]
|
|
532
|
+
)
|
|
533
|
+
self.ratchet_id = self.__destination.latest_ratchet_id
|
|
534
|
+
self.paper_packed = (
|
|
535
|
+
self.packed[: LXMessage.DESTINATION_LENGTH] + encrypted_data
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
content_size = len(self.paper_packed)
|
|
539
|
+
if content_size <= paper_content_limit:
|
|
540
|
+
self.method = self.desired_method
|
|
541
|
+
self.representation = LXMessage.PAPER
|
|
542
|
+
else:
|
|
543
|
+
raise TypeError(
|
|
544
|
+
"LXMessage desired paper delivery method, but content exceeds paper message maximum size."
|
|
545
|
+
)
|
|
546
|
+
|
|
547
|
+
else:
|
|
548
|
+
raise ValueError(
|
|
549
|
+
"Attempt to re-pack LXMessage " + str(self) + " that was already packed"
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def send(self):
|
|
553
|
+
self.determine_transport_encryption()
|
|
554
|
+
|
|
555
|
+
if self.method == LXMessage.OPPORTUNISTIC:
|
|
556
|
+
lxm_packet = self.__as_packet()
|
|
557
|
+
receipt: Any = lxm_packet.send()
|
|
558
|
+
if not isinstance(receipt, bool) and hasattr(
|
|
559
|
+
receipt, "set_delivery_callback"
|
|
560
|
+
):
|
|
561
|
+
receipt.set_delivery_callback( # pylint: disable=no-member
|
|
562
|
+
self.__mark_delivered
|
|
563
|
+
)
|
|
564
|
+
self.progress = 0.50
|
|
565
|
+
self.ratchet_id = lxm_packet.ratchet_id
|
|
566
|
+
self.state = LXMessage.SENT
|
|
567
|
+
|
|
568
|
+
elif self.method == LXMessage.DIRECT:
|
|
569
|
+
self.state = LXMessage.SENDING
|
|
570
|
+
|
|
571
|
+
if self.representation == LXMessage.PACKET:
|
|
572
|
+
lxm_packet = self.__as_packet()
|
|
573
|
+
receipt: Any = lxm_packet.send()
|
|
574
|
+
receipt_obj: Any | None = None
|
|
575
|
+
if not isinstance(receipt, bool):
|
|
576
|
+
receipt_obj = receipt
|
|
577
|
+
self.ratchet_id = self.__delivery_destination.link_id
|
|
578
|
+
if receipt_obj:
|
|
579
|
+
receipt_obj.set_delivery_callback( # pylint: disable=no-member
|
|
580
|
+
self.__mark_delivered
|
|
581
|
+
)
|
|
582
|
+
receipt_obj.set_timeout_callback(self.__link_packet_timed_out)
|
|
583
|
+
self.progress = 0.50
|
|
584
|
+
else:
|
|
585
|
+
if self.__delivery_destination:
|
|
586
|
+
self.__delivery_destination.teardown()
|
|
587
|
+
|
|
588
|
+
elif self.representation == LXMessage.RESOURCE:
|
|
589
|
+
self.resource_representation = self.__as_resource()
|
|
590
|
+
self.ratchet_id = self.__delivery_destination.link_id
|
|
591
|
+
self.progress = 0.10
|
|
592
|
+
|
|
593
|
+
elif self.method == LXMessage.PROPAGATED:
|
|
594
|
+
self.state = LXMessage.SENDING
|
|
595
|
+
|
|
596
|
+
if self.representation == LXMessage.PACKET:
|
|
597
|
+
receipt = self.__as_packet().send()
|
|
598
|
+
if receipt:
|
|
599
|
+
receipt.set_delivery_callback(self.__mark_propagated)
|
|
600
|
+
receipt.set_timeout_callback(self.__link_packet_timed_out)
|
|
601
|
+
self.progress = 0.50
|
|
602
|
+
else:
|
|
603
|
+
self.__delivery_destination.teardown()
|
|
604
|
+
|
|
605
|
+
elif self.representation == LXMessage.RESOURCE:
|
|
606
|
+
self.resource_representation = self.__as_resource()
|
|
607
|
+
self.progress = 0.10
|
|
608
|
+
|
|
609
|
+
def determine_transport_encryption(self):
|
|
610
|
+
# TODO: These descriptions are old and outdated.
|
|
611
|
+
# Update the transport encryption descriptions to
|
|
612
|
+
# account for ratchets and other changes.
|
|
613
|
+
if self.method == LXMessage.OPPORTUNISTIC:
|
|
614
|
+
if self.__destination.type == RNS.Destination.SINGLE:
|
|
615
|
+
self.transport_encrypted = True
|
|
616
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_EC
|
|
617
|
+
elif self.__destination.type == RNS.Destination.GROUP:
|
|
618
|
+
self.transport_encrypted = True
|
|
619
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_AES
|
|
620
|
+
else:
|
|
621
|
+
self.transport_encrypted = False
|
|
622
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_UNENCRYPTED
|
|
623
|
+
elif self.method == LXMessage.DIRECT:
|
|
624
|
+
self.transport_encrypted = True
|
|
625
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_EC
|
|
626
|
+
elif self.method == LXMessage.PROPAGATED:
|
|
627
|
+
if self.__destination.type == RNS.Destination.SINGLE:
|
|
628
|
+
self.transport_encrypted = True
|
|
629
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_EC
|
|
630
|
+
elif self.__destination.type == RNS.Destination.GROUP:
|
|
631
|
+
self.transport_encrypted = True
|
|
632
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_AES
|
|
633
|
+
else:
|
|
634
|
+
self.transport_encrypted = False
|
|
635
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_UNENCRYPTED
|
|
636
|
+
elif self.method == LXMessage.PAPER:
|
|
637
|
+
if self.__destination.type == RNS.Destination.SINGLE:
|
|
638
|
+
self.transport_encrypted = True
|
|
639
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_EC
|
|
640
|
+
elif self.__destination.type == RNS.Destination.GROUP:
|
|
641
|
+
self.transport_encrypted = True
|
|
642
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_AES
|
|
643
|
+
else:
|
|
644
|
+
self.transport_encrypted = False
|
|
645
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_UNENCRYPTED
|
|
646
|
+
else:
|
|
647
|
+
self.transport_encrypted = False
|
|
648
|
+
self.transport_encryption = LXMessage.ENCRYPTION_DESCRIPTION_UNENCRYPTED
|
|
649
|
+
|
|
650
|
+
def __mark_delivered(self, receipt=None):
|
|
651
|
+
RNS.log("Received delivery notification for " + str(self), RNS.LOG_DEBUG)
|
|
652
|
+
self.state = LXMessage.DELIVERED
|
|
653
|
+
self.progress = 1.0
|
|
654
|
+
|
|
655
|
+
if self.__delivery_callback != None and callable(self.__delivery_callback):
|
|
656
|
+
try:
|
|
657
|
+
self.__delivery_callback(self)
|
|
658
|
+
except Exception as e:
|
|
659
|
+
RNS.log(
|
|
660
|
+
"An error occurred in the external delivery callback for "
|
|
661
|
+
+ str(self),
|
|
662
|
+
RNS.LOG_ERROR,
|
|
663
|
+
)
|
|
664
|
+
RNS.trace_exception(e)
|
|
665
|
+
|
|
666
|
+
def __mark_propagated(self, receipt=None):
|
|
667
|
+
RNS.log(
|
|
668
|
+
"Received propagation success notification for " + str(self), RNS.LOG_DEBUG
|
|
669
|
+
)
|
|
670
|
+
self.state = LXMessage.SENT
|
|
671
|
+
self.progress = 1.0
|
|
672
|
+
|
|
673
|
+
if self.__delivery_callback != None and callable(self.__delivery_callback):
|
|
674
|
+
try:
|
|
675
|
+
self.__delivery_callback(self)
|
|
676
|
+
except Exception as e:
|
|
677
|
+
RNS.log(
|
|
678
|
+
"An error occurred in the external delivery callback for "
|
|
679
|
+
+ str(self),
|
|
680
|
+
RNS.LOG_ERROR,
|
|
681
|
+
)
|
|
682
|
+
RNS.trace_exception(e)
|
|
683
|
+
|
|
684
|
+
def __mark_paper_generated(self, receipt=None):
|
|
685
|
+
RNS.log("Paper message generation succeeded for " + str(self), RNS.LOG_DEBUG)
|
|
686
|
+
self.state = LXMessage.PAPER
|
|
687
|
+
self.progress = 1.0
|
|
688
|
+
|
|
689
|
+
if self.__delivery_callback != None and callable(self.__delivery_callback):
|
|
690
|
+
try:
|
|
691
|
+
self.__delivery_callback(self)
|
|
692
|
+
except Exception as e:
|
|
693
|
+
RNS.log(
|
|
694
|
+
"An error occurred in the external delivery callback for "
|
|
695
|
+
+ str(self),
|
|
696
|
+
RNS.LOG_ERROR,
|
|
697
|
+
)
|
|
698
|
+
RNS.trace_exception(e)
|
|
699
|
+
|
|
700
|
+
def __resource_concluded(self, resource):
|
|
701
|
+
if resource.status == RNS.Resource.COMPLETE:
|
|
702
|
+
self.__mark_delivered()
|
|
703
|
+
else:
|
|
704
|
+
if resource.status == RNS.Resource.REJECTED:
|
|
705
|
+
self.state = LXMessage.REJECTED
|
|
706
|
+
|
|
707
|
+
elif self.state != LXMessage.CANCELLED:
|
|
708
|
+
resource.link.teardown()
|
|
709
|
+
self.state = LXMessage.OUTBOUND
|
|
710
|
+
|
|
711
|
+
def __propagation_resource_concluded(self, resource):
|
|
712
|
+
if resource.status == RNS.Resource.COMPLETE:
|
|
713
|
+
self.__mark_propagated()
|
|
714
|
+
else:
|
|
715
|
+
if self.state != LXMessage.CANCELLED:
|
|
716
|
+
resource.link.teardown()
|
|
717
|
+
self.state = LXMessage.OUTBOUND
|
|
718
|
+
|
|
719
|
+
def __link_packet_timed_out(self, packet_receipt):
|
|
720
|
+
if self.state != LXMessage.CANCELLED:
|
|
721
|
+
if packet_receipt:
|
|
722
|
+
packet_receipt.destination.teardown()
|
|
723
|
+
|
|
724
|
+
self.state = LXMessage.OUTBOUND
|
|
725
|
+
|
|
726
|
+
def __update_transfer_progress(self, resource):
|
|
727
|
+
self.progress = 0.10 + (resource.get_progress() * 0.90)
|
|
728
|
+
|
|
729
|
+
def __as_packet(self):
|
|
730
|
+
if not self.packed:
|
|
731
|
+
self.pack()
|
|
732
|
+
|
|
733
|
+
if not self.__delivery_destination:
|
|
734
|
+
raise ValueError(
|
|
735
|
+
"Can't synthesize packet for LXMF message before delivery destination is known"
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
if self.method == LXMessage.OPPORTUNISTIC:
|
|
739
|
+
return RNS.Packet(
|
|
740
|
+
self.__delivery_destination, self.packed[LXMessage.DESTINATION_LENGTH :]
|
|
741
|
+
)
|
|
742
|
+
elif self.method == LXMessage.DIRECT:
|
|
743
|
+
return RNS.Packet(self.__delivery_destination, self.packed)
|
|
744
|
+
elif self.method == LXMessage.PROPAGATED:
|
|
745
|
+
return RNS.Packet(self.__delivery_destination, self.propagation_packed)
|
|
746
|
+
|
|
747
|
+
def __as_resource(self):
|
|
748
|
+
if not self.packed:
|
|
749
|
+
self.pack()
|
|
750
|
+
|
|
751
|
+
if not self.__delivery_destination:
|
|
752
|
+
raise ValueError(
|
|
753
|
+
"Can't synthesize resource for LXMF message before delivery destination is known"
|
|
754
|
+
)
|
|
755
|
+
|
|
756
|
+
if not self.__delivery_destination.type == RNS.Destination.LINK:
|
|
757
|
+
raise TypeError(
|
|
758
|
+
"Tried to synthesize resource for LXMF message on a delivery destination that was not a link"
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
if not self.__delivery_destination.status == RNS.Link.ACTIVE:
|
|
762
|
+
raise ConnectionError(
|
|
763
|
+
"Tried to synthesize resource for LXMF message on a link that was not active"
|
|
764
|
+
)
|
|
765
|
+
|
|
766
|
+
if self.method == LXMessage.DIRECT:
|
|
767
|
+
return RNS.Resource(
|
|
768
|
+
self.packed,
|
|
769
|
+
self.__delivery_destination,
|
|
770
|
+
callback=self.__resource_concluded,
|
|
771
|
+
progress_callback=self.__update_transfer_progress,
|
|
772
|
+
)
|
|
773
|
+
elif self.method == LXMessage.PROPAGATED:
|
|
774
|
+
return RNS.Resource(
|
|
775
|
+
self.propagation_packed,
|
|
776
|
+
self.__delivery_destination,
|
|
777
|
+
callback=self.__propagation_resource_concluded,
|
|
778
|
+
progress_callback=self.__update_transfer_progress,
|
|
779
|
+
)
|
|
780
|
+
else:
|
|
781
|
+
return None
|
|
782
|
+
|
|
783
|
+
def packed_container(self):
|
|
784
|
+
if not self.packed:
|
|
785
|
+
self.pack()
|
|
786
|
+
|
|
787
|
+
container = {
|
|
788
|
+
"state": self.state,
|
|
789
|
+
"lxmf_bytes": self.packed,
|
|
790
|
+
"transport_encrypted": self.transport_encrypted,
|
|
791
|
+
"transport_encryption": self.transport_encryption,
|
|
792
|
+
"method": self.method,
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
return msgpack.packb(container)
|
|
796
|
+
|
|
797
|
+
def write_to_directory(self, directory_path):
|
|
798
|
+
file_name = RNS.hexrep(self.hash, delimit=False)
|
|
799
|
+
file_path = directory_path + "/" + file_name
|
|
800
|
+
|
|
801
|
+
try:
|
|
802
|
+
file = open(file_path, "wb")
|
|
803
|
+
file.write(self.packed_container())
|
|
804
|
+
file.close()
|
|
805
|
+
|
|
806
|
+
return file_path
|
|
807
|
+
|
|
808
|
+
except Exception as e:
|
|
809
|
+
RNS.log(
|
|
810
|
+
'Error while writing LXMF message to file "'
|
|
811
|
+
+ str(file_path)
|
|
812
|
+
+ '". The contained exception was: '
|
|
813
|
+
+ str(e),
|
|
814
|
+
RNS.LOG_ERROR,
|
|
815
|
+
)
|
|
816
|
+
return None
|
|
817
|
+
|
|
818
|
+
def as_uri(self, finalise=True):
|
|
819
|
+
if not self.packed:
|
|
820
|
+
self.pack()
|
|
821
|
+
|
|
822
|
+
if self.desired_method == LXMessage.PAPER and self.paper_packed != None:
|
|
823
|
+
# Encode packed LXM with URL-safe base64 and remove padding
|
|
824
|
+
encoded_bytes = base64.urlsafe_b64encode(self.paper_packed)
|
|
825
|
+
|
|
826
|
+
# Add protocol specifier and return
|
|
827
|
+
lxm_uri = (
|
|
828
|
+
LXMessage.URI_SCHEMA
|
|
829
|
+
+ "://"
|
|
830
|
+
+ encoded_bytes.decode("utf-8").replace("=", "")
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
if finalise:
|
|
834
|
+
self.determine_transport_encryption()
|
|
835
|
+
self.__mark_paper_generated()
|
|
836
|
+
|
|
837
|
+
return lxm_uri
|
|
838
|
+
|
|
839
|
+
else:
|
|
840
|
+
raise TypeError(
|
|
841
|
+
"Attempt to represent LXM with non-paper delivery method as URI"
|
|
842
|
+
)
|
|
843
|
+
|
|
844
|
+
def as_qr(self):
|
|
845
|
+
if not self.packed:
|
|
846
|
+
self.pack()
|
|
847
|
+
|
|
848
|
+
if self.desired_method == LXMessage.PAPER and self.paper_packed != None:
|
|
849
|
+
import importlib
|
|
850
|
+
|
|
851
|
+
if importlib.util.find_spec("qrcode") != None:
|
|
852
|
+
import qrcode
|
|
853
|
+
|
|
854
|
+
qr = qrcode.make(
|
|
855
|
+
error_correction=qrcode.constants.__dict__[
|
|
856
|
+
LXMessage.QR_ERROR_CORRECTION
|
|
857
|
+
],
|
|
858
|
+
border=1,
|
|
859
|
+
data=self.as_uri(finalise=False),
|
|
860
|
+
)
|
|
861
|
+
|
|
862
|
+
self.determine_transport_encryption()
|
|
863
|
+
self.__mark_paper_generated()
|
|
864
|
+
|
|
865
|
+
return qr
|
|
866
|
+
|
|
867
|
+
else:
|
|
868
|
+
RNS.log(
|
|
869
|
+
'Generating QR-code representanions of LXMs requires the "qrcode" module to be installed.',
|
|
870
|
+
RNS.LOG_CRITICAL,
|
|
871
|
+
)
|
|
872
|
+
RNS.log(
|
|
873
|
+
"You can install it with the command: python3 -m pip install qrcode",
|
|
874
|
+
RNS.LOG_CRITICAL,
|
|
875
|
+
)
|
|
876
|
+
return None
|
|
877
|
+
|
|
878
|
+
else:
|
|
879
|
+
raise TypeError(
|
|
880
|
+
"Attempt to represent LXM with non-paper delivery method as QR-code"
|
|
881
|
+
)
|
|
882
|
+
|
|
883
|
+
@staticmethod
|
|
884
|
+
def unpack_from_bytes(lxmf_bytes, original_method=None):
|
|
885
|
+
destination_hash = lxmf_bytes[: LXMessage.DESTINATION_LENGTH]
|
|
886
|
+
source_hash = lxmf_bytes[
|
|
887
|
+
LXMessage.DESTINATION_LENGTH : 2 * LXMessage.DESTINATION_LENGTH
|
|
888
|
+
]
|
|
889
|
+
signature = lxmf_bytes[
|
|
890
|
+
2 * LXMessage.DESTINATION_LENGTH : 2 * LXMessage.DESTINATION_LENGTH
|
|
891
|
+
+ LXMessage.SIGNATURE_LENGTH
|
|
892
|
+
]
|
|
893
|
+
packed_payload = lxmf_bytes[
|
|
894
|
+
2 * LXMessage.DESTINATION_LENGTH + LXMessage.SIGNATURE_LENGTH :
|
|
895
|
+
]
|
|
896
|
+
unpacked_payload = msgpack.unpackb(packed_payload)
|
|
897
|
+
|
|
898
|
+
# Extract stamp from payload if included
|
|
899
|
+
if len(unpacked_payload) > 4:
|
|
900
|
+
stamp = unpacked_payload[4]
|
|
901
|
+
unpacked_payload = unpacked_payload[:4]
|
|
902
|
+
packed_payload = msgpack.packb(unpacked_payload)
|
|
903
|
+
else:
|
|
904
|
+
stamp = None
|
|
905
|
+
|
|
906
|
+
hashed_part = b"" + destination_hash + source_hash + packed_payload
|
|
907
|
+
message_hash = RNS.Identity.full_hash(hashed_part)
|
|
908
|
+
signed_part = b"" + hashed_part + message_hash
|
|
909
|
+
timestamp = unpacked_payload[0]
|
|
910
|
+
title_bytes = unpacked_payload[1]
|
|
911
|
+
content_bytes = unpacked_payload[2]
|
|
912
|
+
fields = unpacked_payload[3]
|
|
913
|
+
|
|
914
|
+
destination_identity = RNS.Identity.recall(destination_hash)
|
|
915
|
+
if destination_identity != None:
|
|
916
|
+
destination = RNS.Destination(
|
|
917
|
+
destination_identity,
|
|
918
|
+
RNS.Destination.OUT,
|
|
919
|
+
RNS.Destination.SINGLE,
|
|
920
|
+
APP_NAME,
|
|
921
|
+
"delivery",
|
|
922
|
+
)
|
|
923
|
+
else:
|
|
924
|
+
destination = None
|
|
925
|
+
|
|
926
|
+
source_identity = RNS.Identity.recall(source_hash)
|
|
927
|
+
if source_identity != None:
|
|
928
|
+
source = RNS.Destination(
|
|
929
|
+
source_identity,
|
|
930
|
+
RNS.Destination.OUT,
|
|
931
|
+
RNS.Destination.SINGLE,
|
|
932
|
+
APP_NAME,
|
|
933
|
+
"delivery",
|
|
934
|
+
)
|
|
935
|
+
else:
|
|
936
|
+
source = None
|
|
937
|
+
|
|
938
|
+
message = LXMessage(
|
|
939
|
+
destination=destination,
|
|
940
|
+
source=source,
|
|
941
|
+
content="",
|
|
942
|
+
title="",
|
|
943
|
+
fields=fields,
|
|
944
|
+
destination_hash=destination_hash,
|
|
945
|
+
source_hash=source_hash,
|
|
946
|
+
desired_method=original_method,
|
|
947
|
+
)
|
|
948
|
+
|
|
949
|
+
message.hash = message_hash
|
|
950
|
+
message.message_id = message.hash
|
|
951
|
+
message.signature = signature
|
|
952
|
+
message.stamp = stamp
|
|
953
|
+
message.incoming = True
|
|
954
|
+
message.timestamp = timestamp
|
|
955
|
+
message.packed = lxmf_bytes
|
|
956
|
+
message.packed_size = len(lxmf_bytes)
|
|
957
|
+
message.set_title_from_bytes(title_bytes)
|
|
958
|
+
message.set_content_from_bytes(content_bytes)
|
|
959
|
+
|
|
960
|
+
try:
|
|
961
|
+
if source:
|
|
962
|
+
if source.identity.validate(signature, signed_part):
|
|
963
|
+
message.signature_validated = True
|
|
964
|
+
else:
|
|
965
|
+
message.signature_validated = False
|
|
966
|
+
message.unverified_reason = LXMessage.SIGNATURE_INVALID
|
|
967
|
+
else:
|
|
968
|
+
signature_validated = False
|
|
969
|
+
message.unverified_reason = LXMessage.SOURCE_UNKNOWN
|
|
970
|
+
RNS.log(
|
|
971
|
+
"Unpacked LXMF message signature could not be validated, since source identity is unknown",
|
|
972
|
+
RNS.LOG_DEBUG,
|
|
973
|
+
)
|
|
974
|
+
except Exception as e:
|
|
975
|
+
message.signature_validated = False
|
|
976
|
+
RNS.log(
|
|
977
|
+
"Error while validating LXMF message signature. The contained exception was: "
|
|
978
|
+
+ str(e),
|
|
979
|
+
RNS.LOG_ERROR,
|
|
980
|
+
)
|
|
981
|
+
|
|
982
|
+
return message
|
|
983
|
+
|
|
984
|
+
@staticmethod
|
|
985
|
+
def unpack_from_file(lxmf_file_handle):
|
|
986
|
+
try:
|
|
987
|
+
container = msgpack.unpackb(lxmf_file_handle.read())
|
|
988
|
+
lxm = LXMessage.unpack_from_bytes(container["lxmf_bytes"])
|
|
989
|
+
|
|
990
|
+
if "state" in container:
|
|
991
|
+
lxm.state = container["state"]
|
|
992
|
+
if "transport_encrypted" in container:
|
|
993
|
+
lxm.transport_encrypted = container["transport_encrypted"]
|
|
994
|
+
if "transport_encryption" in container:
|
|
995
|
+
lxm.transport_encryption = container["transport_encryption"]
|
|
996
|
+
if "method" in container:
|
|
997
|
+
lxm.method = container["method"]
|
|
998
|
+
|
|
999
|
+
return lxm
|
|
1000
|
+
except Exception as e:
|
|
1001
|
+
RNS.log(
|
|
1002
|
+
"Could not unpack LXMessage from file. The contained exception was: "
|
|
1003
|
+
+ str(e),
|
|
1004
|
+
RNS.LOG_ERROR,
|
|
1005
|
+
)
|
|
1006
|
+
return None
|