LXMF.py
1 APP_NAME = "lxmf" 2 3 ########################################################## 4 # The following core fields are provided to facilitate # 5 # interoperability in data exchange between various LXMF # 6 # clients and systems. # 7 ########################################################## 8 FIELD_EMBEDDED_LXMS = 0x01 9 FIELD_TELEMETRY = 0x02 10 FIELD_TELEMETRY_STREAM = 0x03 11 FIELD_ICON_APPEARANCE = 0x04 12 FIELD_FILE_ATTACHMENTS = 0x05 13 FIELD_IMAGE = 0x06 14 FIELD_AUDIO = 0x07 15 FIELD_THREAD = 0x08 16 FIELD_COMMANDS = 0x09 17 FIELD_RESULTS = 0x0A 18 FIELD_GROUP = 0x0B 19 FIELD_TICKET = 0x0C 20 FIELD_EVENT = 0x0D 21 FIELD_RNR_REFS = 0x0E 22 FIELD_RENDERER = 0x0F 23 24 # For usecases such as including custom data structures, 25 # embedding or encapsulating other data types or protocols 26 # that are not native to LXMF, or bridging/tunneling 27 # external protocols or services over LXMF, the following 28 # fields are available. A format/type/protocol (or other) 29 # identifier can be included in the CUSTOM_TYPE field, and 30 # the embedded payload can be included in the CUSTOM_DATA 31 # field. It is up to the client application to correctly 32 # discern and potentially utilise any data embedded using 33 # this mechanism. 34 FIELD_CUSTOM_TYPE = 0xFB 35 FIELD_CUSTOM_DATA = 0xFC 36 FIELD_CUSTOM_META = 0xFD 37 38 # The non-specific and debug fields are intended for 39 # development, testing and debugging use. 40 FIELD_NON_SPECIFIC = 0xFE 41 FIELD_DEBUG = 0xFF 42 43 ########################################################## 44 # The following section lists field-specific specifiers, # 45 # modes and identifiers that are native to LXMF. It is # 46 # optional for any client or system to support any of # 47 # these, and they are provided as template for easing # 48 # interoperability without sacrificing expandability # 49 # and flexibility of the format. # 50 ########################################################## 51 52 # Audio modes for the data structure in FIELD_AUDIO 53 54 # Codec2 Audio Modes 55 AM_CODEC2_450PWB = 0x01 56 AM_CODEC2_450 = 0x02 57 AM_CODEC2_700C = 0x03 58 AM_CODEC2_1200 = 0x04 59 AM_CODEC2_1300 = 0x05 60 AM_CODEC2_1400 = 0x06 61 AM_CODEC2_1600 = 0x07 62 AM_CODEC2_2400 = 0x08 63 AM_CODEC2_3200 = 0x09 64 65 # Opus Audio Modes 66 AM_OPUS_OGG = 0x10 67 AM_OPUS_LBW = 0x11 68 AM_OPUS_MBW = 0x12 69 AM_OPUS_PTT = 0x13 70 AM_OPUS_RT_HDX = 0x14 71 AM_OPUS_RT_FDX = 0x15 72 AM_OPUS_STANDARD = 0x16 73 AM_OPUS_HQ = 0x17 74 AM_OPUS_BROADCAST = 0x18 75 AM_OPUS_LOSSLESS = 0x19 76 77 # Custom, unspecified audio mode, the client must 78 # determine it itself based on the included data. 79 AM_CUSTOM = 0xFF 80 81 # Message renderer specifications for FIELD_RENDERER. 82 # The renderer specification is completely optional, 83 # and only serves as an indication to the receiving 84 # client on how to render the message contents. It is 85 # not mandatory to implement, either on sending or 86 # receiving sides, but is the recommended way to 87 # signal how to render a message, if non-plaintext 88 # formatting is used. 89 RENDERER_PLAIN = 0x00 90 RENDERER_MICRON = 0x01 91 RENDERER_MARKDOWN = 0x02 92 RENDERER_BBCODE = 0x03 93 94 # Optional propagation node metadata fields. These 95 # fields may be highly unstable in allocation and 96 # availability until the version 1.0.0 release, so use 97 # at your own risk until then, and expect changes! 98 PN_META_VERSION = 0x00 99 PN_META_NAME = 0x01 100 PN_META_SYNC_STRATUM = 0x02 101 PN_META_SYNC_THROTTLE = 0x03 102 PN_META_AUTH_BAND = 0x04 103 PN_META_UTIL_PRESSURE = 0x05 104 PN_META_CUSTOM = 0xFF 105 106 ########################################################## 107 # The following helper functions makes it easier to # 108 # handle and operate on LXMF data in client programs # 109 ########################################################## 110 111 import RNS 112 import RNS.vendor.umsgpack as msgpack 113 def display_name_from_app_data(app_data=None): 114 if app_data == None: return None 115 elif len(app_data) == 0: return None 116 else: 117 # Version 0.5.0+ announce format 118 if (app_data[0] >= 0x90 and app_data[0] <= 0x9f) or app_data[0] == 0xdc: 119 peer_data = msgpack.unpackb(app_data) 120 if type(peer_data) == list: 121 if len(peer_data) < 1: return None 122 else: 123 dn = peer_data[0] 124 if dn == None: return None 125 else: 126 try: 127 decoded = dn.decode("utf-8") 128 return decoded 129 except Exception as e: 130 RNS.log(f"Could not decode display name in included announce data. The contained exception was: {e}", RNS.LOG_ERROR) 131 return None 132 133 # Original announce format 134 else: 135 return app_data.decode("utf-8") 136 137 def stamp_cost_from_app_data(app_data=None): 138 if app_data == None or app_data == b"": return None 139 else: 140 # Version 0.5.0+ announce format 141 if (app_data[0] >= 0x90 and app_data[0] <= 0x9f) or app_data[0] == 0xdc: 142 peer_data = msgpack.unpackb(app_data) 143 if type(peer_data) == list: 144 if len(peer_data) < 2: return None 145 else: return peer_data[1] 146 147 # Original announce format 148 else: return None 149 150 def pn_name_from_app_data(app_data=None): 151 if app_data == None: return None 152 else: 153 if pn_announce_data_is_valid(app_data): 154 data = msgpack.unpackb(app_data) 155 metadata = data[6] 156 if not PN_META_NAME in metadata: return None 157 else: 158 try: return metadata[PN_META_NAME].decode("utf-8") 159 except: return None 160 161 return None 162 163 def pn_stamp_cost_from_app_data(app_data=None): 164 if app_data == None: return None 165 else: 166 if pn_announce_data_is_valid(app_data): 167 data = msgpack.unpackb(app_data) 168 return data[5][0] 169 else: 170 return None 171 172 def pn_announce_data_is_valid(data): 173 try: 174 if type(data) != bytes: return False 175 else: data = msgpack.unpackb(data) 176 if len(data) < 7: raise ValueError("Invalid announce data: Insufficient peer data, likely from deprecated LXMF version") 177 else: 178 try: int(data[1]) 179 except: raise ValueError("Invalid announce data: Could not decode timebase") 180 if data[2] != True and data[2] != False: raise ValueError("Invalid announce data: Indeterminate propagation node status") 181 try: int(data[3]) 182 except: raise ValueError("Invalid announce data: Could not decode propagation transfer limit") 183 try: int(data[4]) 184 except: raise ValueError("Invalid announce data: Could not decode propagation sync limit") 185 if type(data[5]) != list: raise ValueError("Invalid announce data: Could not decode stamp costs") 186 try: int(data[5][0]) 187 except: raise ValueError("Invalid announce data: Could not decode target stamp cost") 188 try: int(data[5][1]) 189 except: raise ValueError("Invalid announce data: Could not decode stamp cost flexibility") 190 try: int(data[5][2]) 191 except: raise ValueError("Invalid announce data: Could not decode peering cost") 192 if type(data[6]) != dict: raise ValueError("Invalid announce data: Could not decode metadata") 193 194 except Exception as e: 195 RNS.log(f"Could not validate propagation node announce data: {e}", RNS.LOG_DEBUG) 196 return False 197 198 return True