/ LXMF / LXMF.py
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