text: #
  # messagesuat.py  -- UAT message decoder
  #
  
  """UAT Message Decoder functions"""
  
  from collections import namedtuple
  from pprint import pprint
  
  CHAR_ETX = chr(3)
  CHAR_RS = chr(30)
  CHAR_NULL = chr(0)
  CHAR_TAB = chr(9)
  CHAR_CR = chr(13)
  CHAR_LF = chr(10)
  DLAC2StrTable = [
      CHAR_ETX,  # 0  End-of-Text
      'A',  # 1
      'B',  # 2
      'C',  # 3
      'D',  # 4
      'E',  # 5
      'F',  # 6
      'G',  # 7
      'H',  # 8
      'I',  # 9
      'J',  # 10
      'K',  # 11
      'L',  # 12
      'M',  # 13
      'N',  # 14
      'O',  # 15
      'P',  # 16
      'Q',  # 17
      'R',  # 18
      'S',  # 19
      'T',  # 20
      'U',  # 21
      'V',  # 22
      'W',  # 23
      'X',  # 24
      'Y',  # 25
      'Z',  # 26
      CHAR_NULL,  # 27  Null
      '....',  # 28  Tab
      CHAR_RS,  # 29  Record Separator
      '%s%s' % (CHAR_CR, CHAR_LF),  # 30  CR+LF
      CHAR_NULL,  # 31  Change-Cipher (treated as Null)
      ' ',  # 32
      '!',  # 33
      '"',  # 34
      '#',  # 35
      '$',  # 36
      '%',  # 37
      '&',  # 38
      "'",  # 39
      '(',  # 40
      ')',  # 41
      '*',  # 42
      '+',  # 43
      ',',  # 44
      '-',  # 45
      '.',  # 46
      '/',  # 47
      '0',  # 48
      '1',  # 49
      '2',  # 50
      '3',  # 51
      '4',  # 52
      '5',  # 53
      '6',  # 54
      '7',  # 55
      '8',  # 56
      '9',  # 57
      ':',  # 58
      ';',  # 59
      '<',  # 60
      '=',  # 61
      '>',  # 62
      '?',  # 63
  ]
  
  """
  MessageUATIDMapping = {
      0x00 : _parseHeartbeat,
      0x07 : _parseUplinkData,
      0x0a : _parseOwnershipReport,
      0x0b : _parseOwnershipGeometricAltitude,
      0x14 : _parseTrafficReport,
      0x65 : _parseGpsTime,
  }
  """
  
  def _decodeUatHeader(headerBytes):
      """decode a UAT header"""
      pass
  
  
  def _extractIFrames(dataBytes):
      """extract a list of I-Frame tuples
      @dataBytes  the whole UAT data field (424 bytes)
      """
  
      iframeList=[]
      iframe = namedtuple('IFrame', 'Type Data')
      n=0
      while n < 424:
          if n >= 452:
              # Only two bytes left & not enough for another I-Frame
              break
          if dataBytes[n] == 0x00 and dataBytes[n+1] == 0x00:
              # Assume the rest of the dataBytes are all zeros
              break
          
          # 9-bit number with LSB in the top bit of the second byte
          framelen = (dataBytes[n] << 1) + (_thunkByte(dataBytes[n+1],mask=0x7f,shift=-7))
          
          frameType = _thunkByte(dataBytes[n+1],mask=0x0f)
          n += 2
          
          #print "iframe: n=%d, framelen=%d, n+framelen=%d (<424?)" % (n, framelen, n+framelen)
          iframeList.append(iframe._make([frameType, dataBytes[n:n+framelen]]))
          n += framelen
      return(iframeList)
  
  
  def _extractAPDU(iframeData):
      if len(iframeData) < 4:
          #print "  **iframe too short for ADPU Header"
          return None
      
      apdu = namedtuple('ADPU', 'ProductID Hours Minutes Data')
  
      productId = _thunkByte(iframeData[0], mask=0x1f, shift=6) + _thunkByte(iframeData[1], mask=0xfc, shift=-2)
      hours = _thunkByte(iframeData[2], mask=0x7c, shift=-2)
      minutes = _thunkByte(iframeData[2], mask=0x03, shift=4) + _thunkByte(iframeData[3], mask=0xf0, shift=-4)
      
      #print "Product ID %d, %02d:%02d" % (productId, hours, minutes)
      
      return(apdu._make([productId, hours, minutes, iframeData[4:]]))
  
  
  
  def dlac2string(msgIn):
      """convert DLAC 6-bit encoded message to ASCII string"""
      msgLength = len(msgIn)
      msgOutChars = []
      n = 0  # index into input message array; only increment when no more bits to use
      m = 0  # index into output message array; increment on every loop
      while n < msgLength:
          pos = m % 4
          #print "  msgLength=%d, n=%d, m=%d, pos=%d, msgIn[n]=%02x" % (msgLength,n,m,pos,msgIn[n])
          if pos == 0:
              d = _thunkByte(msgIn[n], mask=0xfc, shift=-2)
              # don't increment n since there are two more bits to use
          elif pos == 1:
              if not (n + 1 < msgLength):
                  break
              d = _thunkByte(msgIn[n], mask=0x03, shift=4) + _thunkByte(msgIn[n+1], mask=0xf0, shift=-4)
              n += 1
          elif pos == 2:
              if not (n + 1 < msgLength):
                  break
              d = _thunkByte(msgIn[n], mask=0x0f, shift=2) + _thunkByte(msgIn[n+1], mask=0xc0, shift=-6)
              n += 1
          elif pos == 3:
              d = _thunkByte(msgIn[n], mask=0x3f)
              n += 1
          assert(d >= 0 and d <= 63)
          msgOutChars.append(DLAC2StrTable[d])
          m += 1
      return(b"".join(msgOutChars))
  
      
  
  def _thunkByte(c, mask=0xff, shift=0):
      """extract an integer from a byte applying a mask and a bit shift
      @c character byte
      @mask the AND mask to get the desired bits
      @shift negative to shift right, positive to shift left, zero for no shift
      """
      val = c & mask
      if shift < 0:
          val = val >> abs(shift)
      elif shift > 0:
          val = val << shift
      return val
  
  
  def messageUatToObject(msg):
      """decode a UAT message named tuple.
      @msg namedtuple('UplinkData', 'MsgType TimeOfReception Header Data')
      Returns decoded UAT message namedtuple('APDUMessage', 'ProductID Hours Minutes IFrames')
      IFrames is a list of namedtuple('IFrame', 'Type Data')
      """
      
      assert(msg.MsgType == 'UplinkData')
      
      apduMsg = namedtuple('APDUMessage', 'ProductID Hours Minutes IFrames')
  
      #print "**** UAT Message ****"
  
      """
      print "UAT Header: (%d bytes)" % (len(msg.Header))
      hexstr = ""
      for n in range(len(msg.Header)):
          if (n % 4) == 0:  hexstr += " "
          hexstr += "%02x" % (msg.Header[n])
      print " " + hexstr
      """
      
      """
      print "UAT Data: (%d bytes)" % (len(msg.Data))
      hexstr = ""
      for n in range(len(msg.Data)):
          if (n % 4) == 0:  hexstr += " "
          hexstr += "%02x" % (msg.Data[n])
      print " " + hexstr
      """
      
      iframeNum = 0
      iframeList = _extractIFrames(msg.Data)
      if len(iframeList) == 0:
          #print "Null I-Frame"
          return
      for iframe in iframeList:
          #print "I-Frame %d, Type %d, Length %d bytes" % (iframeNum, iframe.Type, len(iframe.Data))
          """
          print "I-Frame Data: (%d bytes)" % (len(iframe.Data))
          hexstr = ""
          for n in range(len(iframe.Data)):
              if (n % 4) == 0:  hexstr += " "
              hexstr += "%02x" % (iframe.Data[n])
          print " " + hexstr
          """
          
          apdu = _extractAPDU(iframe.Data)
          if not apdu is None:
              if apdu.ProductID in [8,11,12,13,413]:
                  print "APDU%03d: [%s]" % (apdu.ProductID, dlac2string(apdu.Data))
              else:
                  pass
                  #print "APDU%03d: length=%d" % (apdu.ProductID, len(iframe.Data)-4)
          
          iframeNum += 1
  