2023-10-23 01:34:20 +02:00
|
|
|
APP_NAME = "lxmf"
|
|
|
|
|
|
2024-08-30 00:11:32 +02:00
|
|
|
##########################################################
|
|
|
|
|
# The following core fields are provided to facilitate #
|
|
|
|
|
# interoperability in data exchange between various LXMF #
|
|
|
|
|
# clients and systems. #
|
|
|
|
|
##########################################################
|
2023-10-23 01:34:20 +02:00
|
|
|
FIELD_EMBEDDED_LXMS = 0x01
|
|
|
|
|
FIELD_TELEMETRY = 0x02
|
2023-10-30 02:26:24 +01:00
|
|
|
FIELD_TELEMETRY_STREAM = 0x03
|
|
|
|
|
FIELD_ICON_APPEARANCE = 0x04
|
|
|
|
|
FIELD_FILE_ATTACHMENTS = 0x05
|
|
|
|
|
FIELD_IMAGE = 0x06
|
|
|
|
|
FIELD_AUDIO = 0x07
|
2026-05-24 23:16:15 +02:00
|
|
|
FIELD_THREAD = 0x08 # Bytes, full thread ID hash
|
2023-10-30 02:26:24 +01:00
|
|
|
FIELD_COMMANDS = 0x09
|
2024-03-17 00:35:54 +01:00
|
|
|
FIELD_RESULTS = 0x0A
|
2024-06-02 09:19:47 +02:00
|
|
|
FIELD_GROUP = 0x0B
|
2024-09-06 00:57:06 +02:00
|
|
|
FIELD_TICKET = 0x0C
|
2024-09-09 15:24:36 +02:00
|
|
|
FIELD_EVENT = 0x0D
|
2024-10-13 13:05:52 +02:00
|
|
|
FIELD_RNR_REFS = 0x0E
|
2024-12-09 18:16:12 +01:00
|
|
|
FIELD_RENDERER = 0x0F
|
2026-05-24 22:59:57 +02:00
|
|
|
FIELD_REPLY_TO = 0x30 # Bytes, full LXMessage.hash
|
|
|
|
|
FIELD_REPLY_QUOTE = 0x31 # Bytes, quoted content in UTF-8 encoding
|
|
|
|
|
FIELD_REACTION = 0x40 # Dict, see "Reaction dict indices" below
|
|
|
|
|
FIELD_COMMENT = 0x41 # Dict, see "Comment dict indices" below
|
|
|
|
|
FIELD_CONTINUATION = 0x42 # Dict, see "Continuation dict indices" below
|
|
|
|
|
|
|
|
|
|
# Unallocated fields between 0x00 and 0x80, both included,
|
|
|
|
|
# should be considered reserved for future extensibility
|
|
|
|
|
# For experimental and unstable features, it is recommended
|
|
|
|
|
# to use fields above 0xFF.
|
2024-06-02 09:19:47 +02:00
|
|
|
|
2024-08-30 00:11:32 +02:00
|
|
|
# For usecases such as including custom data structures,
|
|
|
|
|
# embedding or encapsulating other data types or protocols
|
|
|
|
|
# that are not native to LXMF, or bridging/tunneling
|
|
|
|
|
# external protocols or services over LXMF, the following
|
|
|
|
|
# fields are available. A format/type/protocol (or other)
|
|
|
|
|
# identifier can be included in the CUSTOM_TYPE field, and
|
|
|
|
|
# the embedded payload can be included in the CUSTOM_DATA
|
|
|
|
|
# field. It is up to the client application to correctly
|
|
|
|
|
# discern and potentially utilise any data embedded using
|
|
|
|
|
# this mechanism.
|
|
|
|
|
FIELD_CUSTOM_TYPE = 0xFB
|
|
|
|
|
FIELD_CUSTOM_DATA = 0xFC
|
|
|
|
|
FIELD_CUSTOM_META = 0xFD
|
|
|
|
|
|
|
|
|
|
# The non-specific and debug fields are intended for
|
|
|
|
|
# development, testing and debugging use.
|
|
|
|
|
FIELD_NON_SPECIFIC = 0xFE
|
|
|
|
|
FIELD_DEBUG = 0xFF
|
|
|
|
|
|
|
|
|
|
##########################################################
|
|
|
|
|
# The following section lists field-specific specifiers, #
|
|
|
|
|
# modes and identifiers that are native to LXMF. It is #
|
|
|
|
|
# optional for any client or system to support any of #
|
|
|
|
|
# these, and they are provided as template for easing #
|
|
|
|
|
# interoperability without sacrificing expandability #
|
|
|
|
|
# and flexibility of the format. #
|
|
|
|
|
##########################################################
|
|
|
|
|
|
2024-06-02 09:19:47 +02:00
|
|
|
# Audio modes for the data structure in FIELD_AUDIO
|
|
|
|
|
|
|
|
|
|
# Codec2 Audio Modes
|
|
|
|
|
AM_CODEC2_450PWB = 0x01
|
|
|
|
|
AM_CODEC2_450 = 0x02
|
|
|
|
|
AM_CODEC2_700C = 0x03
|
|
|
|
|
AM_CODEC2_1200 = 0x04
|
|
|
|
|
AM_CODEC2_1300 = 0x05
|
|
|
|
|
AM_CODEC2_1400 = 0x06
|
|
|
|
|
AM_CODEC2_1600 = 0x07
|
|
|
|
|
AM_CODEC2_2400 = 0x08
|
|
|
|
|
AM_CODEC2_3200 = 0x09
|
|
|
|
|
|
|
|
|
|
# Opus Audio Modes
|
2024-06-04 18:31:18 +02:00
|
|
|
AM_OPUS_OGG = 0x10
|
|
|
|
|
AM_OPUS_LBW = 0x11
|
|
|
|
|
AM_OPUS_MBW = 0x12
|
|
|
|
|
AM_OPUS_PTT = 0x13
|
|
|
|
|
AM_OPUS_RT_HDX = 0x14
|
|
|
|
|
AM_OPUS_RT_FDX = 0x15
|
|
|
|
|
AM_OPUS_STANDARD = 0x16
|
|
|
|
|
AM_OPUS_HQ = 0x17
|
|
|
|
|
AM_OPUS_BROADCAST = 0x18
|
|
|
|
|
AM_OPUS_LOSSLESS = 0x19
|
2024-06-02 09:25:33 +02:00
|
|
|
|
2024-08-30 00:11:32 +02:00
|
|
|
# Custom, unspecified audio mode, the client must
|
|
|
|
|
# determine it itself based on the included data.
|
2024-09-06 19:54:28 +02:00
|
|
|
AM_CUSTOM = 0xFF
|
|
|
|
|
|
2024-12-09 18:16:12 +01:00
|
|
|
# Message renderer specifications for FIELD_RENDERER.
|
|
|
|
|
# The renderer specification is completely optional,
|
|
|
|
|
# and only serves as an indication to the receiving
|
|
|
|
|
# client on how to render the message contents. It is
|
|
|
|
|
# not mandatory to implement, either on sending or
|
|
|
|
|
# receiving sides, but is the recommended way to
|
|
|
|
|
# signal how to render a message, if non-plaintext
|
|
|
|
|
# formatting is used.
|
|
|
|
|
RENDERER_PLAIN = 0x00
|
|
|
|
|
RENDERER_MICRON = 0x01
|
|
|
|
|
RENDERER_MARKDOWN = 0x02
|
|
|
|
|
RENDERER_BBCODE = 0x03
|
2024-09-06 19:54:28 +02:00
|
|
|
|
2026-05-24 22:59:57 +02:00
|
|
|
############################################################
|
|
|
|
|
# To be finalized in 1.0.0. A workdoc with open interaction
|
|
|
|
|
# through rngit is available for comments and nuancing on:
|
|
|
|
|
#
|
|
|
|
|
# a8d24177d946de4f1f0a0fe1af9a1338:/page/work.mu`g=reticulum|r=lxmf
|
|
|
|
|
#
|
|
|
|
|
# Clients that have implemented different reply, reaction
|
|
|
|
|
# or comment mechanisms can choose to transitionally parse
|
|
|
|
|
# their own specific formats, but are recommended to attempt
|
|
|
|
|
# parsing the structure and format defined herein first,
|
|
|
|
|
# and fall back to their client-specific structure second.
|
|
|
|
|
|
|
|
|
|
# Reaction dict indicies are integers to preserve bandwidth.
|
|
|
|
|
#
|
|
|
|
|
# Clients choose how to handle reaction content, if at all.
|
|
|
|
|
# While reactions are typically a single unicode emoji or
|
|
|
|
|
# similar, the exact implementation and sanitization is
|
|
|
|
|
# left up to the client. When using the FIELD_REACTION
|
|
|
|
|
# field, the contents is a dict with the following keys:
|
|
|
|
|
REACTION_TO = 0x00 # Bytes, full LXMessage.hash
|
|
|
|
|
REACTION_CONTENT = 0x01 # Bytes, the reaction content in UTF-8 encoding
|
|
|
|
|
|
|
|
|
|
# Comment dict indicies are integers to preserve bandwidth.
|
|
|
|
|
#
|
|
|
|
|
# Clients choose how to handle messages intended as comments
|
|
|
|
|
# for other message, if at all. The actual comment content
|
|
|
|
|
# is carried as the normal LXM content, meaning clients that
|
|
|
|
|
# do not support comments will display them as normal messages.
|
|
|
|
|
# When using the FIELD_COMMENT field, the contents is a dict
|
|
|
|
|
# with the following keys:
|
|
|
|
|
COMMENT_FOR = 0x00 # Bytes, full LXMessage.hash
|
|
|
|
|
|
|
|
|
|
# Continuation dict indicies are integers to preserve bandwidth.
|
|
|
|
|
#
|
|
|
|
|
# Clients choose how to handle messages that continue earlier
|
|
|
|
|
# messages, if at all. The actual continuation content is
|
|
|
|
|
# carried as the normal LXM content, meaning clients that
|
|
|
|
|
# do not support continuations will display them as normal.
|
|
|
|
|
# When using the FIELD_CONTINUATION field, the contents is a
|
|
|
|
|
# dict with the following keys:
|
|
|
|
|
CONTINUATION_OF = 0x00 # Bytes, full LXMessage.hash
|
|
|
|
|
############################################################
|
|
|
|
|
|
2025-10-30 16:43:26 +01:00
|
|
|
# Optional propagation node metadata fields. These
|
|
|
|
|
# fields may be highly unstable in allocation and
|
|
|
|
|
# availability until the version 1.0.0 release, so use
|
|
|
|
|
# at your own risk until then, and expect changes!
|
|
|
|
|
PN_META_VERSION = 0x00
|
|
|
|
|
PN_META_NAME = 0x01
|
|
|
|
|
PN_META_SYNC_STRATUM = 0x02
|
|
|
|
|
PN_META_SYNC_THROTTLE = 0x03
|
|
|
|
|
PN_META_AUTH_BAND = 0x04
|
|
|
|
|
PN_META_UTIL_PRESSURE = 0x05
|
2025-10-30 16:44:15 +01:00
|
|
|
PN_META_CUSTOM = 0xFF
|
2025-10-30 16:43:26 +01:00
|
|
|
|
2026-04-19 13:27:32 +02:00
|
|
|
# Supported functionality codes for signalling
|
|
|
|
|
# feature and capability support.
|
|
|
|
|
SF_COMPRESSION = 0x00
|
|
|
|
|
|
2024-09-06 19:54:28 +02:00
|
|
|
##########################################################
|
|
|
|
|
# The following helper functions makes it easier to #
|
|
|
|
|
# handle and operate on LXMF data in client programs #
|
|
|
|
|
##########################################################
|
|
|
|
|
|
2024-11-23 12:49:01 +01:00
|
|
|
import RNS
|
2024-09-06 19:54:28 +02:00
|
|
|
import RNS.vendor.umsgpack as msgpack
|
|
|
|
|
def display_name_from_app_data(app_data=None):
|
2025-10-30 13:14:59 +01:00
|
|
|
if app_data == None: return None
|
|
|
|
|
elif len(app_data) == 0: return None
|
2024-09-06 19:54:28 +02:00
|
|
|
else:
|
|
|
|
|
# Version 0.5.0+ announce format
|
|
|
|
|
if (app_data[0] >= 0x90 and app_data[0] <= 0x9f) or app_data[0] == 0xdc:
|
|
|
|
|
peer_data = msgpack.unpackb(app_data)
|
|
|
|
|
if type(peer_data) == list:
|
2025-10-30 13:14:59 +01:00
|
|
|
if len(peer_data) < 1: return None
|
2024-09-06 19:54:28 +02:00
|
|
|
else:
|
2024-09-07 11:35:17 +02:00
|
|
|
dn = peer_data[0]
|
2025-10-30 13:14:59 +01:00
|
|
|
if dn == None: return None
|
2024-09-07 11:35:17 +02:00
|
|
|
else:
|
|
|
|
|
try:
|
2026-05-06 18:45:41 +02:00
|
|
|
decoded = dn.decode("utf-8").replace("\x00", "").strip()
|
2024-09-07 11:35:17 +02:00
|
|
|
return decoded
|
2024-11-23 12:49:01 +01:00
|
|
|
except Exception as e:
|
|
|
|
|
RNS.log(f"Could not decode display name in included announce data. The contained exception was: {e}", RNS.LOG_ERROR)
|
2024-09-07 11:35:17 +02:00
|
|
|
return None
|
2024-09-06 19:54:28 +02:00
|
|
|
|
|
|
|
|
# Original announce format
|
|
|
|
|
else:
|
|
|
|
|
return app_data.decode("utf-8")
|
|
|
|
|
|
|
|
|
|
def stamp_cost_from_app_data(app_data=None):
|
2025-10-30 13:14:59 +01:00
|
|
|
if app_data == None or app_data == b"": return None
|
2024-09-06 19:54:28 +02:00
|
|
|
else:
|
|
|
|
|
# Version 0.5.0+ announce format
|
|
|
|
|
if (app_data[0] >= 0x90 and app_data[0] <= 0x9f) or app_data[0] == 0xdc:
|
|
|
|
|
peer_data = msgpack.unpackb(app_data)
|
|
|
|
|
if type(peer_data) == list:
|
2025-10-30 13:14:59 +01:00
|
|
|
if len(peer_data) < 2: return None
|
|
|
|
|
else: return peer_data[1]
|
2024-09-06 19:54:28 +02:00
|
|
|
|
|
|
|
|
# Original announce format
|
2025-10-30 13:14:59 +01:00
|
|
|
else: return None
|
2024-11-23 12:49:01 +01:00
|
|
|
|
2026-04-19 13:27:32 +02:00
|
|
|
def compression_support_from_app_data(app_data=None):
|
|
|
|
|
if app_data == None or app_data == b"": return None
|
|
|
|
|
else:
|
|
|
|
|
# Version 0.5.0+ announce format
|
|
|
|
|
if (app_data[0] >= 0x90 and app_data[0] <= 0x9f) or app_data[0] == 0xdc:
|
|
|
|
|
peer_data = msgpack.unpackb(app_data)
|
|
|
|
|
if type(peer_data) == list:
|
|
|
|
|
if len(peer_data) < 3: return True
|
|
|
|
|
else:
|
|
|
|
|
if not type(peer_data[2]) == list: return True
|
|
|
|
|
else: return SF_COMPRESSION in peer_data[2]
|
|
|
|
|
|
|
|
|
|
# Original announce format
|
|
|
|
|
else: return True
|
|
|
|
|
|
2025-10-31 22:24:55 +01:00
|
|
|
def pn_name_from_app_data(app_data=None):
|
|
|
|
|
if app_data == None: return None
|
|
|
|
|
else:
|
|
|
|
|
if pn_announce_data_is_valid(app_data):
|
|
|
|
|
data = msgpack.unpackb(app_data)
|
|
|
|
|
metadata = data[6]
|
|
|
|
|
if not PN_META_NAME in metadata: return None
|
|
|
|
|
else:
|
|
|
|
|
try: return metadata[PN_META_NAME].decode("utf-8")
|
|
|
|
|
except: return None
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
def pn_stamp_cost_from_app_data(app_data=None):
|
|
|
|
|
if app_data == None: return None
|
|
|
|
|
else:
|
|
|
|
|
if pn_announce_data_is_valid(app_data):
|
|
|
|
|
data = msgpack.unpackb(app_data)
|
|
|
|
|
return data[5][0]
|
|
|
|
|
else:
|
|
|
|
|
return None
|
|
|
|
|
|
2024-11-23 12:49:01 +01:00
|
|
|
def pn_announce_data_is_valid(data):
|
|
|
|
|
try:
|
2025-10-30 21:19:38 +01:00
|
|
|
if type(data) != bytes: return False
|
|
|
|
|
else: data = msgpack.unpackb(data)
|
2025-11-01 18:27:11 +01:00
|
|
|
if len(data) < 7: raise ValueError("Invalid announce data: Insufficient peer data, likely from deprecated LXMF version")
|
2024-11-23 12:49:01 +01:00
|
|
|
else:
|
2025-10-30 16:55:44 +01:00
|
|
|
try: int(data[1])
|
2025-10-31 13:53:59 +01:00
|
|
|
except: raise ValueError("Invalid announce data: Could not decode timebase")
|
2025-10-30 16:55:44 +01:00
|
|
|
if data[2] != True and data[2] != False: raise ValueError("Invalid announce data: Indeterminate propagation node status")
|
2025-10-30 15:39:00 +01:00
|
|
|
try: int(data[3])
|
2025-10-31 13:53:59 +01:00
|
|
|
except: raise ValueError("Invalid announce data: Could not decode propagation transfer limit")
|
2025-10-30 16:43:26 +01:00
|
|
|
try: int(data[4])
|
2025-10-31 13:53:59 +01:00
|
|
|
except: raise ValueError("Invalid announce data: Could not decode propagation sync limit")
|
2025-10-31 21:45:40 +01:00
|
|
|
if type(data[5]) != list: raise ValueError("Invalid announce data: Could not decode stamp costs")
|
2025-10-30 16:43:26 +01:00
|
|
|
try: int(data[5][0])
|
2025-10-31 13:53:59 +01:00
|
|
|
except: raise ValueError("Invalid announce data: Could not decode target stamp cost")
|
2025-10-30 16:43:26 +01:00
|
|
|
try: int(data[5][1])
|
2025-10-31 13:53:59 +01:00
|
|
|
except: raise ValueError("Invalid announce data: Could not decode stamp cost flexibility")
|
|
|
|
|
try: int(data[5][2])
|
|
|
|
|
except: raise ValueError("Invalid announce data: Could not decode peering cost")
|
|
|
|
|
if type(data[6]) != dict: raise ValueError("Invalid announce data: Could not decode metadata")
|
2024-11-23 12:49:01 +01:00
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
RNS.log(f"Could not validate propagation node announce data: {e}", RNS.LOG_DEBUG)
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
return True
|