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,1655 @@
1
+ #!/usr/bin/env python3
2
+
3
+ # Reticulum License
4
+ #
5
+ # Copyright (c) 2020-2025 Mark Qvist
6
+ #
7
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
8
+ # of this software and associated documentation files (the "Software"), to deal
9
+ # in the Software without restriction, including without limitation the rights
10
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ # copies of the Software, and to permit persons to whom the Software is
12
+ # furnished to do so, subject to the following conditions:
13
+ #
14
+ # - The Software shall not be used in any kind of system which includes amongst
15
+ # its functions the ability to purposefully do harm to human beings.
16
+ #
17
+ # - The Software shall not be used, directly or indirectly, in the creation of
18
+ # an artificial intelligence, machine learning or language model training
19
+ # dataset, including but not limited to any use that contributes to the
20
+ # training or development of such a model or algorithm.
21
+ #
22
+ # - The above copyright notice and this permission notice shall be included in
23
+ # all copies or substantial portions of the Software.
24
+ #
25
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31
+ # SOFTWARE.
32
+
33
+ import argparse
34
+ import os
35
+ import shlex
36
+ import subprocess
37
+ import threading
38
+ import time
39
+
40
+ import RNS
41
+ from RNS.vendor.configobj import ConfigObj
42
+
43
+ from reticulum_telemetry_hub import lxmf_daemon as LXMF
44
+ from reticulum_telemetry_hub.config.manager import HubConfigurationManager
45
+ from reticulum_telemetry_hub.lxmf_daemon.LXMF import APP_NAME
46
+ from reticulum_telemetry_hub.lxmf_daemon._version import __version__
47
+
48
+ DEFFERED_JOBS_DELAY = 10
49
+ JOBS_INTERVAL = 5
50
+
51
+ configpath = None
52
+ ignoredpath = None
53
+ identitypath = None
54
+ storagedir = None
55
+ lxmdir = None
56
+ targetloglevel = None
57
+
58
+ identity = None
59
+ lxmd_config = None
60
+ message_router = None
61
+ lxmf_destination = None
62
+ active_configuration = {}
63
+
64
+ last_peer_announce = None
65
+ last_node_announce = None
66
+
67
+
68
+ def _default_config_directory() -> str:
69
+ """Return the lxmd configuration directory within the hub storage path."""
70
+
71
+ storage_path = HubConfigurationManager().storage_path
72
+ return str(storage_path / "lxmd")
73
+
74
+
75
+ def create_default_config(configpath):
76
+ lxmd_config = ConfigObj(__default_lxmd_config__.splitlines())
77
+ lxmd_config.filename = configpath
78
+ lxmd_config.write()
79
+
80
+
81
+ def apply_config():
82
+ global active_configuration, targetloglevel
83
+ try:
84
+ # Load peer settings
85
+ if "lxmf" in lxmd_config and "display_name" in lxmd_config["lxmf"]:
86
+ active_configuration["display_name"] = lxmd_config["lxmf"]["display_name"]
87
+ else:
88
+ active_configuration["display_name"] = "Anonymous Peer"
89
+
90
+ if "lxmf" in lxmd_config and "announce_at_start" in lxmd_config["lxmf"]:
91
+ active_configuration["peer_announce_at_start"] = lxmd_config[
92
+ "lxmf"
93
+ ].as_bool("announce_at_start")
94
+ else:
95
+ active_configuration["peer_announce_at_start"] = False
96
+
97
+ if "lxmf" in lxmd_config and "announce_interval" in lxmd_config["lxmf"]:
98
+ active_configuration["peer_announce_interval"] = (
99
+ lxmd_config["lxmf"].as_int("announce_interval") * 60
100
+ )
101
+ else:
102
+ active_configuration["peer_announce_interval"] = None
103
+
104
+ if (
105
+ "lxmf" in lxmd_config
106
+ and "delivery_transfer_max_accepted_size" in lxmd_config["lxmf"]
107
+ ):
108
+ active_configuration["delivery_transfer_max_accepted_size"] = lxmd_config[
109
+ "lxmf"
110
+ ].as_float("delivery_transfer_max_accepted_size")
111
+ if active_configuration["delivery_transfer_max_accepted_size"] < 0.38:
112
+ active_configuration["delivery_transfer_max_accepted_size"] = 0.38
113
+ else:
114
+ active_configuration["delivery_transfer_max_accepted_size"] = 1000
115
+
116
+ if "lxmf" in lxmd_config and "on_inbound" in lxmd_config["lxmf"]:
117
+ active_configuration["on_inbound"] = lxmd_config["lxmf"]["on_inbound"]
118
+ else:
119
+ active_configuration["on_inbound"] = None
120
+
121
+ # Load propagation node settings
122
+ if "propagation" in lxmd_config and "enable_node" in lxmd_config["propagation"]:
123
+ active_configuration["enable_propagation_node"] = lxmd_config[
124
+ "propagation"
125
+ ].as_bool("enable_node")
126
+ else:
127
+ active_configuration["enable_propagation_node"] = False
128
+
129
+ if "propagation" in lxmd_config and "node_name" in lxmd_config["propagation"]:
130
+ active_configuration["node_name"] = lxmd_config["propagation"].get(
131
+ "node_name"
132
+ )
133
+ else:
134
+ active_configuration["node_name"] = None
135
+
136
+ if (
137
+ "propagation" in lxmd_config
138
+ and "auth_required" in lxmd_config["propagation"]
139
+ ):
140
+ active_configuration["auth_required"] = lxmd_config["propagation"].as_bool(
141
+ "auth_required"
142
+ )
143
+ else:
144
+ active_configuration["auth_required"] = False
145
+
146
+ if (
147
+ "propagation" in lxmd_config
148
+ and "announce_at_start" in lxmd_config["propagation"]
149
+ ):
150
+ active_configuration["node_announce_at_start"] = lxmd_config[
151
+ "propagation"
152
+ ].as_bool("announce_at_start")
153
+ else:
154
+ active_configuration["node_announce_at_start"] = False
155
+
156
+ if "propagation" in lxmd_config and "autopeer" in lxmd_config["propagation"]:
157
+ active_configuration["autopeer"] = lxmd_config["propagation"].as_bool(
158
+ "autopeer"
159
+ )
160
+ else:
161
+ active_configuration["autopeer"] = True
162
+
163
+ if (
164
+ "propagation" in lxmd_config
165
+ and "autopeer_maxdepth" in lxmd_config["propagation"]
166
+ ):
167
+ active_configuration["autopeer_maxdepth"] = lxmd_config[
168
+ "propagation"
169
+ ].as_int("autopeer_maxdepth")
170
+ else:
171
+ active_configuration["autopeer_maxdepth"] = None
172
+
173
+ if (
174
+ "propagation" in lxmd_config
175
+ and "announce_interval" in lxmd_config["propagation"]
176
+ ):
177
+ active_configuration["node_announce_interval"] = (
178
+ lxmd_config["propagation"].as_int("announce_interval") * 60
179
+ )
180
+ else:
181
+ active_configuration["node_announce_interval"] = None
182
+
183
+ if (
184
+ "propagation" in lxmd_config
185
+ and "message_storage_limit" in lxmd_config["propagation"]
186
+ ):
187
+ active_configuration["message_storage_limit"] = lxmd_config[
188
+ "propagation"
189
+ ].as_float("message_storage_limit")
190
+ if active_configuration["message_storage_limit"] < 0.005:
191
+ active_configuration["message_storage_limit"] = 0.005
192
+ else:
193
+ active_configuration["message_storage_limit"] = 500
194
+
195
+ if (
196
+ "propagation" in lxmd_config
197
+ and "propagation_transfer_max_accepted_size" in lxmd_config["propagation"]
198
+ ):
199
+ active_configuration["propagation_transfer_max_accepted_size"] = (
200
+ lxmd_config["propagation"].as_float(
201
+ "propagation_transfer_max_accepted_size"
202
+ )
203
+ )
204
+ if active_configuration["propagation_transfer_max_accepted_size"] < 0.38:
205
+ active_configuration["propagation_transfer_max_accepted_size"] = 0.38
206
+ else:
207
+ active_configuration["propagation_transfer_max_accepted_size"] = 256
208
+
209
+ if (
210
+ "propagation" in lxmd_config
211
+ and "propagation_message_max_accepted_size" in lxmd_config["propagation"]
212
+ ):
213
+ active_configuration["propagation_transfer_max_accepted_size"] = (
214
+ lxmd_config["propagation"].as_float(
215
+ "propagation_message_max_accepted_size"
216
+ )
217
+ )
218
+ if active_configuration["propagation_transfer_max_accepted_size"] < 0.38:
219
+ active_configuration["propagation_transfer_max_accepted_size"] = 0.38
220
+ else:
221
+ active_configuration["propagation_transfer_max_accepted_size"] = 256
222
+
223
+ if (
224
+ "propagation" in lxmd_config
225
+ and "propagation_sync_max_accepted_size" in lxmd_config["propagation"]
226
+ ):
227
+ active_configuration["propagation_sync_max_accepted_size"] = lxmd_config[
228
+ "propagation"
229
+ ].as_float("propagation_sync_max_accepted_size")
230
+ if active_configuration["propagation_sync_max_accepted_size"] < 0.38:
231
+ active_configuration["propagation_sync_max_accepted_size"] = 0.38
232
+ else:
233
+ active_configuration["propagation_sync_max_accepted_size"] = 256 * 40
234
+
235
+ if (
236
+ "propagation" in lxmd_config
237
+ and "propagation_stamp_cost_target" in lxmd_config["propagation"]
238
+ ):
239
+ active_configuration["propagation_stamp_cost_target"] = lxmd_config[
240
+ "propagation"
241
+ ].as_int("propagation_stamp_cost_target")
242
+ if (
243
+ active_configuration["propagation_stamp_cost_target"]
244
+ < LXMF.LXMRouter.PROPAGATION_COST_MIN
245
+ ):
246
+ active_configuration["propagation_stamp_cost_target"] = (
247
+ LXMF.LXMRouter.PROPAGATION_COST_MIN
248
+ )
249
+ else:
250
+ active_configuration["propagation_stamp_cost_target"] = (
251
+ LXMF.LXMRouter.PROPAGATION_COST
252
+ )
253
+
254
+ if (
255
+ "propagation" in lxmd_config
256
+ and "propagation_stamp_cost_flexibility" in lxmd_config["propagation"]
257
+ ):
258
+ active_configuration["propagation_stamp_cost_flexibility"] = lxmd_config[
259
+ "propagation"
260
+ ].as_int("propagation_stamp_cost_flexibility")
261
+ if active_configuration["propagation_stamp_cost_flexibility"] < 0:
262
+ active_configuration["propagation_stamp_cost_flexibility"] = 0
263
+ else:
264
+ active_configuration["propagation_stamp_cost_flexibility"] = (
265
+ LXMF.LXMRouter.PROPAGATION_COST_FLEX
266
+ )
267
+
268
+ if (
269
+ "propagation" in lxmd_config
270
+ and "peering_cost" in lxmd_config["propagation"]
271
+ ):
272
+ active_configuration["peering_cost"] = lxmd_config["propagation"].as_int(
273
+ "peering_cost"
274
+ )
275
+ if active_configuration["peering_cost"] < 0:
276
+ active_configuration["peering_cost"] = 0
277
+ else:
278
+ active_configuration["peering_cost"] = LXMF.LXMRouter.PEERING_COST
279
+
280
+ if (
281
+ "propagation" in lxmd_config
282
+ and "remote_peering_cost_max" in lxmd_config["propagation"]
283
+ ):
284
+ active_configuration["remote_peering_cost_max"] = lxmd_config[
285
+ "propagation"
286
+ ].as_int("remote_peering_cost_max")
287
+ if active_configuration["remote_peering_cost_max"] < 0:
288
+ active_configuration["remote_peering_cost_max"] = 0
289
+ else:
290
+ active_configuration["remote_peering_cost_max"] = (
291
+ LXMF.LXMRouter.MAX_PEERING_COST
292
+ )
293
+
294
+ if (
295
+ "propagation" in lxmd_config
296
+ and "prioritise_destinations" in lxmd_config["propagation"]
297
+ ):
298
+ active_configuration["prioritised_lxmf_destinations"] = lxmd_config[
299
+ "propagation"
300
+ ].as_list("prioritise_destinations")
301
+ else:
302
+ active_configuration["prioritised_lxmf_destinations"] = []
303
+
304
+ if (
305
+ "propagation" in lxmd_config
306
+ and "control_allowed" in lxmd_config["propagation"]
307
+ ):
308
+ active_configuration["control_allowed_identities"] = lxmd_config[
309
+ "propagation"
310
+ ].as_list("control_allowed")
311
+ else:
312
+ active_configuration["control_allowed_identities"] = []
313
+
314
+ if (
315
+ "propagation" in lxmd_config
316
+ and "static_peers" in lxmd_config["propagation"]
317
+ ):
318
+ static_peers = lxmd_config["propagation"].as_list("static_peers")
319
+ active_configuration["static_peers"] = []
320
+ for static_peer in static_peers:
321
+ active_configuration["static_peers"].append(bytes.fromhex(static_peer))
322
+ else:
323
+ active_configuration["static_peers"] = []
324
+
325
+ if "propagation" in lxmd_config and "max_peers" in lxmd_config["propagation"]:
326
+ active_configuration["max_peers"] = lxmd_config["propagation"].as_int(
327
+ "max_peers"
328
+ )
329
+ else:
330
+ active_configuration["max_peers"] = None
331
+
332
+ if (
333
+ "propagation" in lxmd_config
334
+ and "from_static_only" in lxmd_config["propagation"]
335
+ ):
336
+ active_configuration["from_static_only"] = lxmd_config[
337
+ "propagation"
338
+ ].as_bool("from_static_only")
339
+ else:
340
+ active_configuration["from_static_only"] = False
341
+
342
+ # Load various settings
343
+ if "logging" in lxmd_config and "loglevel" in lxmd_config["logging"]:
344
+ targetloglevel = lxmd_config["logging"].as_int("loglevel")
345
+
346
+ active_configuration["ignored_lxmf_destinations"] = []
347
+ if os.path.isfile(ignoredpath):
348
+ try:
349
+ fh = open(ignoredpath, "rb")
350
+ ignored_input = fh.read()
351
+ fh.close()
352
+
353
+ ignored_hash_strs = ignored_input.splitlines()
354
+
355
+ for hash_str in ignored_hash_strs:
356
+ if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH // 8 * 2:
357
+ try:
358
+ ignored_hash = bytes.fromhex(hash_str.decode("utf-8"))
359
+ active_configuration["ignored_lxmf_destinations"].append(
360
+ ignored_hash
361
+ )
362
+
363
+ except Exception as e:
364
+ RNS.log(
365
+ "Could not decode hash from: " + str(hash_str),
366
+ RNS.LOG_DEBUG,
367
+ )
368
+ RNS.log(
369
+ "The contained exception was: " + str(e), RNS.LOG_DEBUG
370
+ )
371
+
372
+ except Exception as e:
373
+ RNS.log(
374
+ "Error while loading list of ignored destinations: " + str(e),
375
+ RNS.LOG_ERROR,
376
+ )
377
+
378
+ active_configuration["allowed_identities"] = []
379
+ if os.path.isfile(allowedpath):
380
+ try:
381
+ fh = open(allowedpath, "rb")
382
+ allowed_input = fh.read()
383
+ fh.close()
384
+
385
+ allowed_hash_strs = allowed_input.splitlines()
386
+
387
+ for hash_str in allowed_hash_strs:
388
+ if len(hash_str) == RNS.Identity.TRUNCATED_HASHLENGTH // 8 * 2:
389
+ try:
390
+ allowed_hash = bytes.fromhex(hash_str.decode("utf-8"))
391
+ active_configuration["allowed_identities"].append(
392
+ allowed_hash
393
+ )
394
+
395
+ except Exception as e:
396
+ RNS.log(
397
+ "Could not decode hash from: " + str(hash_str),
398
+ RNS.LOG_DEBUG,
399
+ )
400
+ RNS.log(
401
+ "The contained exception was: " + str(e), RNS.LOG_DEBUG
402
+ )
403
+
404
+ except Exception as e:
405
+ RNS.log(
406
+ "Error while loading list of allowed identities: " + str(e),
407
+ RNS.LOG_ERROR,
408
+ )
409
+
410
+ except Exception as e:
411
+ RNS.log(
412
+ "Could not apply LXM Daemon configuration. The contained exception was: "
413
+ + str(e),
414
+ RNS.LOG_ERROR,
415
+ )
416
+ raise e
417
+ exit(3)
418
+
419
+
420
+ def lxmf_delivery(lxm):
421
+ global active_configuration, lxmdir
422
+
423
+ try:
424
+ written_path = lxm.write_to_directory(lxmdir)
425
+ RNS.log(
426
+ "Received " + str(lxm) + " written to " + str(written_path), RNS.LOG_DEBUG
427
+ )
428
+
429
+ if active_configuration["on_inbound"]:
430
+ RNS.log("Calling external program to handle message", RNS.LOG_DEBUG)
431
+ command = active_configuration["on_inbound"]
432
+ processing_command = command + ' "' + written_path + '"'
433
+ return_code = subprocess.call(
434
+ shlex.split(processing_command),
435
+ stdout=subprocess.DEVNULL,
436
+ stderr=subprocess.DEVNULL,
437
+ )
438
+
439
+ else:
440
+ RNS.log("No action defined for inbound messages, ignoring", RNS.LOG_DEBUG)
441
+
442
+ except Exception as e:
443
+ RNS.log(
444
+ "Error occurred while processing received message "
445
+ + str(lxm)
446
+ + ". The contained exception was: "
447
+ + str(e),
448
+ RNS.LOG_ERROR,
449
+ )
450
+
451
+
452
+ def program_setup(
453
+ configdir=None,
454
+ rnsconfigdir=None,
455
+ run_pn=False,
456
+ on_inbound=None,
457
+ verbosity=0,
458
+ quietness=0,
459
+ service=False,
460
+ ):
461
+ global configpath, ignoredpath, identitypath, allowedpath, storagedir, lxmdir
462
+ global lxmd_config, active_configuration, targetloglevel
463
+ global message_router, lxmf_destination
464
+
465
+ if service:
466
+ targetlogdest = RNS.LOG_FILE
467
+ targetloglevel = None
468
+ else:
469
+ targetlogdest = RNS.LOG_STDOUT
470
+
471
+ # Get configuration
472
+ if configdir is None:
473
+ configdir = _default_config_directory()
474
+
475
+ configdir = str(configdir)
476
+
477
+ configpath = configdir + "/config"
478
+ ignoredpath = configdir + "/ignored"
479
+ allowedpath = configdir + "/allowed"
480
+ identitypath = configdir + "/identity"
481
+ storagedir = configdir + "/storage"
482
+ lxmdir = storagedir + "/messages"
483
+
484
+ if not os.path.isdir(storagedir):
485
+ os.makedirs(storagedir)
486
+
487
+ if not os.path.isdir(lxmdir):
488
+ os.makedirs(lxmdir)
489
+
490
+ if not os.path.isfile(configpath):
491
+ RNS.log("Could not load config file, creating default configuration file...")
492
+ create_default_config(configpath)
493
+ RNS.log(
494
+ "Default config file created. Make any necessary changes in "
495
+ + configpath
496
+ + " and restart lxmd if needed."
497
+ )
498
+ time.sleep(1.5)
499
+
500
+ if os.path.isfile(configpath):
501
+ try:
502
+ lxmd_config = ConfigObj(configpath)
503
+ except Exception as e:
504
+ RNS.log("Could not parse the configuration at " + configpath, RNS.LOG_ERROR)
505
+ RNS.log("Check your configuration file for errors!", RNS.LOG_ERROR)
506
+ RNS.panic()
507
+
508
+ apply_config()
509
+ RNS.log("Configuration loaded from " + configpath, RNS.LOG_VERBOSE)
510
+
511
+ if targetloglevel is None:
512
+ targetloglevel = 3
513
+
514
+ if verbosity != 0 or quietness != 0:
515
+ targetloglevel = targetloglevel + verbosity - quietness
516
+
517
+ # Start Reticulum
518
+ RNS.log("Substantiating Reticulum...")
519
+ reticulum = RNS.Reticulum(
520
+ configdir=rnsconfigdir, loglevel=targetloglevel, logdest=targetlogdest
521
+ )
522
+
523
+ # Generate or load primary identity
524
+ if os.path.isfile(identitypath):
525
+ try:
526
+ identity = RNS.Identity.from_file(identitypath)
527
+ if identity is not None:
528
+ RNS.log("Loaded Primary Identity %s" % (str(identity)))
529
+ else:
530
+ RNS.log(
531
+ "Could not load the Primary Identity from " + identitypath,
532
+ RNS.LOG_ERROR,
533
+ )
534
+ exit(4)
535
+ except Exception as e:
536
+ RNS.log(
537
+ "Could not load the Primary Identity from " + identitypath,
538
+ RNS.LOG_ERROR,
539
+ )
540
+ RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
541
+ exit(1)
542
+ else:
543
+ try:
544
+ RNS.log("No Primary Identity file found, creating new...")
545
+ identity = RNS.Identity()
546
+ identity.to_file(identitypath)
547
+ RNS.log("Created new Primary Identity %s" % (str(identity)))
548
+ except Exception as e:
549
+ RNS.log("Could not create and save a new Primary Identity", RNS.LOG_ERROR)
550
+ RNS.log("The contained exception was: %s" % (str(e)), RNS.LOG_ERROR)
551
+ exit(2)
552
+
553
+ # Start LXMF
554
+ message_router = LXMF.LXMRouter(
555
+ identity=identity,
556
+ storagepath=storagedir,
557
+ autopeer=active_configuration["autopeer"],
558
+ autopeer_maxdepth=active_configuration["autopeer_maxdepth"],
559
+ propagation_limit=active_configuration[
560
+ "propagation_transfer_max_accepted_size"
561
+ ],
562
+ propagation_cost=active_configuration["propagation_stamp_cost_target"],
563
+ propagation_cost_flexibility=active_configuration[
564
+ "propagation_stamp_cost_flexibility"
565
+ ],
566
+ peering_cost=active_configuration["peering_cost"],
567
+ max_peering_cost=active_configuration["remote_peering_cost_max"],
568
+ sync_limit=active_configuration["propagation_sync_max_accepted_size"],
569
+ delivery_limit=active_configuration["delivery_transfer_max_accepted_size"],
570
+ max_peers=active_configuration["max_peers"],
571
+ static_peers=active_configuration["static_peers"],
572
+ from_static_only=active_configuration["from_static_only"],
573
+ name=active_configuration["node_name"],
574
+ )
575
+
576
+ message_router.register_delivery_callback(lxmf_delivery)
577
+
578
+ for destination_hash in active_configuration["ignored_lxmf_destinations"]:
579
+ message_router.ignore_destination(destination_hash)
580
+
581
+ lxmf_destination = message_router.register_delivery_identity(
582
+ identity, display_name=active_configuration["display_name"]
583
+ )
584
+
585
+ RNS.Identity.remember(
586
+ packet_hash=None,
587
+ destination_hash=lxmf_destination.hash,
588
+ public_key=identity.get_public_key(),
589
+ app_data=None,
590
+ )
591
+
592
+ # Set up authentication
593
+ if active_configuration["auth_required"]:
594
+ message_router.set_authentication(required=True)
595
+
596
+ if len(active_configuration["allowed_identities"]) == 0:
597
+ RNS.log(
598
+ "Clint authentication was enabled, but no identity hashes could be loaded from "
599
+ + str(allowedpath)
600
+ + ". Nobody will be able to sync messages from this propagation node.",
601
+ RNS.LOG_WARNING,
602
+ )
603
+
604
+ for identity_hash in active_configuration["allowed_identities"]:
605
+ message_router.allow(identity_hash)
606
+
607
+ RNS.log(
608
+ "LXMF Router ready to receive on " + RNS.prettyhexrep(lxmf_destination.hash)
609
+ )
610
+
611
+ if run_pn or active_configuration["enable_propagation_node"]:
612
+ message_router.set_message_storage_limit(
613
+ megabytes=active_configuration["message_storage_limit"]
614
+ )
615
+ for dest_str in active_configuration["prioritised_lxmf_destinations"]:
616
+ try:
617
+ dest_hash = bytes.fromhex(dest_str)
618
+ if len(dest_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH // 8:
619
+ message_router.prioritise(dest_hash)
620
+ except Exception as e:
621
+ RNS.log(
622
+ "Cannot prioritise "
623
+ + str(dest_str)
624
+ + ", it is not a valid destination hash",
625
+ RNS.LOG_ERROR,
626
+ )
627
+
628
+ message_router.enable_propagation()
629
+
630
+ for ident_str in active_configuration["control_allowed_identities"]:
631
+ try:
632
+ identity_hash = bytes.fromhex(ident_str)
633
+ if len(identity_hash) == RNS.Reticulum.TRUNCATED_HASHLENGTH // 8:
634
+ message_router.allow_control(identity_hash)
635
+ except Exception as e:
636
+ RNS.log(
637
+ f"Cannot allow control from {ident_str}, it is not a valid identity hash",
638
+ RNS.LOG_ERROR,
639
+ )
640
+
641
+ RNS.log(
642
+ "LXMF Propagation Node started on "
643
+ + RNS.prettyhexrep(message_router.propagation_destination.hash)
644
+ )
645
+
646
+ RNS.log(
647
+ "Started lxmd version {version}".format(version=__version__), RNS.LOG_NOTICE
648
+ )
649
+
650
+ threading.Thread(target=deferred_start_jobs, daemon=True).start()
651
+
652
+ while True:
653
+ time.sleep(1)
654
+
655
+
656
+ def jobs():
657
+ global active_configuration, last_peer_announce, last_node_announce
658
+ global message_router, lxmf_destination
659
+
660
+ while True:
661
+ try:
662
+ if (
663
+ "peer_announce_interval" in active_configuration
664
+ and active_configuration["peer_announce_interval"] is not None
665
+ ):
666
+ if (
667
+ time.time()
668
+ > last_peer_announce
669
+ + active_configuration["peer_announce_interval"]
670
+ ):
671
+ RNS.log(
672
+ "Sending announce for LXMF delivery destination",
673
+ RNS.LOG_VERBOSE,
674
+ )
675
+ message_router.announce(lxmf_destination.hash)
676
+ last_peer_announce = time.time()
677
+
678
+ if (
679
+ "node_announce_interval" in active_configuration
680
+ and active_configuration["node_announce_interval"] is not None
681
+ ):
682
+ if (
683
+ time.time()
684
+ > last_node_announce
685
+ + active_configuration["node_announce_interval"]
686
+ ):
687
+ RNS.log(
688
+ "Sending announce for LXMF Propagation Node", RNS.LOG_VERBOSE
689
+ )
690
+ message_router.announce_propagation_node()
691
+ last_node_announce = time.time()
692
+
693
+ except Exception as e:
694
+ RNS.log(
695
+ "An error occurred while running periodic jobs. The contained exception was: "
696
+ + str(e),
697
+ RNS.LOG_ERROR,
698
+ )
699
+
700
+ time.sleep(JOBS_INTERVAL)
701
+
702
+
703
+ def deferred_start_jobs():
704
+ global active_configuration, last_peer_announce, last_node_announce
705
+ global message_router, lxmf_destination
706
+ time.sleep(DEFFERED_JOBS_DELAY)
707
+ RNS.log("Running deferred start jobs", RNS.LOG_DEBUG)
708
+ if active_configuration["peer_announce_at_start"]:
709
+ RNS.log("Sending announce for LXMF delivery destination", RNS.LOG_EXTREME)
710
+ message_router.announce(lxmf_destination.hash)
711
+
712
+ if active_configuration["node_announce_at_start"]:
713
+ RNS.log("Sending announce for LXMF Propagation Node", RNS.LOG_EXTREME)
714
+ message_router.announce_propagation_node()
715
+
716
+ last_peer_announce = time.time()
717
+ last_node_announce = time.time()
718
+ threading.Thread(target=jobs, daemon=True).start()
719
+
720
+
721
+ def _request_sync(
722
+ identity, destination_hash, remote_identity, timeout=15, exit_on_fail=False
723
+ ):
724
+ control_destination = RNS.Destination(
725
+ remote_identity,
726
+ RNS.Destination.OUT,
727
+ RNS.Destination.SINGLE,
728
+ APP_NAME,
729
+ "propagation",
730
+ "control",
731
+ )
732
+
733
+ timeout = time.time() + timeout
734
+
735
+ def check_timeout():
736
+ if time.time() > timeout:
737
+ if exit_on_fail:
738
+ print("Requesting lxmd peer sync timed out, exiting now")
739
+ exit(200)
740
+ else:
741
+ return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
742
+ else:
743
+ time.sleep(0.1)
744
+
745
+ if not RNS.Transport.has_path(control_destination.hash):
746
+ RNS.Transport.request_path(control_destination.hash)
747
+ while not RNS.Transport.has_path(control_destination.hash):
748
+ tc = check_timeout()
749
+ if tc:
750
+ return tc
751
+
752
+ link = RNS.Link(control_destination)
753
+ while not link.status == RNS.Link.ACTIVE:
754
+ tc = check_timeout()
755
+ if tc:
756
+ return tc
757
+
758
+ link.identify(identity)
759
+ request_receipt = link.request(
760
+ LXMF.LXMRouter.SYNC_REQUEST_PATH,
761
+ data=destination_hash,
762
+ response_callback=None,
763
+ failed_callback=None,
764
+ )
765
+ while not request_receipt.get_status() == RNS.RequestReceipt.READY:
766
+ tc = check_timeout()
767
+ if tc:
768
+ return tc
769
+
770
+ link.teardown()
771
+ return request_receipt.get_response()
772
+
773
+
774
+ def request_sync(
775
+ target,
776
+ remote=None,
777
+ configdir=None,
778
+ rnsconfigdir=None,
779
+ verbosity=0,
780
+ quietness=0,
781
+ timeout=15,
782
+ identity_path=None,
783
+ ):
784
+ global configpath, identitypath, storagedir, lxmdir
785
+ global lxmd_config, active_configuration, targetloglevel
786
+
787
+ try:
788
+ peer_destination_hash = bytes.fromhex(target)
789
+ if len(peer_destination_hash) != RNS.Identity.TRUNCATED_HASHLENGTH // 8:
790
+ raise ValueError(
791
+ f"Destination hash length must be {RNS.Identity.TRUNCATED_HASHLENGTH//8*2} characters"
792
+ )
793
+ except Exception as e:
794
+ print(f"Invalid peer destination hash: {e}")
795
+ exit(203)
796
+ remote
797
+ _remote_init(configdir, rnsconfigdir, verbosity, quietness, identity_path)
798
+ response = _request_sync(
799
+ identity,
800
+ peer_destination_hash,
801
+ remote_identity=_get_target_identity(remote),
802
+ timeout=timeout,
803
+ exit_on_fail=True,
804
+ )
805
+
806
+ if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
807
+ print("Remote received no identity")
808
+ exit(203)
809
+ elif response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
810
+ print("Access denied")
811
+ exit(204)
812
+ elif response == LXMF.LXMPeer.LXMPeer.ERROR_INVALID_DATA:
813
+ print("Invalid data received by remote")
814
+ exit(205)
815
+ elif response == LXMF.LXMPeer.LXMPeer.ERROR_NOT_FOUND:
816
+ print("The requested peer was not found")
817
+ exit(206)
818
+ elif response is None:
819
+ print("Empty response received")
820
+ exit(207)
821
+ else:
822
+ print(f"Sync requested for peer {RNS.prettyhexrep(peer_destination_hash)}")
823
+ exit(0)
824
+
825
+
826
+ def _request_unpeer(
827
+ identity, destination_hash, remote_identity, timeout=15, exit_on_fail=False
828
+ ):
829
+ control_destination = RNS.Destination(
830
+ remote_identity,
831
+ RNS.Destination.OUT,
832
+ RNS.Destination.SINGLE,
833
+ APP_NAME,
834
+ "propagation",
835
+ "control",
836
+ )
837
+
838
+ timeout = time.time() + timeout
839
+
840
+ def check_timeout():
841
+ if time.time() > timeout:
842
+ if exit_on_fail:
843
+ print("Requesting lxmd peering break timed out, exiting now")
844
+ exit(200)
845
+ else:
846
+ return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
847
+ else:
848
+ time.sleep(0.1)
849
+
850
+ if not RNS.Transport.has_path(control_destination.hash):
851
+ RNS.Transport.request_path(control_destination.hash)
852
+ while not RNS.Transport.has_path(control_destination.hash):
853
+ tc = check_timeout()
854
+ if tc:
855
+ return tc
856
+
857
+ link = RNS.Link(control_destination)
858
+ while not link.status == RNS.Link.ACTIVE:
859
+ tc = check_timeout()
860
+ if tc:
861
+ return tc
862
+
863
+ link.identify(identity)
864
+ request_receipt = link.request(
865
+ LXMF.LXMRouter.UNPEER_REQUEST_PATH,
866
+ data=destination_hash,
867
+ response_callback=None,
868
+ failed_callback=None,
869
+ )
870
+ while not request_receipt.get_status() == RNS.RequestReceipt.READY:
871
+ tc = check_timeout()
872
+ if tc:
873
+ return tc
874
+
875
+ link.teardown()
876
+ return request_receipt.get_response()
877
+
878
+
879
+ def request_unpeer(
880
+ target,
881
+ remote=None,
882
+ configdir=None,
883
+ rnsconfigdir=None,
884
+ verbosity=0,
885
+ quietness=0,
886
+ timeout=15,
887
+ identity_path=None,
888
+ ):
889
+ global configpath, identitypath, storagedir, lxmdir
890
+ global lxmd_config, active_configuration, targetloglevel
891
+
892
+ try:
893
+ peer_destination_hash = bytes.fromhex(target)
894
+ if len(peer_destination_hash) != RNS.Identity.TRUNCATED_HASHLENGTH // 8:
895
+ raise ValueError(
896
+ f"Destination hash length must be {RNS.Identity.TRUNCATED_HASHLENGTH//8*2} characters"
897
+ )
898
+ except Exception as e:
899
+ print(f"Invalid peer destination hash: {e}")
900
+ exit(203)
901
+ remote
902
+ _remote_init(configdir, rnsconfigdir, verbosity, quietness, identity_path)
903
+ response = _request_unpeer(
904
+ identity,
905
+ peer_destination_hash,
906
+ remote_identity=_get_target_identity(remote),
907
+ timeout=timeout,
908
+ exit_on_fail=True,
909
+ )
910
+
911
+ if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
912
+ print("Remote received no identity")
913
+ exit(203)
914
+ elif response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
915
+ print("Access denied")
916
+ exit(204)
917
+ elif response == LXMF.LXMPeer.LXMPeer.ERROR_INVALID_DATA:
918
+ print("Invalid data received by remote")
919
+ exit(205)
920
+ elif response == LXMF.LXMPeer.LXMPeer.ERROR_NOT_FOUND:
921
+ print("The requested peer was not found")
922
+ exit(206)
923
+ elif response is None:
924
+ print("Empty response received")
925
+ exit(207)
926
+ else:
927
+ print(f"Broke peering with {RNS.prettyhexrep(peer_destination_hash)}")
928
+ exit(0)
929
+
930
+
931
+ def query_status(identity, remote_identity=None, timeout=5, exit_on_fail=False):
932
+ if remote_identity is None:
933
+ remote_identity = identity
934
+ control_destination = RNS.Destination(
935
+ remote_identity,
936
+ RNS.Destination.OUT,
937
+ RNS.Destination.SINGLE,
938
+ APP_NAME,
939
+ "propagation",
940
+ "control",
941
+ )
942
+
943
+ timeout = time.time() + timeout
944
+
945
+ def check_timeout():
946
+ if time.time() > timeout:
947
+ if exit_on_fail:
948
+ print("Getting lxmd statistics timed out, exiting now")
949
+ exit(200)
950
+ else:
951
+ return LXMF.LXMPeer.LXMPeer.ERROR_TIMEOUT
952
+ else:
953
+ time.sleep(0.1)
954
+
955
+ if not RNS.Transport.has_path(control_destination.hash):
956
+ RNS.Transport.request_path(control_destination.hash)
957
+ while not RNS.Transport.has_path(control_destination.hash):
958
+ tc = check_timeout()
959
+ if tc:
960
+ return tc
961
+
962
+ link = RNS.Link(control_destination)
963
+ while not link.status == RNS.Link.ACTIVE:
964
+ tc = check_timeout()
965
+ if tc:
966
+ return tc
967
+
968
+ link.identify(identity)
969
+ request_receipt = link.request(
970
+ LXMF.LXMRouter.STATS_GET_PATH,
971
+ data=None,
972
+ response_callback=None,
973
+ failed_callback=None,
974
+ )
975
+ while not request_receipt.get_status() == RNS.RequestReceipt.READY:
976
+ tc = check_timeout()
977
+ if tc:
978
+ return tc
979
+
980
+ link.teardown()
981
+ return request_receipt.get_response()
982
+
983
+
984
+ def get_status(
985
+ remote=None,
986
+ configdir=None,
987
+ rnsconfigdir=None,
988
+ verbosity=0,
989
+ quietness=0,
990
+ timeout=5,
991
+ show_status=False,
992
+ show_peers=False,
993
+ identity_path=None,
994
+ ):
995
+
996
+ global identity
997
+ _remote_init(configdir, rnsconfigdir, verbosity, quietness, identity_path)
998
+ response = query_status(
999
+ identity,
1000
+ remote_identity=_get_target_identity(remote),
1001
+ timeout=timeout,
1002
+ exit_on_fail=True,
1003
+ )
1004
+
1005
+ if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_IDENTITY:
1006
+ print("Remote received no identity")
1007
+ exit(203)
1008
+ if response == LXMF.LXMPeer.LXMPeer.ERROR_NO_ACCESS:
1009
+ print("Access denied")
1010
+ exit(204)
1011
+ elif response is None:
1012
+ print("Empty response received")
1013
+ exit(207)
1014
+ else:
1015
+ s = response
1016
+ mutil = round(
1017
+ (s["messagestore"]["bytes"] / s["messagestore"]["limit"]) * 100, 2
1018
+ )
1019
+ ms_util = f"{mutil}%"
1020
+ if s["from_static_only"]:
1021
+ who_str = "static peers only"
1022
+ else:
1023
+ who_str = "all nodes"
1024
+
1025
+ available_peers = 0
1026
+ unreachable_peers = 0
1027
+ peered_incoming = 0
1028
+ peered_outgoing = 0
1029
+ peered_rx_bytes = 0
1030
+ peered_tx_bytes = 0
1031
+ for peer_id in s["peers"]:
1032
+ p = s["peers"][peer_id]
1033
+ pm = p["messages"]
1034
+ peered_incoming += pm["incoming"]
1035
+ peered_outgoing += pm["outgoing"]
1036
+ peered_rx_bytes += p["rx_bytes"]
1037
+ peered_tx_bytes += p["tx_bytes"]
1038
+
1039
+ if p["alive"]:
1040
+ available_peers += 1
1041
+ else:
1042
+ unreachable_peers += 1
1043
+
1044
+ total_incoming = (
1045
+ peered_incoming
1046
+ + s["unpeered_propagation_incoming"]
1047
+ + s["clients"]["client_propagation_messages_received"]
1048
+ )
1049
+ total_rx_bytes = peered_rx_bytes + s["unpeered_propagation_rx_bytes"]
1050
+ if total_incoming != 0:
1051
+ df = round(peered_outgoing / total_incoming, 2)
1052
+ else:
1053
+ df = 0
1054
+
1055
+ dhs = RNS.prettyhexrep(s["destination_hash"])
1056
+ uts = RNS.prettytime(s["uptime"])
1057
+ print(f"\nLXMF Propagation Node running on {dhs}, uptime is {uts}")
1058
+
1059
+ if show_status:
1060
+ msb = RNS.prettysize(s["messagestore"]["bytes"])
1061
+ msl = RNS.prettysize(s["messagestore"]["limit"])
1062
+ ptl = RNS.prettysize(s["propagation_limit"] * 1000)
1063
+ psl = RNS.prettysize(s["sync_limit"] * 1000)
1064
+ uprx = RNS.prettysize(s["unpeered_propagation_rx_bytes"])
1065
+ mscnt = s["messagestore"]["count"]
1066
+ stp = s["total_peers"]
1067
+ smp = s["max_peers"]
1068
+ sdp = s["discovered_peers"]
1069
+ ssp = s["static_peers"]
1070
+ cprr = s["clients"]["client_propagation_messages_received"]
1071
+ cprs = s["clients"]["client_propagation_messages_served"]
1072
+ upi = s["unpeered_propagation_incoming"]
1073
+ psc = s["target_stamp_cost"]
1074
+ scf = s["stamp_cost_flexibility"]
1075
+ pc = s["peering_cost"]
1076
+ pcm = s["max_peering_cost"]
1077
+ print(
1078
+ f"Messagestore contains {mscnt} messages, {msb} ({ms_util} utilised of {msl})"
1079
+ )
1080
+ print(f"Required propagation stamp cost is {psc}, flexibility is {scf}")
1081
+ print(f"Peering cost is {pc}, max remote peering cost is {pcm}")
1082
+ print(f"Accepting propagated messages from {who_str}")
1083
+ print(f"{ptl} message limit, {psl} sync limit")
1084
+ print(f"")
1085
+ print(f"Peers : {stp} total (peer limit is {smp})")
1086
+ print(f" {sdp} discovered, {ssp} static")
1087
+ print(
1088
+ f" {available_peers} available, {unreachable_peers} unreachable"
1089
+ )
1090
+ print(f"")
1091
+ print(
1092
+ f"Traffic : {total_incoming} messages received in total ({RNS.prettysize(total_rx_bytes)})"
1093
+ )
1094
+ print(
1095
+ f" {peered_incoming} messages received from peered nodes ({RNS.prettysize(peered_rx_bytes)})"
1096
+ )
1097
+ print(f" {upi} messages received from unpeered nodes ({uprx})")
1098
+ print(
1099
+ f" {peered_outgoing} messages transferred to peered nodes ({RNS.prettysize(peered_tx_bytes)})"
1100
+ )
1101
+ print(
1102
+ f" {cprr} propagation messages received directly from clients"
1103
+ )
1104
+ print(f" {cprs} propagation messages served to clients")
1105
+ print(f" Distribution factor is {df}")
1106
+ print(f"")
1107
+
1108
+ if show_peers:
1109
+ if not show_status:
1110
+ print("")
1111
+
1112
+ for peer_id in s["peers"]:
1113
+ ind = " "
1114
+ p = s["peers"][peer_id]
1115
+ if p["type"] == "static":
1116
+ t = "Static peer "
1117
+ elif p["type"] == "discovered":
1118
+ t = "Discovered peer "
1119
+ else:
1120
+ t = "Unknown peer "
1121
+ a = "Available" if p["alive"] == True else "Unreachable"
1122
+ h = max(time.time() - p["last_heard"], 0)
1123
+ hops = p["network_distance"]
1124
+ hs = (
1125
+ "hops unknown"
1126
+ if hops == RNS.Transport.PATHFINDER_M
1127
+ else f"{hops} hop away" if hops == 1 else f"{hops} hops away"
1128
+ )
1129
+ pm = p["messages"]
1130
+ pk = p["peering_key"]
1131
+ pc = p["peering_cost"]
1132
+ psc = p["target_stamp_cost"]
1133
+ psf = p["stamp_cost_flexibility"]
1134
+ if pc is None:
1135
+ pc = "unknown"
1136
+ if psc is None:
1137
+ psc = "unknown"
1138
+ if psf is None:
1139
+ psf = "unknown"
1140
+ if pk is None:
1141
+ pk = "Not generated"
1142
+ else:
1143
+ pk = f"Generated, value is {pk}"
1144
+ if p["last_sync_attempt"] != 0:
1145
+ lsa = p["last_sync_attempt"]
1146
+ ls = f"last synced {RNS.prettytime(max(time.time()-lsa, 0))} ago"
1147
+ else:
1148
+ ls = "never synced"
1149
+
1150
+ sstr = RNS.prettyspeed(p["str"])
1151
+ sler = RNS.prettyspeed(p["ler"])
1152
+ stl = (
1153
+ RNS.prettysize(p["transfer_limit"] * 1000)
1154
+ if p["transfer_limit"]
1155
+ else "Unknown"
1156
+ )
1157
+ ssl = (
1158
+ RNS.prettysize(p["sync_limit"] * 1000)
1159
+ if p["sync_limit"]
1160
+ else "unknown"
1161
+ )
1162
+ srxb = RNS.prettysize(p["rx_bytes"])
1163
+ stxb = RNS.prettysize(p["tx_bytes"])
1164
+ pmo = pm["offered"]
1165
+ pmout = pm["outgoing"]
1166
+ pmi = pm["incoming"]
1167
+ pmuh = pm["unhandled"]
1168
+ ar = round(p["acceptance_rate"] * 100, 2)
1169
+ if p["name"] is None:
1170
+ nn = ""
1171
+ else:
1172
+ nn = p["name"].strip().replace("\n", "").replace("\r", "")
1173
+ if len(nn) > 45:
1174
+ nn = f"{nn[:45]}..."
1175
+ print(f"{ind}{t}{RNS.prettyhexrep(peer_id)}")
1176
+ if len(nn):
1177
+ print(f"{ind*2}Name : {nn}")
1178
+ print(
1179
+ f"{ind*2}Status : {a}, {hs}, last heard {RNS.prettytime(h)} ago"
1180
+ )
1181
+ print(
1182
+ f"{ind*2}Costs : Propagation {psc} (flex {psf}), peering {pc}"
1183
+ )
1184
+ print(f"{ind*2}Sync key : {pk}")
1185
+ print(f"{ind*2}Speeds : {sstr} STR, {sler} LER")
1186
+ print(f"{ind*2}Limits : {stl} message limit, {ssl} sync limit")
1187
+ print(
1188
+ f"{ind*2}Messages : {pmo} offered, {pmout} outgoing, {pmi} incoming, {ar}% acceptance rate"
1189
+ )
1190
+ print(f"{ind*2}Traffic : {srxb} received, {stxb} sent")
1191
+ ms = "" if pm["unhandled"] == 1 else "s"
1192
+ print(f"{ind*2}Sync state : {pmuh} unhandled message{ms}, {ls}")
1193
+ print("")
1194
+
1195
+
1196
+ def _get_target_identity(remote=None, timeout=5):
1197
+ global identity
1198
+ timeout = time.time() + timeout
1199
+
1200
+ def check_timeout():
1201
+ if time.time() > timeout:
1202
+ print("Resolving remote identity timed out, exiting now")
1203
+ exit(200)
1204
+ else:
1205
+ time.sleep(0.1)
1206
+
1207
+ if remote is None:
1208
+ return identity
1209
+ else:
1210
+ try:
1211
+ destination_hash = bytes.fromhex(remote)
1212
+ if len(destination_hash) != RNS.Identity.TRUNCATED_HASHLENGTH // 8:
1213
+ raise ValueError(
1214
+ f"Destination hash length must be {RNS.Identity.TRUNCATED_HASHLENGTH//8*2} characters"
1215
+ )
1216
+ except Exception as e:
1217
+ print(f"Invalid remote destination hash: {e}")
1218
+ exit(203)
1219
+
1220
+ remote_identity = RNS.Identity.recall(destination_hash)
1221
+ if remote_identity:
1222
+ return remote_identity
1223
+ else:
1224
+ if not RNS.Transport.has_path(destination_hash):
1225
+ RNS.Transport.request_path(destination_hash)
1226
+ while not RNS.Transport.has_path(destination_hash):
1227
+ check_timeout()
1228
+
1229
+ return RNS.Identity.recall(destination_hash)
1230
+
1231
+
1232
+ def _remote_init(
1233
+ configdir=None, rnsconfigdir=None, verbosity=0, quietness=0, identity_path=None
1234
+ ):
1235
+ global configpath, identitypath, storagedir, lxmdir, identity
1236
+ global lxmd_config, active_configuration, targetloglevel
1237
+ targetlogdest = RNS.LOG_STDOUT
1238
+
1239
+ if identity_path is None:
1240
+ if configdir is None:
1241
+ configdir = _default_config_directory()
1242
+
1243
+ configdir = str(configdir)
1244
+
1245
+ configpath = configdir + "/config"
1246
+ identitypath = configdir + "/identity"
1247
+ identity = None
1248
+
1249
+ if not os.path.isdir(configdir):
1250
+ RNS.log(
1251
+ "Specified configuration directory does not exist, exiting now",
1252
+ RNS.LOG_ERROR,
1253
+ )
1254
+ exit(201)
1255
+ if not os.path.isfile(identitypath):
1256
+ RNS.log(
1257
+ "Identity file not found in specified configuration directory, exiting now",
1258
+ RNS.LOG_ERROR,
1259
+ )
1260
+ exit(202)
1261
+ else:
1262
+ identity = RNS.Identity.from_file(identitypath)
1263
+ if identity is None:
1264
+ RNS.log(
1265
+ "Could not load the Primary Identity from " + identitypath,
1266
+ RNS.LOG_ERROR,
1267
+ )
1268
+ exit(4)
1269
+
1270
+ else:
1271
+ if not os.path.isfile(identity_path):
1272
+ RNS.log(
1273
+ "Identity file not found in specified configuration directory, exiting now",
1274
+ RNS.LOG_ERROR,
1275
+ )
1276
+ exit(202)
1277
+ else:
1278
+ identity = RNS.Identity.from_file(identity_path)
1279
+ if identity is None:
1280
+ RNS.log(
1281
+ "Could not load the Primary Identity from " + identity_path,
1282
+ RNS.LOG_ERROR,
1283
+ )
1284
+ exit(4)
1285
+
1286
+ if targetloglevel is None:
1287
+ targetloglevel = 3
1288
+ if verbosity != 0 or quietness != 0:
1289
+ targetloglevel = targetloglevel + verbosity - quietness
1290
+
1291
+ reticulum = RNS.Reticulum(
1292
+ configdir=rnsconfigdir, loglevel=targetloglevel, logdest=targetlogdest
1293
+ )
1294
+
1295
+
1296
+ def main():
1297
+ try:
1298
+ parser = argparse.ArgumentParser(
1299
+ description="Lightweight Extensible Messaging Daemon"
1300
+ )
1301
+ parser.add_argument(
1302
+ "--config",
1303
+ action="store",
1304
+ default=None,
1305
+ help="path to alternative lxmd config directory",
1306
+ type=str,
1307
+ )
1308
+ parser.add_argument(
1309
+ "--rnsconfig",
1310
+ action="store",
1311
+ default=None,
1312
+ help="path to alternative Reticulum config directory",
1313
+ type=str,
1314
+ )
1315
+ parser.add_argument(
1316
+ "-p",
1317
+ "--propagation-node",
1318
+ action="store_true",
1319
+ default=False,
1320
+ help="run an LXMF Propagation Node",
1321
+ )
1322
+ parser.add_argument(
1323
+ "-i",
1324
+ "--on-inbound",
1325
+ action="store",
1326
+ metavar="PATH",
1327
+ default=None,
1328
+ help="executable to run when a message is received",
1329
+ type=str,
1330
+ )
1331
+ parser.add_argument("-v", "--verbose", action="count", default=0)
1332
+ parser.add_argument("-q", "--quiet", action="count", default=0)
1333
+ parser.add_argument(
1334
+ "-s",
1335
+ "--service",
1336
+ action="store_true",
1337
+ default=False,
1338
+ help="lxmd is running as a service and should log to file",
1339
+ )
1340
+ parser.add_argument(
1341
+ "--status", action="store_true", default=False, help="display node status"
1342
+ )
1343
+ parser.add_argument(
1344
+ "--peers", action="store_true", default=False, help="display peered nodes"
1345
+ )
1346
+ parser.add_argument(
1347
+ "--sync",
1348
+ action="store",
1349
+ default=None,
1350
+ help="request a sync with the specified peer",
1351
+ type=str,
1352
+ )
1353
+ parser.add_argument(
1354
+ "-b",
1355
+ "--break",
1356
+ dest="unpeer",
1357
+ action="store",
1358
+ default=None,
1359
+ help="break peering with the specified peer",
1360
+ type=str,
1361
+ )
1362
+ parser.add_argument(
1363
+ "--timeout",
1364
+ action="store",
1365
+ default=None,
1366
+ help="timeout in seconds for query operations",
1367
+ type=float,
1368
+ )
1369
+ parser.add_argument(
1370
+ "-r",
1371
+ "--remote",
1372
+ action="store",
1373
+ default=None,
1374
+ help="remote propagation node destination hash",
1375
+ type=str,
1376
+ )
1377
+ parser.add_argument(
1378
+ "--identity",
1379
+ action="store",
1380
+ default=None,
1381
+ help="path to identity used for remote requests",
1382
+ type=str,
1383
+ )
1384
+ parser.add_argument(
1385
+ "--exampleconfig",
1386
+ action="store_true",
1387
+ default=False,
1388
+ help="print verbose configuration example to stdout and exit",
1389
+ )
1390
+ parser.add_argument(
1391
+ "--version",
1392
+ action="version",
1393
+ version="lxmd {version}".format(version=__version__),
1394
+ )
1395
+
1396
+ args = parser.parse_args()
1397
+
1398
+ if args.exampleconfig:
1399
+ print(__default_lxmd_config__)
1400
+ exit()
1401
+
1402
+ if args.status or args.peers:
1403
+ if not args.timeout:
1404
+ args.timeout = 5
1405
+ get_status(
1406
+ configdir=args.config,
1407
+ rnsconfigdir=args.rnsconfig,
1408
+ verbosity=args.verbose,
1409
+ quietness=args.quiet,
1410
+ timeout=args.timeout,
1411
+ show_status=args.status,
1412
+ show_peers=args.peers,
1413
+ identity_path=args.identity,
1414
+ remote=args.remote,
1415
+ )
1416
+ exit()
1417
+
1418
+ if args.sync:
1419
+ if not args.timeout:
1420
+ args.timeout = 10
1421
+ request_sync(
1422
+ target=args.sync,
1423
+ configdir=args.config,
1424
+ rnsconfigdir=args.rnsconfig,
1425
+ verbosity=args.verbose,
1426
+ quietness=args.quiet,
1427
+ timeout=args.timeout,
1428
+ identity_path=args.identity,
1429
+ remote=args.remote,
1430
+ )
1431
+ exit()
1432
+
1433
+ if args.unpeer:
1434
+ if not args.timeout:
1435
+ args.timeout = 10
1436
+ request_unpeer(
1437
+ target=args.unpeer,
1438
+ configdir=args.config,
1439
+ rnsconfigdir=args.rnsconfig,
1440
+ verbosity=args.verbose,
1441
+ quietness=args.quiet,
1442
+ timeout=args.timeout,
1443
+ identity_path=args.identity,
1444
+ remote=args.remote,
1445
+ )
1446
+ exit()
1447
+
1448
+ program_setup(
1449
+ configdir=args.config,
1450
+ rnsconfigdir=args.rnsconfig,
1451
+ run_pn=args.propagation_node,
1452
+ on_inbound=args.on_inbound,
1453
+ verbosity=args.verbose,
1454
+ quietness=args.quiet,
1455
+ service=args.service,
1456
+ )
1457
+
1458
+ except KeyboardInterrupt:
1459
+ print("")
1460
+ exit()
1461
+
1462
+
1463
+ __default_lxmd_config__ = """# This is an example LXM Daemon config file.
1464
+ # You should probably edit it to suit your
1465
+ # intended usage.
1466
+
1467
+ [propagation]
1468
+
1469
+ # Whether to enable propagation node
1470
+
1471
+ enable_node = no
1472
+
1473
+ # You can specify identity hashes for remotes
1474
+ # that are allowed to control and query status
1475
+ # for this propagation node.
1476
+
1477
+ # control_allowed = 7d7e542829b40f32364499b27438dba8, 437229f8e29598b2282b88bad5e44698
1478
+
1479
+ # An optional name for this node, included
1480
+ # in announces.
1481
+
1482
+ # node_name = Anonymous Propagation Node
1483
+
1484
+ # Automatic announce interval in minutes.
1485
+ # 6 hours by default.
1486
+
1487
+ announce_interval = 360
1488
+
1489
+ # Whether to announce when the node starts.
1490
+
1491
+ announce_at_start = yes
1492
+
1493
+ # Wheter to automatically peer with other
1494
+ # propagation nodes on the network.
1495
+
1496
+ autopeer = yes
1497
+
1498
+ # The maximum peering depth (in hops) for
1499
+ # automatically peered nodes.
1500
+
1501
+ autopeer_maxdepth = 4
1502
+
1503
+ # The maximum amount of storage to use for
1504
+ # the LXMF Propagation Node message store,
1505
+ # specified in megabytes. When this limit
1506
+ # is reached, LXMF will periodically remove
1507
+ # messages in its message store. By default,
1508
+ # LXMF prioritises keeping messages that are
1509
+ # new and small. Large and old messages will
1510
+ # be removed first. This setting is optional
1511
+ # and defaults to 500 megabytes.
1512
+
1513
+ # message_storage_limit = 500
1514
+
1515
+ # The maximum accepted transfer size per in-
1516
+ # coming propagation message, in kilobytes.
1517
+ # This sets the upper limit for the size of
1518
+ # single messages accepted onto this node.
1519
+
1520
+ # propagation_message_max_accepted_size = 256
1521
+
1522
+ # The maximum accepted transfer size per in-
1523
+ # coming propagation node sync.
1524
+ #
1525
+ # If a node wants to propagate a larger number
1526
+ # of messages to this node, than what can fit
1527
+ # within this limit, it will prioritise sending
1528
+ # the smallest messages first, and try again
1529
+ # with any remaining messages at a later point.
1530
+
1531
+ # propagation_sync_max_accepted_size = 10240
1532
+
1533
+ # You can configure the target stamp cost
1534
+ # required to deliver messages via this node.
1535
+
1536
+ # propagation_stamp_cost_target = 16
1537
+
1538
+ # If set higher than 0, the stamp cost flexi-
1539
+ # bility option will make this node accept
1540
+ # messages with a lower stamp cost than the
1541
+ # target from other propagation nodes (but
1542
+ # not from peers directly). This allows the
1543
+ # network to gradually adjust stamp cost.
1544
+
1545
+ # propagation_stamp_cost_flexibility = 3
1546
+
1547
+ # The peering_cost option configures the target
1548
+ # value required for a remote node to peer with
1549
+ # and deliver messages to this node.
1550
+
1551
+ # peering_cost = 18
1552
+
1553
+ # You can configure the maximum peering cost
1554
+ # of remote nodes that this node will peer with.
1555
+ # Setting this to a higher number will allow
1556
+ # this node to peer with other nodes requiring
1557
+ # a higher peering key value, but will require
1558
+ # more computation time during initial peering
1559
+ # when generating the peering key.
1560
+
1561
+ # remote_peering_cost_max = 26
1562
+
1563
+ # You can tell the LXMF message router to
1564
+ # prioritise storage for one or more
1565
+ # destinations. If the message store reaches
1566
+ # the specified limit, LXMF will prioritise
1567
+ # keeping messages for destinations specified
1568
+ # with this option. This setting is optional,
1569
+ # and generally you do not need to use it.
1570
+
1571
+ # prioritise_destinations = 41d20c727598a3fbbdf9106133a3a0ed, d924b81822ca24e68e2effea99bcb8cf
1572
+
1573
+ # You can configure the maximum number of other
1574
+ # propagation nodes that this node will peer
1575
+ # with automatically. The default is 20.
1576
+
1577
+ # max_peers = 20
1578
+
1579
+ # You can configure a list of static propagation
1580
+ # node peers, that this node will always be
1581
+ # peered with, by specifying a list of
1582
+ # destination hashes.
1583
+
1584
+ # static_peers = e17f833c4ddf8890dd3a79a6fea8161d, 5a2d0029b6e5ec87020abaea0d746da4
1585
+
1586
+ # You can configure the propagation node to
1587
+ # only accept incoming propagation messages
1588
+ # from configured static peers.
1589
+
1590
+ # from_static_only = True
1591
+
1592
+ # By default, any destination is allowed to
1593
+ # connect and download messages, but you can
1594
+ # optionally restrict this. If you enable
1595
+ # authentication, you must provide a list of
1596
+ # allowed identity hashes in the a file named
1597
+ # "allowed" in the lxmd config directory.
1598
+
1599
+ auth_required = no
1600
+
1601
+
1602
+ [lxmf]
1603
+
1604
+ # The LXM Daemon will create an LXMF destination
1605
+ # that it can receive messages on. This option sets
1606
+ # the announced display name for this destination.
1607
+
1608
+ display_name = Anonymous Peer
1609
+
1610
+ # It is possible to announce the internal LXMF
1611
+ # destination when the LXM Daemon starts up.
1612
+
1613
+ announce_at_start = no
1614
+
1615
+ # You can also announce the delivery destination
1616
+ # at a specified interval. This is not enabled by
1617
+ # default.
1618
+
1619
+ # announce_interval = 360
1620
+
1621
+ # The maximum accepted unpacked size for mes-
1622
+ # sages received directly from other peers,
1623
+ # specified in kilobytes. Messages larger than
1624
+ # this will be rejected before the transfer
1625
+ # begins.
1626
+
1627
+ delivery_transfer_max_accepted_size = 1000
1628
+
1629
+ # You can configure an external program to be run
1630
+ # every time a message is received. The program
1631
+ # will receive as an argument the full path to the
1632
+ # message saved as a file. The example below will
1633
+ # simply result in the message getting deleted as
1634
+ # soon as it has been received.
1635
+
1636
+ # on_inbound = rm
1637
+
1638
+
1639
+ [logging]
1640
+ # Valid log levels are 0 through 7:
1641
+ # 0: Log only critical information
1642
+ # 1: Log errors and lower log levels
1643
+ # 2: Log warnings and lower log levels
1644
+ # 3: Log notices and lower log levels
1645
+ # 4: Log info and lower (this is the default)
1646
+ # 5: Verbose logging
1647
+ # 6: Debug logging
1648
+ # 7: Extreme logging
1649
+
1650
+ loglevel = 4
1651
+
1652
+ """
1653
+
1654
+ if __name__ == "__main__":
1655
+ main()