/ RNS / Interfaces / WeaveInterface.py
WeaveInterface.py
   1  # Reticulum License
   2  #
   3  # Copyright (c) 2016-2025 Mark Qvist
   4  #
   5  # Permission is hereby granted, free of charge, to any person obtaining a copy
   6  # of this software and associated documentation files (the "Software"), to deal
   7  # in the Software without restriction, including without limitation the rights
   8  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
   9  # copies of the Software, and to permit persons to whom the Software is
  10  # furnished to do so, subject to the following conditions:
  11  #
  12  # - The Software shall not be used in any kind of system which includes amongst
  13  #   its functions the ability to purposefully do harm to human beings.
  14  #
  15  # - The Software shall not be used, directly or indirectly, in the creation of
  16  #   an artificial intelligence, machine learning or language model training
  17  #   dataset, including but not limited to any use that contributes to the
  18  #   training or development of such a model or algorithm.
  19  #
  20  # - The above copyright notice and this permission notice shall be included in
  21  #   all copies or substantial portions of the Software.
  22  #
  23  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  24  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  25  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  26  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  27  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  28  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  29  # SOFTWARE.
  30  
  31  import RNS
  32  import threading
  33  import time
  34  
  35  from collections import deque
  36  from RNS.Interfaces.Interface import Interface
  37  
  38  class HDLC():
  39      FLAG              = 0x7E
  40      ESC               = 0x7D
  41      ESC_MASK          = 0x20
  42  
  43      @staticmethod
  44      def escape(data):
  45          data = data.replace(bytes([HDLC.ESC]), bytes([HDLC.ESC, HDLC.ESC^HDLC.ESC_MASK]))
  46          data = data.replace(bytes([HDLC.FLAG]), bytes([HDLC.ESC, HDLC.FLAG^HDLC.ESC_MASK]))
  47          return data
  48  
  49  class WDCL():
  50      WDCL_T_DISCOVER        = 0x00
  51      WDCL_T_CONNECT         = 0x01
  52      WDCL_T_CMD             = 0x02
  53      WDCL_T_LOG             = 0x03
  54      WDCL_T_DISP            = 0x04
  55      WDCL_T_ENDPOINT_PKT    = 0x05
  56      WDCL_T_ENCAP_PROTO     = 0x06
  57  
  58      WDCL_BROADCAST         = bytes([0xFF, 0xFF, 0xFF, 0xFF])
  59  
  60      WDCL_HANDSHAKE_TIMEOUT = 2
  61  
  62      HEADER_MINSIZE         = 4+1
  63      MAX_CHUNK              = 32768
  64      port                   = None
  65      speed                  = None
  66      databits               = None
  67      parity                 = None
  68      stopbits               = None
  69      serial                 = None
  70  
  71      def __init__(self, owner, device, port, as_interface=False):
  72          import importlib.util
  73          if RNS.vendor.platformutils.is_android():
  74              self.on_android  = True
  75              if importlib.util.find_spec('usbserial4a') != None:
  76                  from usbserial4a import serial4a as serial
  77                  parity = "N"
  78  
  79                  if importlib.util.find_spec('jnius') == None:
  80                      RNS.log("Could not load jnius API wrapper for Android, RNode interface cannot be created.", RNS.LOG_CRITICAL)
  81                      RNS.log("This probably means you are trying to use an USB-based interface from within Termux or similar.", RNS.LOG_CRITICAL)
  82                      RNS.log("This is currently not possible, due to this environment limiting access to the native Android APIs.", RNS.LOG_CRITICAL)
  83                      RNS.panic()
  84  
  85              else:
  86                  RNS.log("Could not load USB serial module for Android, Weave interface cannot be created.", RNS.LOG_CRITICAL)
  87                  RNS.panic()
  88          
  89          else:
  90              self.on_android = False
  91              if importlib.util.find_spec('serial') != None:
  92                  import serial
  93                  parity = serial.PARITY_NONE
  94              else:
  95                  RNS.log("Using the Weave interface requires a serial communication module to be installed.", RNS.LOG_CRITICAL)
  96                  RNS.log("You can install one with the command: python3 -m pip install pyserial", RNS.LOG_CRITICAL)
  97                  RNS.panic()
  98  
  99          if not RNS.vendor.platformutils.is_android():
 100              if port == None: raise ValueError("No port specified")
 101          
 102          self.supports_discovery   = True
 103          self.discovery_frequency  = None
 104          self.discovery_bandwidth  = None
 105          self.discovery_channel    = None
 106          self.discovery_modulation = None
 107  
 108          self.switch_identity = owner.switch_identity
 109          self.switch_id = self.switch_identity.sig_pub_bytes[-4:]
 110          self.switch_pub_bytes = self.switch_identity.sig_pub_bytes
 111  
 112          self.rxb = 0
 113          self.txb = 0
 114          self.owner = owner
 115          self.as_interface = as_interface
 116          self.device = device
 117          self.device.connection = self
 118          self.pyserial = serial
 119          self.serial   = None
 120          self.port     = port
 121          self.speed    = 3000000
 122          self.databits = 8
 123          self.parity   = parity
 124          self.stopbits = 1
 125          self.timeout  = 100
 126          self.online   = False
 127          self.frame_buffer = b""
 128          self.next_tx = 0
 129          self.should_run = True
 130          self.receiver = None
 131          self.wdcl_connected = False
 132          self.reconnecting = False
 133          self.frame_queue = deque()
 134          if not self.as_interface:
 135              self.id = RNS.Identity.full_hash(port.hwid.encode("utf-8"))
 136  
 137          if self.as_interface:
 138              try:
 139                  self.open_port()
 140                  if self.serial and self.serial.is_open: self.configure_device()
 141                  else: raise IOError("Could not open serial port")
 142  
 143              except Exception as e:
 144                  RNS.log("Could not open serial port for interface "+str(self), RNS.LOG_ERROR)
 145                  RNS.log("The contained exception was: "+str(e), RNS.LOG_ERROR)
 146                  RNS.log("Reticulum will attempt to bring up this interface periodically", RNS.LOG_ERROR)
 147                  if not self.owner.detached and not self.reconnecting:
 148                      thread = threading.Thread(target=self.reconnect_port)
 149                      thread.daemon = True
 150                      thread.start()
 151  
 152          else:
 153              try: self.open_port()
 154              except Exception as e:
 155                  self.owner.wlog("Could not open serial port")
 156                  raise e
 157  
 158              if self.serial.is_open: self.configure_device()
 159              else: raise IOError("Could not open serial port")
 160  
 161  
 162      def open_port(self):
 163          if not self.on_android:
 164              if self.as_interface:
 165                  RNS.log(f"Opening serial port {self.port}...", RNS.LOG_VERBOSE)
 166                  target_port = self.port
 167              else:
 168                  self.owner.wlog(f"Opening serial port {self.port.device}...")
 169                  target_port = self.port.device
 170  
 171              self.serial = self.pyserial.Serial(
 172                  port = target_port,
 173                  baudrate = self.speed,
 174                  bytesize = self.databits,
 175                  parity = self.parity,
 176                  stopbits = self.stopbits,
 177                  xonxoff = False,
 178                  rtscts = False,
 179                  timeout = 0.250,
 180                  inter_byte_timeout = None,
 181                  write_timeout = None,
 182                  dsrdtr = False)
 183  
 184          else:
 185              if self.port != None:
 186                  # Get device parameters
 187                  from usb4a import usb
 188                  device = usb.get_usb_device(self.port)
 189                  if device:
 190                      vid = device.getVendorId()
 191                      pid = device.getProductId()
 192  
 193                      # Driver overrides for speficic chips
 194                      proxy = self.pyserial.get_serial_port
 195                      if vid == 0x1A86 and pid == 0x55D4:
 196                          # Force CDC driver for Qinheng CH34x
 197                          RNS.log(str(self)+" using CDC driver for "+RNS.hexrep(vid)+":"+RNS.hexrep(pid), RNS.LOG_DEBUG)
 198                          from usbserial4a.cdcacmserial4a import CdcAcmSerial
 199                          proxy = CdcAcmSerial
 200  
 201                      self.serial = proxy(
 202                          self.port,
 203                          baudrate = self.speed,
 204                          bytesize = self.databits,
 205                          parity = self.parity,
 206                          stopbits = self.stopbits,
 207                          xonxoff = False,
 208                          rtscts = False,
 209                          timeout = None,
 210                          inter_byte_timeout = None,
 211                          # write_timeout = wtimeout,
 212                          dsrdtr = False,
 213                      )
 214  
 215                      if vid == 0x0403:
 216                          # Hardware parameters for FTDI devices @ 115200 baud
 217                          self.serial.DEFAULT_READ_BUFFER_SIZE = 16 * 1024
 218                          self.serial.USB_READ_TIMEOUT_MILLIS = 100
 219                          self.serial.timeout = 0.1
 220                      elif vid == 0x10C4:
 221                          # Hardware parameters for SiLabs CP210x @ 115200 baud
 222                          self.serial.DEFAULT_READ_BUFFER_SIZE = 64 
 223                          self.serial.USB_READ_TIMEOUT_MILLIS = 12
 224                          self.serial.timeout = 0.012
 225                      elif vid == 0x1A86 and pid == 0x55D4:
 226                          # Hardware parameters for Qinheng CH34x @ 115200 baud
 227                          self.serial.DEFAULT_READ_BUFFER_SIZE = 64
 228                          self.serial.USB_READ_TIMEOUT_MILLIS = 12
 229                          self.serial.timeout = 0.1
 230                      else:
 231                          # Default values
 232                          self.serial.DEFAULT_READ_BUFFER_SIZE = 1 * 1024
 233                          self.serial.USB_READ_TIMEOUT_MILLIS = 100
 234                          self.serial.timeout = 0.1
 235  
 236                      RNS.log(str(self)+" USB read buffer size set to "+RNS.prettysize(self.serial.DEFAULT_READ_BUFFER_SIZE), RNS.LOG_DEBUG)
 237                      RNS.log(str(self)+" USB read timeout set to "+str(self.serial.USB_READ_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
 238                      RNS.log(str(self)+" USB write timeout set to "+str(self.serial.USB_WRITE_TIMEOUT_MILLIS)+"ms", RNS.LOG_DEBUG)
 239  
 240      def close(self):
 241          self.should_run = False
 242          self.online = False
 243          self.wdcl_connected = False
 244          if self.serial:
 245              self.serial.close()
 246              if self.as_interface: RNS.log((f"Closed serial port {str(self.port)} for {str(self)}"), RNS.LOG_VERBOSE) 
 247              else: self.owner.wlog(f"Closed serial port {str(self.port.device)} for {str(self)}")
 248  
 249      def configure_device(self):
 250          thread = threading.Thread(target=self.read_loop)
 251          thread.daemon = True
 252          thread.start()
 253          if self.as_interface: RNS.log(f"Serial port {self.port} is now open, discovering remote device...", RNS.LOG_VERBOSE)
 254          else: self.owner.wlog("Serial port "+self.port.device+" is now open")
 255          self.device.discover()
 256  
 257          if self.as_interface:
 258              timeout = time.time() + self.WDCL_HANDSHAKE_TIMEOUT
 259              while time.time() < timeout and not self.wdcl_connected: time.sleep(0.1)
 260              if not self.wdcl_connected:
 261                  raise IOError(f"WDCL connection handshake timed out for {self}")
 262                  self.online = False
 263                  self.wdcl_connected = False
 264                  if self.serial:
 265                      try: self.serial.close()
 266                      except Exception as e: RNS.log("Error while cleaning serial connection: {e}", RNS.LOG_ERROR)
 267  
 268          self.online = True
 269  
 270      def process_incoming(self, data):
 271          self.rxb += len(data)
 272          if self.device:
 273              while len(self.frame_queue): self.device.incoming_frame(self.frame_queue.pop())
 274              self.device.incoming_frame(data)
 275          else: self.frame_queue.append(data)
 276  
 277      def process_outgoing(self, data):
 278          if self.serial.is_open:
 279              data = bytes([HDLC.FLAG])+HDLC.escape(data)+bytes([HDLC.FLAG])
 280              written = self.serial.write(data)
 281              self.txb += len(data)          
 282              if written != len(data):
 283                  raise IOError("Serial interface only wrote "+str(written)+" bytes of "+str(len(data)))
 284  
 285      def read_loop(self):
 286          try:
 287              while self.serial.is_open:
 288                  data_in = self.serial.read(1500)
 289                  if len(data_in) > 0:
 290                      self.frame_buffer += data_in
 291                      flags_remaining = True
 292                      while flags_remaining:
 293                          frame_start = self.frame_buffer.find(HDLC.FLAG)
 294                          if frame_start != -1:
 295                              frame_end = self.frame_buffer.find(HDLC.FLAG, frame_start+1)
 296                              if frame_end != -1:
 297                                  frame = self.frame_buffer[frame_start+1:frame_end]
 298                                  frame = frame.replace(bytes([HDLC.ESC, HDLC.FLAG ^ HDLC.ESC_MASK]), bytes([HDLC.FLAG]))
 299                                  frame = frame.replace(bytes([HDLC.ESC, HDLC.ESC  ^ HDLC.ESC_MASK]), bytes([HDLC.ESC]))
 300                                  if len(frame) > WDCL.HEADER_MINSIZE: self.process_incoming(frame)
 301                                  self.frame_buffer = self.frame_buffer[frame_end:]
 302                              else:
 303                                  flags_remaining = False
 304                          else:
 305                              flags_remaining = False
 306                      
 307          except Exception as e:
 308              self.online = False
 309              self.wdcl_connected = False
 310              if self.should_run:
 311                  if self.as_interface:
 312                      RNS.log("A serial port error occurred, the contained exception was: "+str(e), RNS.LOG_ERROR)
 313                      RNS.log("Will attempt to reconnect the interface periodically.", RNS.LOG_ERROR)
 314                  else:
 315                      self.owner.wlog("A serial port error occurred, the contained exception was: "+str(e))
 316                      self.owner.wlog("Will attempt to reconnect the interface periodically.")
 317                  RNS.trace_exception(e)
 318  
 319          self.online = False
 320          self.wdcl_connected = False
 321          try: self.serial.close()
 322          except: pass
 323          if self.should_run: self.reconnect_port()
 324  
 325      def reconnect_port(self):
 326          if self.reconnecting: return
 327          self.reconnecting = True
 328          self.wdcl_connected = False
 329          while not self.online:
 330              try:
 331                  time.sleep(5)
 332                  if self.as_interface: RNS.log("Attempting to reconnect serial port "+str(self.port)+" for "+str(self.owner)+"...", RNS.LOG_DEBUG)
 333                  else: self.owner.wlog("Attempting to reconnect serial port "+str(self.port.device)+" for "+str(self.owner)+"...")
 334                  self.open_port()
 335                  if self.serial and self.serial.is_open: self.configure_device()
 336              except Exception as e:
 337                  if self.as_interface: RNS.log("Error while reconnecting port, the contained exception was: "+str(e), RNS.LOG_ERROR)
 338                  else: self.owner.wlog("Error while reconnecting port, the contained exception was: "+str(e))
 339                  RNS.trace_exception(e)
 340  
 341          self.reconnecting = False
 342          if self.as_interface: RNS.log("Reconnected serial port for "+str(self), RNS.LOG_INFO)
 343          else: self.owner.wlog("Reconnected serial port for "+str(self))
 344  
 345      def __str__(self):
 346          if self.as_interface: return f"WDCL over {self.port}"
 347          else:
 348              if self.port.serial_number: sn_str = f" {self.port.serial_number}"
 349              else: sn_str = ""
 350              return f"{self.port.product}{sn_str} (USB)"
 351  
 352  class Cmd():
 353      WDCL_CMD_ENDPOINT_PKT        = 0x0001
 354      WDCL_CMD_ENDPOINTS_LIST      = 0x0100
 355      WDCL_CMD_REMOTE_DISPLAY      = 0x0A00
 356      WDCL_CMD_REMOTE_INPUT        = 0x0A01
 357  
 358  class Evt():
 359      ET_MSG                       = 0x0000
 360      ET_SYSTEM_BOOT               = 0x0001
 361      ET_CORE_INIT                 = 0x0002
 362      ET_DRV_UART_INIT             = 0x1000
 363      ET_DRV_USB_CDC_INIT          = 0x1010
 364      ET_DRV_USB_CDC_HOST_AVAIL    = 0x1011
 365      ET_DRV_USB_CDC_HOST_SUSPEND  = 0x1012
 366      ET_DRV_USB_CDC_HOST_RESUME   = 0x1013
 367      ET_DRV_USB_CDC_CONNECTED     = 0x1014
 368      ET_DRV_USB_CDC_READ_ERR      = 0x1015
 369      ET_DRV_USB_CDC_OVERFLOW      = 0x1016
 370      ET_DRV_USB_CDC_DROPPED       = 0x1017
 371      ET_DRV_USB_CDC_TX_TIMEOUT    = 0x1018
 372      ET_DRV_I2C_INIT              = 0x1020
 373      ET_DRV_NVS_INIT              = 0x1030
 374      ET_DRV_NVS_ERASE             = 0x1031
 375      ET_DRV_CRYPTO_INIT           = 0x1040
 376      ET_DRV_DISPLAY_INIT          = 0x1050
 377      ET_DRV_DISPLAY_BUS_AVAILABLE = 0x1051
 378      ET_DRV_DISPLAY_IO_CONFIGURED = 0x1052
 379      ET_DRV_DISPLAY_PANEL_CREATED = 0x1053
 380      ET_DRV_DISPLAY_PANEL_RESET   = 0x1054
 381      ET_DRV_DISPLAY_PANEL_INIT    = 0x1055
 382      ET_DRV_DISPLAY_PANEL_ENABLE  = 0x1056
 383      ET_DRV_DISPLAY_REMOTE_ENABLE = 0x1057
 384      ET_DRV_W80211_INIT           = 0x1060
 385      ET_DRV_W80211_INIT           = 0x1061
 386      ET_DRV_W80211_CHANNEL        = 0x1062
 387      ET_DRV_W80211_POWER          = 0x1063
 388      ET_KRN_LOGGER_INIT           = 0x2000
 389      ET_KRN_LOGGER_OUTPUT         = 0x2001
 390      ET_KRN_UI_INIT               = 0x2010
 391      ET_PROTO_WDCL_INIT           = 0x3000
 392      ET_PROTO_WDCL_RUNNING        = 0x3001
 393      ET_PROTO_WDCL_CONNECTION     = 0x3002
 394      ET_PROTO_WDCL_HOST_ENDPOINT  = 0x3003
 395      ET_PROTO_WEAVE_INIT          = 0x3100
 396      ET_PROTO_WEAVE_RUNNING       = 0x3101
 397      ET_PROTO_WEAVE_EP_ALIVE      = 0x3102
 398      ET_PROTO_WEAVE_EP_TIMEOUT    = 0x3103
 399      ET_PROTO_WEAVE_EP_VIA        = 0x3104
 400      ET_SRVCTL_REMOTE_DISPLAY     = 0xA000
 401      ET_INTERFACE_REGISTERED      = 0xD000
 402      ET_STAT_STATE                = 0xE000
 403      ET_STAT_UPTIME               = 0xE001
 404      ET_STAT_TIMEBASE             = 0xE002
 405      ET_STAT_CPU                  = 0xE003
 406      ET_STAT_TASK_CPU             = 0xE004
 407      ET_STAT_MEMORY               = 0xE005
 408      ET_STAT_STORAGE              = 0xE006
 409      ET_SYSERR_MEM_EXHAUSTED      = 0xF000
 410  
 411      IF_TYPE_USB                  = 0x01
 412      IF_TYPE_UART                 = 0x02
 413      IF_TYPE_W80211               = 0x03
 414      IF_TYPE_BLE                  = 0x04
 415      IF_TYPE_LORA                 = 0x05
 416      IF_TYPE_ETHERNET             = 0x06
 417      IF_TYPE_WIFI                 = 0x07
 418      IF_TYPE_TCP                  = 0x08
 419      IF_TYPE_UDP                  = 0x09
 420      IF_TYPE_IR                   = 0x0A
 421      IF_TYPE_AFSK                 = 0x0B
 422      IF_TYPE_GPIO                 = 0x0C
 423      IF_TYPE_SPI                  = 0x0D
 424      IF_TYPE_I2C                  = 0x0E
 425      IF_TYPE_CAN                  = 0x0F
 426      IF_TYPE_DMA                  = 0x10
 427  
 428      event_descriptions = {
 429          ET_SYSTEM_BOOT: "System boot",
 430          ET_CORE_INIT: "Core initialization",
 431          ET_DRV_UART_INIT: "UART driver initialization",
 432          ET_DRV_USB_CDC_INIT: "USB CDC driver initialization",
 433          ET_DRV_USB_CDC_HOST_AVAIL: "USB CDC host became available",
 434          ET_DRV_USB_CDC_HOST_SUSPEND: "USB CDC host suspend",
 435          ET_DRV_USB_CDC_HOST_RESUME: "USB CDC host resume",
 436          ET_DRV_USB_CDC_CONNECTED: "USB CDC host connection",
 437          ET_DRV_USB_CDC_READ_ERR: "USB CDC read error",
 438          ET_DRV_USB_CDC_OVERFLOW: "USB CDC overflow occurred",
 439          ET_DRV_USB_CDC_DROPPED: "USB CDC dropped bytes",
 440          ET_DRV_USB_CDC_TX_TIMEOUT: "USB CDC TX flush timeout",
 441          ET_DRV_I2C_INIT: "I2C driver initialization",
 442          ET_DRV_NVS_INIT: "NVS driver initialization",
 443          ET_DRV_CRYPTO_INIT: "Cryptography driver initialization",
 444          ET_DRV_W80211_INIT: "W802.11 driver initialization",
 445          ET_DRV_W80211_CHANNEL: "W802.11 channel configuration",
 446          ET_DRV_W80211_POWER: "W802.11 TX power configuration",
 447          ET_DRV_DISPLAY_INIT: "Display driver initialization",
 448          ET_DRV_DISPLAY_BUS_AVAILABLE: "Display bus availability",
 449          ET_DRV_DISPLAY_IO_CONFIGURED: "Display I/O configuration",
 450          ET_DRV_DISPLAY_PANEL_CREATED: "Display panel allocation",
 451          ET_DRV_DISPLAY_PANEL_RESET: "Display panel reset",
 452          ET_DRV_DISPLAY_PANEL_INIT: "Display panel initialization",
 453          ET_DRV_DISPLAY_PANEL_ENABLE: "Display panel activation",
 454          ET_DRV_DISPLAY_REMOTE_ENABLE: "Remote display output activation",
 455          ET_KRN_LOGGER_INIT: "Logging service initialization",
 456          ET_KRN_LOGGER_OUTPUT: "Logging service output activation",
 457          ET_KRN_UI_INIT: "User interface service initialization",
 458          ET_PROTO_WDCL_INIT: "WDCL protocol initialization",
 459          ET_PROTO_WDCL_RUNNING: "WDCL protocol activation",
 460          ET_PROTO_WDCL_CONNECTION: "WDCL host connection",
 461          ET_PROTO_WDCL_HOST_ENDPOINT: "Weave host endpoint",
 462          ET_PROTO_WEAVE_INIT: "Weave protocol initialization",
 463          ET_PROTO_WEAVE_RUNNING: "Weave protocol activation",
 464          ET_PROTO_WEAVE_EP_ALIVE: "Weave endpoint alive",
 465          ET_PROTO_WEAVE_EP_TIMEOUT: "Weave endpoint disappeared",
 466          ET_SRVCTL_REMOTE_DISPLAY: "Remote display service control event",
 467          ET_INTERFACE_REGISTERED: "Interface registration",
 468          ET_SYSERR_MEM_EXHAUSTED: "System memory exhausted",
 469      }
 470  
 471      interface_types = {
 472          IF_TYPE_USB: "usb",
 473          IF_TYPE_UART: "uart",
 474          IF_TYPE_W80211: "mw",
 475          IF_TYPE_BLE: "ble",
 476          IF_TYPE_LORA: "lora",
 477          IF_TYPE_ETHERNET: "eth",
 478          IF_TYPE_WIFI: "wifi",
 479          IF_TYPE_TCP: "tcp",
 480          IF_TYPE_UDP: "udp",
 481          IF_TYPE_IR: "ir",
 482          IF_TYPE_AFSK: "afsk",
 483          IF_TYPE_GPIO: "gpio",
 484          IF_TYPE_SPI: "spi",
 485          IF_TYPE_I2C: "i2c",
 486          IF_TYPE_CAN: "can",
 487          IF_TYPE_DMA: "dma",
 488      }
 489  
 490      channel_descriptions = {
 491          1: "Channel 1 (2412 MHz)",
 492          2: "Channel 2 (2417 MHz)",
 493          3: "Channel 3 (2422 MHz)",
 494          4: "Channel 4 (2427 MHz)",
 495          5: "Channel 5 (2432 MHz)",
 496          6: "Channel 6 (2437 MHz)",
 497          7: "Channel 7 (2442 MHz)",
 498          8: "Channel 8 (2447 MHz)",
 499          9: "Channel 9 (2452 MHz)",
 500          10: "Channel 10 (2457 MHz)",
 501          11: "Channel 11 (2462 MHz)",
 502          12: "Channel 12 (2467 MHz)",
 503          13: "Channel 13 (2472 MHz)",
 504          14: "Channel 14 (2484 MHz)",
 505      }
 506  
 507      LOG_FORCE    = 0
 508      LOG_CRITICAL = 1
 509      LOG_ERROR    = 2
 510      LOG_WARNING  = 3
 511      LOG_NOTICE   = 4
 512      LOG_INFO     = 5
 513      LOG_VERBOSE  = 6
 514      LOG_DEBUG    = 7
 515      LOG_EXTREME  = 8
 516      LOG_SYSTEM   = 9
 517  
 518      levels = {
 519          LOG_FORCE: "Forced",
 520          LOG_CRITICAL: "Critical",
 521          LOG_ERROR: "Error",
 522          LOG_WARNING: "Warning",
 523          LOG_NOTICE: "Notice",
 524          LOG_INFO: "Info",
 525          LOG_VERBOSE: "Verbose",
 526          LOG_DEBUG: "Debug",
 527          LOG_EXTREME: "Extreme",
 528          LOG_SYSTEM: "System",
 529      }
 530  
 531      task_descriptions = {
 532          "taskLVGL": "Driver: UI Renderer",
 533          "ui_service": "Service: User Interface",
 534          "TinyUSB":  "Driver: USB",
 535          "drv_w80211": "Driver: W802.11",
 536          "system_stats": "System: Stats",
 537          "core": "System: Core",
 538          "protocol_wdcl": "Protocol: WDCL",
 539          "protocol_weave": "Protocol: Weave",
 540          "tiT": "Protocol: TCP/IP",
 541          "ipc0": "System: CPU 0 IPC",
 542          "ipc1": "System: CPU 1 IPC",
 543          "esp_timer": "Driver: Timers",
 544          "Tmr Svc": "Service: Timers",
 545          "kernel_logger": "Service: Logging",
 546          "remote_display": "Service: Remote Display",
 547          "wifi": "System: WiFi Hardware",
 548          "sys_evt": "System: Kernel Events",
 549      }
 550  
 551      @staticmethod
 552      def level(level):
 553          if level in Evt.levels: return Evt.levels[level]
 554          else: return "Unknown"
 555  
 556  class LogFrame():
 557      timestamp = None
 558      level = None
 559      event = None
 560      data = b""
 561  
 562      def __init__(self, timestamp=None, level=None, event=None, data=b""):
 563          self.timestamp = timestamp; self.level = level
 564          self.event = event; self.data = data
 565  
 566  class WeaveEndpoint():
 567      QUEUE_LEN = 1024
 568  
 569      def __init__(self, endpoint_addr):
 570          self.endpoint_addr = endpoint_addr
 571          self.alive = time.time()
 572          self.via = None
 573          self.received = deque(maxlen=WeaveEndpoint.QUEUE_LEN)
 574  
 575      def receive(self, data):
 576          self.received.append(data)
 577  
 578  class WeaveDevice():
 579      STATLEN_MAX            = 120
 580      STAT_UPDATE_THROTTLE   = 0.5
 581  
 582      WEAVE_SWITCH_ID_LEN    = 4
 583      WEAVE_ENDPOINT_ID_LEN  = 8
 584      WEAVE_FLOWSEQ_LEN      = 2
 585      WEAVE_HMAC_LEN         = 8
 586      WEAVE_AUTH_LEN         = WEAVE_ENDPOINT_ID_LEN+WEAVE_HMAC_LEN
 587  
 588      WEAVE_PUBKEY_SIZE      = 32
 589      WEAVE_PRVKEY_SIZE      = 64
 590      WEAVE_SIGNATURE_LEN    = 64
 591  
 592      def __init__(self, as_interface=False, rns_interface=None):
 593          self.identity        = None
 594          self.receiver        = None
 595          self.switch_id       = None
 596          self.endpoint_id     = None
 597          self.owner           = None
 598          self.rns_interface   = rns_interface
 599          self.as_interface    = as_interface
 600          self.endpoints       = {}
 601          self.active_tasks    = {}
 602          self.cpu_load        = 0
 603          self.memory_total    = 0
 604          self.memory_free     = 0
 605          self.memory_used     = 0
 606          self.memory_used_pct = 0
 607          self.log_queue       = deque()
 608          self.memory_stats    = deque(maxlen=WeaveDevice.STATLEN_MAX)
 609          self.cpu_stats       = deque(maxlen=WeaveDevice.STATLEN_MAX)
 610          self.display_buffer  = bytearray(0)
 611          self.update_display  = False
 612  
 613          self.next_update_memory = 0
 614          self.next_update_cpu    = 0
 615  
 616      def wdcl_send(self, packet_type, data):
 617          if not self.switch_id:
 618              if self.as_interface: RNS.log("Attempt to transmit on {self} while remote Weave device identity is unknown", RNS.LOG_ERROR)
 619              else: self.receiver.log("Error: Attempt to transmit while remote Weave device identity is unknown")
 620          else:
 621              frame  = self.switch_id
 622              frame += bytes([packet_type])
 623              frame += data
 624              self.connection.process_outgoing(frame)
 625  
 626      def wdcl_broadcast(self, packet_type, data):
 627          frame  = WDCL.WDCL_BROADCAST
 628          frame += bytes([packet_type])
 629          frame += data
 630          self.connection.process_outgoing(frame)
 631  
 632      def wdcl_send_command(self, command, data):
 633          frame  = b""
 634          frame += bytes([command>>8, (command & 0xFF)])
 635          frame += data
 636          self.wdcl_send(WDCL.WDCL_T_CMD, frame)
 637  
 638      def discover(self):
 639          self.wdcl_broadcast(WDCL.WDCL_T_DISCOVER, self.connection.switch_id)
 640  
 641      def handshake(self):
 642          if self.identity == None:
 643              if self.as_interface: RNS.log("Attempt to perform handshake on {self} before remote device discovery completion", RNS.LOG_ERROR)
 644              else: self.receiver.log("Attempt to perform handshake before remote device discovery completion")
 645          else:
 646              signed_id = self.switch_id
 647              signature = self.connection.switch_identity.sign(signed_id)
 648              data      = self.connection.switch_pub_bytes
 649              data     += signature
 650              self.wdcl_send(WDCL.WDCL_T_CONNECT, data)
 651              if self.as_interface: RNS.log(f"WDCL connection handshake sent", RNS.LOG_VERBOSE)
 652              else: self.receiver.log("Connection handshake sent")
 653  
 654      def capture_stats_cpu(self):
 655          self.cpu_stats.append({"timestamp": time.time(), "cpu_load": self.cpu_load})
 656          if self.receiver and self.receiver.ready and len(self.memory_stats) > 1: self.receiver.stats_update("cpu")
 657  
 658      def capture_stats_memory(self):
 659          self.memory_stats.append({"timestamp": time.time(), "memory_used": self.memory_used})
 660          if self.receiver and self.receiver.ready and len(self.memory_stats) > 1: self.receiver.stats_update("memory")
 661  
 662      def get_cpu_stats(self):
 663          tbegin = None
 664          stats = {"timestamps": [], "values": [], "max": 100, "unit": "%"}
 665          for i in range(0, len(self.cpu_stats)):
 666              if tbegin == None: tbegin = self.cpu_stats[len(self.cpu_stats)-1]["timestamp"]
 667              stats["timestamps"].append(self.cpu_stats[i]["timestamp"]-tbegin)
 668              stats["values"].append(self.cpu_stats[i]["cpu_load"])
 669  
 670          return stats
 671  
 672      def get_memory_stats(self):
 673          tbegin = None
 674          stats = {"timestamps": [], "values": [], "max": self.memory_total, "unit": "B"}
 675          for i in range(0, len(self.memory_stats)):
 676              if tbegin == None: tbegin = self.memory_stats[len(self.memory_stats)-1]["timestamp"]
 677              stats["timestamps"].append(self.memory_stats[i]["timestamp"]-tbegin)
 678              stats["values"].append(self.memory_stats[i]["memory_used"])
 679  
 680          return stats
 681  
 682      def get_active_tasks(self):
 683          active_tasks = {}
 684          now = time.time()
 685          for task_id in self.active_tasks:
 686              if not task_id.startswith("IDLE"):
 687                  task_description = task_id
 688                  if task_id in Evt.task_descriptions: task_description = Evt.task_descriptions[task_id]
 689                  if now - self.active_tasks[task_id]["timestamp"] < 5:
 690                      active_tasks[task_description] = self.active_tasks[task_id]
 691  
 692          return active_tasks
 693  
 694      def disconnect_display(self):
 695          self.wdcl_send_command(Cmd.WDCL_CMD_REMOTE_DISPLAY, bytes([0x00]))
 696          self.update_display = False
 697  
 698      def connect_display(self):
 699          self.wdcl_send_command(Cmd.WDCL_CMD_REMOTE_DISPLAY, bytes([0x01]))
 700          self.update_display = True
 701  
 702      def endpoint_alive(self, endpoint_id):
 703          if not endpoint_id in self.endpoints: self.endpoints[endpoint_id] = WeaveEndpoint(endpoint_id)
 704          else: self.endpoints[endpoint_id].alive = time.time()
 705  
 706          if self.as_interface: self.rns_interface.add_peer(endpoint_id)
 707  
 708      def endpoint_via(self, endpoint_id, via_switch_id):
 709          if endpoint_id in self.endpoints: self.endpoints[endpoint_id].via = via_switch_id
 710          if self.as_interface: self.rns_interface.endpoint_via(endpoint_id, via_switch_id)
 711  
 712      def deliver_packet(self, endpoint_id, data):
 713          packet_data = endpoint_id+data
 714          self.wdcl_send_command(Cmd.WDCL_CMD_ENDPOINT_PKT, packet_data)
 715  
 716      def received_packet(self, source, data):
 717          self.endpoint_alive(source)
 718          if self.as_interface:
 719              self.rns_interface.process_incoming(data, source)
 720  
 721      def incoming_frame(self, data):
 722          if len(data) > self.WEAVE_SWITCH_ID_LEN+2 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_ENDPOINT_PKT and data[:self.WEAVE_SWITCH_ID_LEN] == self.connection.switch_id:
 723              payload = data[self.WEAVE_SWITCH_ID_LEN+1:-self.WEAVE_ENDPOINT_ID_LEN]
 724              src_endpoint = data[-self.WEAVE_ENDPOINT_ID_LEN:]
 725              self.received_packet(src_endpoint, payload)
 726  
 727          elif len(data) > self.WEAVE_SWITCH_ID_LEN+1 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_DISCOVER:
 728              discovery_response_len = self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE+self.WEAVE_SIGNATURE_LEN
 729              if len(data) == discovery_response_len:
 730                  signed_id        = data[:self.WEAVE_SWITCH_ID_LEN]
 731                  remote_pub_key   = data[self.WEAVE_SWITCH_ID_LEN+1:self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE]
 732                  remote_switch_id = remote_pub_key[-4:]
 733                  remote_signature = data[self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE:self.WEAVE_SWITCH_ID_LEN+1+self.WEAVE_PUBKEY_SIZE+self.WEAVE_SIGNATURE_LEN]
 734                  remote_identity = RNS.Identity(create_keys=False)
 735                  remote_identity.load_public_key(remote_pub_key*2)
 736                  if remote_identity.validate(remote_signature, signed_id):
 737                      if self.as_interface: RNS.log(f"Remote Weave device {RNS.hexrep(remote_switch_id)} discovered", RNS.LOG_VERBOSE)
 738                      else: self.receiver.log(f"Remote Weave device {RNS.hexrep(remote_switch_id)} discovered")
 739                      self.identity = remote_identity
 740                      self.switch_id = remote_switch_id
 741                      self.handshake()
 742                  else:
 743                      if self.as_interface: RNS.LOG("Invalid remote device discovery response received", RNS.LOG_ERROR)
 744                      else: self.receiver.log("Invalid remote device discovery response received")
 745  
 746          elif len(data) > self.WEAVE_SWITCH_ID_LEN+1 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_LOG:
 747              fd  = data[self.WEAVE_SWITCH_ID_LEN+2:]
 748              ts  = fd[1] << 24 | fd[2] << 16 | fd[3] << 8 | fd[4]
 749              lvl = fd[5]; evt = fd[6] << 8 | fd[7]; data = fd[8:]
 750              self.log_handle(LogFrame(timestamp=ts/1000.0, level=lvl, event=evt, data=data))
 751  
 752          elif len(data) > self.WEAVE_SWITCH_ID_LEN+10 and data[self.WEAVE_SWITCH_ID_LEN] == WDCL.WDCL_T_DISP:
 753              fd  = data[self.WEAVE_SWITCH_ID_LEN+1:]
 754              cf  = fd[0]
 755              ofs = fd[1] << 24 | fd[2] << 16 | fd[3] << 8 | fd[4]
 756              dsz = fd[5] << 24 | fd[6] << 16 | fd[7] << 8 | fd[8]
 757              fbf = fd[9:]
 758  
 759              w = 128; h = 64
 760  
 761              if dsz > len(self.display_buffer): self.display_buffer = bytearray(dsz)
 762              self.display_buffer[ofs:ofs+len(fbf)] = fbf
 763  
 764              if self.receiver and self.receiver.ready and ofs+len(fbf) == dsz:
 765                  if self.update_display: self.receiver.display_update(self.display_buffer, w, h)
 766  
 767      def log_handle(self, frame):
 768          # Handle system event signalling
 769          if frame.event == Evt.ET_PROTO_WDCL_CONNECTION: self.connection.wdcl_connected = True
 770          if frame.event == Evt.ET_PROTO_WDCL_HOST_ENDPOINT and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_id = frame.data
 771          if frame.event == Evt.ET_PROTO_WEAVE_EP_ALIVE and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN: self.endpoint_alive(frame.data)
 772          if frame.event == Evt.ET_PROTO_WEAVE_EP_VIA and len(frame.data) == self.WEAVE_ENDPOINT_ID_LEN+self.WEAVE_SWITCH_ID_LEN: self.endpoint_via(frame.data[:self.WEAVE_ENDPOINT_ID_LEN], frame.data[self.WEAVE_ENDPOINT_ID_LEN:])
 773          elif frame.event == Evt.ET_STAT_TASK_CPU: self.active_tasks[frame.data[1:].decode("utf-8")] = { "cpu_load": frame.data[0], "timestamp": time.time() }
 774          elif frame.event == Evt.ET_STAT_CPU: 
 775              self.cpu_load = frame.data[0]
 776              self.capture_stats_cpu()
 777          elif frame.event == Evt.ET_STAT_MEMORY:
 778              self.memory_free     = int.from_bytes(frame.data[:4])
 779              self.memory_total    = int.from_bytes(frame.data[4:])
 780              self.memory_used     = self.memory_total-self.memory_free
 781              self.memory_used_pct = round((self.memory_used/self.memory_total)*100, 2)
 782              self.capture_stats_memory()
 783  
 784          # Handle generic messages and unmapped events
 785          else:
 786              ts = RNS.prettytime(frame.timestamp)
 787              if frame.event == Evt.ET_MSG:
 788                  if len(frame.data): data_string = frame.data.decode("utf-8")
 789                  else: data_string = ""
 790                  rendered = f"[{ts}] [{Evt.level(frame.level)}]: {data_string}"
 791  
 792              else:
 793                  if frame.event in Evt.event_descriptions: event_description = Evt.event_descriptions[frame.event]
 794                  else: event_description = f"0x{RNS.hexrep(frame.event, delimit=False)}"
 795  
 796                  if frame.event == Evt.ET_INTERFACE_REGISTERED:
 797                      if len(frame.data) >= 2:
 798                          interface_index = frame.data[0]; interface_type = frame.data[1]
 799                          type_name = "phy"
 800                          if interface_type in Evt.interface_types: type_name = Evt.interface_types[interface_type]
 801                          data_string = f": {type_name}{interface_index}"
 802                      else: data_string = ""
 803                  else:
 804                      if len(frame.data):
 805                          data_string = f": {RNS.hexrep(frame.data)}"
 806                          if   frame.event == Evt.ET_DRV_USB_CDC_CONNECTED:
 807                              if   frame.data[0] == 0x01: data_string = ": Connected"
 808                              elif frame.data[0] == 0x00: data_string = ": Disconnected"
 809                          elif frame.event == Evt.ET_DRV_W80211_CHANNEL:
 810                              if   frame.data[0] in Evt.channel_descriptions: data_string = f": {Evt.channel_descriptions[frame.data[0]]}"
 811                              else:                                           data_string = f": {RNS.hexrep(frame.data)}"
 812                          elif frame.event == Evt.ET_DRV_W80211_POWER:
 813                              tx_power = frame.data[0]*0.25
 814                              data_string = f": {tx_power} dBm ({int(10**(tx_power/10))} mW)"
 815                          elif frame.event >= Evt.ET_CORE_INIT and frame.event <= Evt.ET_PROTO_WEAVE_RUNNING:
 816                              if   frame.data[0] == 0x01: data_string = ": Success"
 817                              elif frame.data[0] == 0x00:
 818                                  if frame.level == Evt.LOG_ERROR: data_string = ": Failure"
 819                                  else:                            data_string = ": Stopped"
 820                              else:                       data_string = f": {RNS.hexrep(frame.data)}"
 821                          
 822                      else: data_string = ""
 823  
 824                  rendered = f"[{ts}] [{Evt.level(frame.level)}] [{event_description}]{data_string}"
 825              
 826              if self.as_interface:
 827                  RNS.log(f"{self.rns_interface}: {rendered}", RNS.LOG_EXTREME)
 828              else:
 829                  if self.receiver and self.receiver.ready:
 830                      while len(self.log_queue): self.receiver.log(self.log_queue.pop())
 831                      self.receiver.log(rendered)
 832                  else: self.log_queue.append(rendered)
 833  
 834  class WeaveInterface(Interface):
 835      HW_MTU = 1024
 836      FIXED_MTU = True
 837  
 838      DEFAULT_IFAC_SIZE  = 16
 839      PEERING_TIMEOUT    = 20.0
 840      BITRATE_GUESS      = 250*1000
 841  
 842      MULTI_IF_DEQUE_LEN = 48
 843      MULTI_IF_DEQUE_TTL = 0.75
 844  
 845      @property
 846      def cpu_load(self):
 847          if not self.device: return None
 848          else: return self.device.cpu_load
 849  
 850      @property
 851      def mem_load(self):
 852          if not self.device: return None
 853          else: return self.device.memory_used_pct
 854  
 855      @property
 856      def switch_id(self):
 857          if not self.device: return None
 858          else: return self.device.switch_id
 859  
 860      @property
 861      def endpoint_id(self):
 862          if not self.device: return None
 863          else: return self.device.endpoint_id
 864  
 865      def __init__(self, owner, configuration):
 866          c                      = Interface.get_config_obj(configuration)
 867          name                   = c["name"]
 868          port                   = c["port"]
 869          configured_bitrate     = c["configured_bitrate"] if "configured_bitrate" in c else None
 870  
 871          from RNS.Interfaces import netinfo
 872          super().__init__()
 873          self.netinfo = netinfo
 874  
 875          self.HW_MTU = WeaveInterface.HW_MTU
 876          self.IN  = True
 877          self.OUT = False
 878          self.name = name
 879          self.port = port
 880          self.switch_identity = RNS.Identity()
 881          self.owner = owner
 882          self.hw_errors = []
 883          self._online = False
 884          self.final_init_done = False
 885          self.peers = {}
 886          self.timed_out_interfaces = {}
 887          self.spawned_interfaces = {}
 888          self.write_lock = threading.Lock()
 889          self.mif_deque = deque(maxlen=WeaveInterface.MULTI_IF_DEQUE_LEN)
 890          self.mif_deque_times = deque(maxlen=WeaveInterface.MULTI_IF_DEQUE_LEN)
 891  
 892          self.announce_rate_target = None
 893          self.peer_job_interval = WeaveInterface.PEERING_TIMEOUT*1.1
 894          self.peering_timeout   = WeaveInterface.PEERING_TIMEOUT
 895  
 896          self.receives = True
 897          if configured_bitrate != None: self.bitrate = configured_bitrate
 898          else: self.bitrate = WeaveInterface.BITRATE_GUESS
 899  
 900      def final_init(self):
 901          self.device = WeaveDevice(as_interface=True, rns_interface=self)
 902          self.connection = WDCL(owner=self, device=self.device, port=self.port, as_interface=True)
 903  
 904          job_thread = threading.Thread(target=self.peer_jobs)
 905          job_thread.daemon = True
 906          job_thread.start()
 907  
 908          self._online = True
 909          self.final_init_done = True
 910  
 911      def peer_jobs(self):
 912          while True:
 913              time.sleep(self.peer_job_interval)
 914              now = time.time()
 915              timed_out_peers = []
 916  
 917              # Check for timed out peers
 918              for peer_addr in self.peers:
 919                  peer = self.peers[peer_addr]
 920                  last_heard = peer[1]
 921                  if now > last_heard+self.peering_timeout:
 922                      timed_out_peers.append(peer_addr)
 923  
 924              # Remove any timed out peers
 925              for peer_addr in timed_out_peers:
 926                  removed_peer = self.peers.pop(peer_addr)
 927                  if peer_addr in self.spawned_interfaces:
 928                      spawned_interface = self.spawned_interfaces[peer_addr]
 929                      spawned_interface.detach()
 930                      spawned_interface.teardown()
 931                  RNS.log(str(self)+" removed peer "+RNS.hexrep(peer_addr)+" on "+RNS.hexrep(removed_peer[0]), RNS.LOG_DEBUG)
 932              
 933      @property
 934      def peer_count(self):
 935          return len(self.spawned_interfaces)
 936  
 937      def endpoint_via(self, endpoint_addr, via_switch_addr):
 938          if endpoint_addr in self.peers: self.peers[endpoint_addr][2].via_switch_id = via_switch_addr
 939  
 940      def add_peer(self, endpoint_addr):    
 941          if not endpoint_addr in self.peers:
 942              spawned_interface = WeaveInterfacePeer(self, endpoint_addr)
 943              spawned_interface.OUT = self.OUT
 944              spawned_interface.IN  = self.IN
 945              spawned_interface.parent_interface = self
 946              spawned_interface.bitrate = self.bitrate
 947              
 948              spawned_interface.ifac_size = self.ifac_size
 949              spawned_interface.ifac_netname = self.ifac_netname
 950              spawned_interface.ifac_netkey = self.ifac_netkey
 951              if spawned_interface.ifac_netname != None or spawned_interface.ifac_netkey != None:
 952                  ifac_origin = b""
 953                  if spawned_interface.ifac_netname != None:
 954                      ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netname.encode("utf-8"))
 955                  if spawned_interface.ifac_netkey != None:
 956                      ifac_origin += RNS.Identity.full_hash(spawned_interface.ifac_netkey.encode("utf-8"))
 957  
 958                  ifac_origin_hash = RNS.Identity.full_hash(ifac_origin)
 959                  spawned_interface.ifac_key = RNS.Cryptography.hkdf(
 960                      length=64,
 961                      derive_from=ifac_origin_hash,
 962                      salt=RNS.Reticulum.IFAC_SALT,
 963                      context=None
 964                  )
 965                  spawned_interface.ifac_identity = RNS.Identity.from_bytes(spawned_interface.ifac_key)
 966                  spawned_interface.ifac_signature = spawned_interface.ifac_identity.sign(RNS.Identity.full_hash(spawned_interface.ifac_key))
 967  
 968              spawned_interface.announce_rate_target = self.announce_rate_target
 969              spawned_interface.announce_rate_grace = self.announce_rate_grace
 970              spawned_interface.announce_rate_penalty = self.announce_rate_penalty
 971              spawned_interface.mode = self.mode
 972              spawned_interface.HW_MTU = self.HW_MTU
 973              spawned_interface._online = True
 974              RNS.Transport.interfaces.append(spawned_interface)
 975              if endpoint_addr in self.spawned_interfaces:
 976                  self.spawned_interfaces[endpoint_addr].detach()
 977                  self.spawned_interfaces[endpoint_addr].teardown()
 978                  self.spawned_interfaces.pop(spawned_interface)
 979  
 980              self.spawned_interfaces[endpoint_addr] = spawned_interface
 981              self.peers[endpoint_addr] = [endpoint_addr, time.time(), spawned_interface]
 982  
 983              RNS.log(f"{self} added peer {RNS.hexrep(endpoint_addr)}", RNS.LOG_DEBUG)
 984          else:
 985              self.refresh_peer(endpoint_addr)
 986  
 987      def refresh_peer(self, endpoint_addr):
 988          try:
 989              self.peers[endpoint_addr][1] = time.time()
 990          except Exception as e:
 991              RNS.log(f"An error occurred while refreshing peer {RNS.hexrep(endpoint_addr)} on {self}: {e}", RNS.LOG_ERROR)
 992  
 993      def process_incoming(self, data, endpoint_addr=None):
 994          if self.online and endpoint_addr in self.spawned_interfaces:
 995              self.spawned_interfaces[endpoint_addr].process_incoming(data, endpoint_addr)
 996  
 997      def process_outgoing(self,data):
 998          pass
 999  
1000      def detach(self):
1001          self._online = False
1002  
1003      @property
1004      def online(self):
1005          if not self._online: return False
1006          else: return self.connection.online
1007  
1008      @online.setter
1009      def online(self, value):
1010          self._online = value
1011      
1012      def __str__(self):
1013          return "WeaveInterface["+self.name+"]"
1014  
1015  class WeaveInterfacePeer(Interface):
1016  
1017      def __init__(self, owner, endpoint_addr):
1018          super().__init__()
1019          self.owner = owner
1020          self.parent_interface = owner
1021          self.endpoint_addr = endpoint_addr
1022          self.via_switch_id = None
1023          self.peer_addr = None
1024          self.addr_info = None
1025          self.HW_MTU = self.owner.HW_MTU
1026          self.FIXED_MTU = self.owner.FIXED_MTU
1027          self._online = False
1028  
1029      def __str__(self):
1030          return f"WeaveInterfacePeer[{RNS.hexrep(self.endpoint_addr)}]"
1031  
1032      @property
1033      def online(self):
1034          if not self._online or not self.owner: return false
1035          else: return self.owner.online
1036  
1037      @online.setter
1038      def online(self, value):
1039          self._online = value
1040  
1041      def process_incoming(self, data, endpoint_addr=None):
1042          if self.online:
1043              data_hash = RNS.Identity.full_hash(data)
1044              deque_hit = False
1045              if data_hash in self.owner.mif_deque:
1046                  for te in self.owner.mif_deque_times:
1047                      if te[0] == data_hash and time.time() < te[1]+WeaveInterface.MULTI_IF_DEQUE_TTL:
1048                          deque_hit = True
1049                          break
1050  
1051              if not deque_hit:
1052                  self.owner.refresh_peer(self.endpoint_addr)
1053                  self.owner.mif_deque.append(data_hash)
1054                  self.owner.mif_deque_times.append([data_hash, time.time()])
1055                  self.rxb += len(data)
1056                  self.owner.rxb += len(data)
1057                  self.owner.owner.inbound(data, self)
1058  
1059      def process_outgoing(self, data):
1060          if self.online:
1061              with self.owner.write_lock:
1062                  try:
1063                      self.owner.device.deliver_packet(self.endpoint_addr, data)
1064                      self.txb += len(data)
1065                      self.owner.txb += len(data)
1066                  except Exception as e:
1067                      RNS.log("Could not transmit on "+str(self)+". The contained exception was: "+str(e), RNS.LOG_ERROR)
1068  
1069      def detach(self):
1070          self._online = False
1071          self.detached = True
1072          
1073      def teardown(self):
1074          if not self.detached:
1075              RNS.log("The interface "+str(self)+" experienced an unrecoverable error and is being torn down.", RNS.LOG_ERROR)
1076              if RNS.Reticulum.panic_on_interface_error:
1077                  RNS.panic()
1078  
1079          else: RNS.log("The interface "+str(self)+" is being torn down.", RNS.LOG_VERBOSE)
1080  
1081          self._online = False
1082          self.OUT = False
1083          self.IN = False
1084  
1085          if self.endpoint_addr in self.owner.spawned_interfaces:
1086              try: self.owner.spawned_interfaces.pop(self.endpoint_addr)
1087              except Exception as e:
1088                  RNS.log(f"Could not remove {self} from parent interface on detach. The contained exception was: {e}", RNS.LOG_ERROR)
1089  
1090          if self in RNS.Transport.interfaces:
1091              RNS.Transport.interfaces.remove(self)