1
0

device.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. """XAP Device
  2. """
  3. import hid
  4. import json
  5. import time
  6. import gzip
  7. import random
  8. import threading
  9. import functools
  10. from struct import pack, unpack
  11. from platform import platform
  12. from .types import XAPSecureStatus, XAPFlags, XAPRequest, XAPResponse
  13. from .routes import XAPRoutes, XAPRouteError
  14. from .util import u32toBCD
  15. def _gen_token():
  16. """Generate XAP token - cannot start with 00xx or 'reserved' (FFFE|FFFF)
  17. """
  18. token = random.randrange(0x0100, 0xFFFD)
  19. # swap endianness
  20. return unpack('<H', pack('>H', token))[0]
  21. class XAPDevice:
  22. def __init__(self, dev):
  23. """Constructor opens hid device and starts dependent services
  24. """
  25. self.responses = {}
  26. self.do_read = True
  27. self.dev = hid.Device(path=dev['path'])
  28. self.bg = threading.Thread(target=self._read_loop, daemon=True)
  29. self.bg.start()
  30. def __enter__(self):
  31. return self
  32. def __exit__(self, exc_type, exc_value, exc_traceback):
  33. self.close()
  34. def close(self):
  35. self.do_read = False
  36. time.sleep(1)
  37. self.dev.close()
  38. def _read_loop(self):
  39. """Background thread to signal waiting transactions
  40. """
  41. while self.do_read:
  42. data = self.dev.read(XAPResponse.fmt.size, 100)
  43. if data:
  44. r = XAPResponse.from_bytes(data)
  45. event = self.responses.get(r.token)
  46. if event:
  47. event._ret = data
  48. event.set()
  49. def _query_device_info(self):
  50. datalen = int.from_bytes(self.transaction(XAPRoutes.QMK_CONFIG_BLOB_LEN) or bytes(0), 'little')
  51. if not datalen:
  52. return {}
  53. data = []
  54. offset = 0
  55. while offset < datalen:
  56. chunk = self.transaction(XAPRoutes.QMK_CONFIG_BLOB_CHUNK, offset)
  57. data += chunk
  58. offset += len(chunk)
  59. str_data = gzip.decompress(bytearray(data[:datalen]))
  60. return json.loads(str_data)
  61. def listen(self):
  62. """Receive a 'broadcast' message
  63. """
  64. token = 0xFFFF
  65. event = threading.Event()
  66. self.responses[token] = event
  67. while not hasattr(event, '_ret'):
  68. event.wait(timeout=0.25)
  69. r = XAPResponse.from_bytes(event._ret)
  70. return (r.flags, r.data[:r.length])
  71. def _transaction(self, *args):
  72. """Request/Receive
  73. """
  74. # convert args to array of bytes
  75. data = bytes()
  76. for arg in args:
  77. if isinstance(arg, (bytes, bytearray)):
  78. data += arg
  79. if isinstance(arg, int): # TODO: remove terrible assumption of u16
  80. data += arg.to_bytes(2, byteorder='little')
  81. token = _gen_token()
  82. buffer = XAPRequest(token, len(data), data).to_bytes()
  83. event = threading.Event()
  84. self.responses[token] = event
  85. # prepend 0 on windows because reasons...
  86. if 'windows' in platform().lower():
  87. buffer = b'\x00' + buffer
  88. self.dev.write(buffer)
  89. event.wait(timeout=1)
  90. self.responses.pop(token, None)
  91. if not hasattr(event, '_ret'):
  92. return None
  93. r = XAPResponse.from_bytes(event._ret)
  94. if r.flags & XAPFlags.SUCCESS == 0:
  95. return None
  96. return r.data[:r.length]
  97. @functools.lru_cache
  98. def capability(self, route):
  99. cap = int.from_bytes(self._transaction(route) or bytes(0), 'little')
  100. return cap
  101. @functools.lru_cache
  102. def subsystem(self):
  103. sub = int.from_bytes(self._transaction(XAPRoutes.XAP_SUBSYSTEM_QUERY) or bytes(0), 'little')
  104. return sub
  105. @functools.lru_cache
  106. def version(self):
  107. xap = int.from_bytes(self._transaction(XAPRoutes.XAP_VERSION_QUERY) or bytes(0), 'little')
  108. qmk = int.from_bytes(self._transaction(XAPRoutes.QMK_VERSION_QUERY) or bytes(0), 'little')
  109. return {'xap': u32toBCD(xap), 'qmk': u32toBCD(qmk)}
  110. def _ensure_route(self, route):
  111. (sub, rt) = route
  112. cap = bytes([sub, 1])
  113. if self.subsystem() & (1 << sub) == 0:
  114. raise XAPRouteError("subsystem not available")
  115. if self.capability(cap) & (1 << rt) == 0:
  116. raise XAPRouteError("route not available")
  117. def transaction(self, route, *args):
  118. self._ensure_route(route)
  119. return self._transaction(route, *args)
  120. @functools.lru_cache
  121. def info(self):
  122. data = self._query_device_info()
  123. data['_id'] = self.transaction(XAPRoutes.QMK_HARDWARE_ID)
  124. data['_version'] = self.version()
  125. return data
  126. def status(self):
  127. lock = int.from_bytes(self.transaction(XAPRoutes.XAP_SECURE_STATUS) or bytes(0), 'little')
  128. data = {}
  129. data['lock'] = XAPSecureStatus(lock).name
  130. return data
  131. def unlock(self):
  132. self.transaction(XAPRoutes.XAP_SECURE_UNLOCK)
  133. def lock(self):
  134. self.transaction(XAPRoutes.XAP_SECURE_LOCK)
  135. def reset(self):
  136. status = int.from_bytes(self.transaction(XAPRoutes.QMK_BOOTLOADER_JUMP) or bytes(0), 'little')
  137. return status == 1