def test_unpack_uid(): assert unpack(b"\xDF\x30\x01\x30\x02\xC1\x01\x03") == ([1, 2, 2], b"") assert unpack(b"\xDF\x30\x01\x30\x02\xC2\x01\x00\x03") == ([1, 2, 2], b"") assert unpack(b"\xDF\x30\x01\x30\x02\xC3\x01\x00\x00\x03") == ([1, 2, 2], b"") assert unpack(b"\xDF\x30\x01\x30\x02\xC4\x01\x00\x00\x00\x03") == ([ 1, 2, 2 ], b"")
def test_unpack_ptr(): assert unpack(b"\xD2\x41\x61\xA0") == (["a", "a"], b"") assert unpack(b"\xD4\x43\x66\x6F\x6F\x43\x62\x61\x72\xA0\xA1") == ( ["foo", "bar", "foo", "bar"], b"", ) assert unpack(b"\xE3\x41\x61\x41\x62\x41\x63\xE1\x41\x64\xA0\xA3\x01") == ( { "a": "b", "c": { "d": "a" }, "d": True }, b"", )
async def _process_buffer(self) -> None: _LOGGER.debug("Process buffer (size: %d)", len(self.buffer)) payload_length = 4 + int.from_bytes(self.buffer[1:4], byteorder="big") if len(self.buffer) < payload_length: _LOGGER.debug( "Expected %d bytes, have %d (waiting for more)", payload_length, len(self.buffer), ) self._receive_event.clear() return frame_type = FrameType(self.buffer[0]) data = self.buffer[4:payload_length] self.buffer = self.buffer[payload_length:] if frame_type in COMPANION_AUTH_FRAMES: try: self.handle_auth_frame(frame_type, opack.unpack(data)[0]) except Exception: _LOGGER.exception("failed to handle auth frame") else: try: resp = await self.send_to_atv(frame_type, data) self.send_to_client(frame_type, resp) except Exception: _LOGGER.exception("data exchange failed") self._receive_event.clear()
def test_golden(): data = { "_i": "_systemInfo", "_x": 1254122577, "_btHP": False, "_c": { "_pubID": "AA:BB:CC:DD:EE:FF", "_sv": "230.1", "_bf": 0, "_siriInfo": { "collectorElectionVersion": 1.0, "deviceCapabilities": { "seymourEnabled": 1, "voiceTriggerEnabled": 2 }, "sharedDataProtoBuf": 512 * b"\x08", }, "_stA": [ "com.apple.LiveAudio", "com.apple.siri.wakeup", "com.apple.Seymour", "com.apple.announce", "com.apple.coreduet.sync", "com.apple.SeymourSession", ], "_i": "6c62fca18b11", "_clFl": 128, "_idsID": "44E14ABC-DDDD-4188-B661-11BAAAF6ECDE", "_hkUID": [UUID("17ed160a-81f8-4488-962c-6b1a83eb0081")], "_dC": "1", "_sf": 256, "model": "iPhone10,6", "name": "iPhone", }, "_t": 2, } packed = pack(data) unpacked = unpack(packed) assert DeepDiff(unpacked, data, ignore_order=True)
async def send_to_atv(self, frame_type: FrameType, data: bytes): """Send data to remote device (ATV).""" log_binary(_LOGGER, f">>(ENCRYPTED) FrameType={frame_type}", Message=data) if self.chacha: header = bytes([frame_type.value]) + len(data).to_bytes( 3, byteorder="big") data = self.chacha.decrypt(data, aad=header) log_binary(_LOGGER, "<<(DECRYPTED)", Message=data) unpacked = cast(Dict[Any, Any], opack.unpack(data)[0]) # TODO: Bad cast if frame_type in COMPANION_AUTH_FRAMES: return await self.protocol.exchange_auth(frame_type, unpacked) return await self.protocol.exchange_opack(frame_type, unpacked)
def frame_received(self, frame_type: FrameType, data: bytes) -> None: """Frame was received from remote device.""" _LOGGER.debug("Received frame %s: %s", frame_type, data) if frame_type in _OPACK_FRAMES or frame_type in _AUTH_FRAMES: try: opack_data, _ = opack.unpack(data) if not isinstance(opack_data, dict): _LOGGER.debug("Unsupported OPACK base type: %s", type(opack_data)) return if frame_type in _AUTH_FRAMES: self._handle_auth(frame_type, opack_data) else: self._handle_opack(frame_type, opack_data) except Exception: _LOGGER.exception("failed to process frame") else: _LOGGER.debug("Received unsupported frame type: %s", frame_type)
def data_received(self, data): self.buffer += data while self.buffer: payload_length = 4 + int.from_bytes(self.buffer[1:4], byteorder="big") if len(self.buffer) < payload_length: _LOGGER.debug("Expect %d bytes, have %d bytes", payload_length, len(self.buffer)) break frame_type = FrameType(self.buffer[0]) header = self.buffer[0:4] data = self.buffer[4:payload_length] self.buffer = self.buffer[payload_length:] if self.chacha: data = self.chacha.decrypt(data, aad=header) unpacked, _ = opack.unpack(data) try: if frame_type in COMPANION_AUTH_FRAMES: self.handle_auth_frame(frame_type, unpacked) else: if not self.chacha: raise Exception("client has not authenticated") _LOGGER.debug("Received OPACK: %s", unpacked) handler_method_name = f"handle_{unpacked['_i'].lower()}" if hasattr(self, handler_method_name): getattr(self, handler_method_name)(unpacked) else: _LOGGER.warning("No handler for type %s", unpacked["_i"]) except Exception: _LOGGER.exception("failed to handle incoming data")
def test_unpack_none(): assert unpack(b"\x04") == (None, b"")
def test_unpack_boolean(): assert unpack(b"\x01") == (True, b"") assert unpack(b"\x02") == (False, b"")
def test_unpack_endless_dict(): assert unpack(b"\xEF" + b"\x41" + b"\x41".join(bytes([x]) for x in range(97, 127)) + b"\x03") == (dict( (chr(x), chr(x + 1)) for x in range(97, 127, 2)), b"")
def test_unpack_uuid(): assert unpack(b"\x05\x124Vx\x124Vx\x124Vx\x124Vx") == ( UUID("{12345678-1234-5678-1234-567812345678}"), b"", )
def test_unpack_endless_array(): list1 = b"\xDF\x41\x61" + 15 * b"\xa0" + b"\x03" list2 = b"\xDF\x41\x62" + 15 * b"\xa1" + b"\x03" assert unpack(list1) == (16 * ["a"], b"") assert unpack(b"\xD2" + list1 + list2) == ([16 * ["a"], 16 * ["b"]], b"")
def test_unpack_dict(): assert unpack(b"\xe0") == ({}, b"") assert unpack(b"\xe2\x41\x61\x14\x02\x04") == ({"a": 12, False: None}, b"") assert unpack(b"\xe1\x01\xe1\x41\x61\x0a") == ({True: {"a": 2}}, b"")
def test_unpack_longer_raw_bytes(): assert unpack(b"\x91\x21" + (33 * b"\x61")) == (33 * b"\x61", b"") assert unpack(b"\x92\x00\x01" + (256 * b"\x61")) == (256 * b"\x61", b"")
def test_unpack_larger_integers(): assert unpack(b"\x30\x28") == (0x28, b"") assert unpack(b"\x31\xFf\x01") == (0x1FF, b"") assert unpack(b"\x32\xFF\xFF\x01") == (0x1FFFF, b"") assert unpack(b"\x33\xFF\xFF\xFF\x01") == (0x1FFFFFF, b"")
def test_unpack_absolute_time(): # TODO: This is not implemented, it only parses the time stamp as an integer assert unpack(b"\x06\x01\x00\x00\x00\x00\x00\x00\x00") == (1, b"")
def test_unpack_short_raw_bytes(): assert unpack(b"\x71\xac") == (b"\xac", b"") assert unpack(b"\x73\x12\x34\x56") == (b"\x12\x34\x56", b"") assert unpack(b"\x90" + (0x20 * b"\xad")) == (0x20 * b"\xad", b"")
def test_unpack_short_strings(): assert unpack(b"\x41\x61") == ("a", b"") assert unpack(b"\x43\x61\x62\x63") == ("abc", b"") assert unpack(b"\x60" + (0x20 * b"\x61")) == (0x20 * "a", b"")
def test_unpack_float64(): assert unpack(b"\x36\x00\x00\x00\x00\x00\x00\xF0\x3F") == (1.0, b"")
def test_pack_unfloat32(): assert unpack(b"\x35\x00\x00\x80\x3f") == (1.0, b"")
def test_unpack_unsupported_type(): with pytest.raises(TypeError): unpack(b"\x00")
def test_unpack_array(): assert unpack(b"\xd0") == ([], b"") assert unpack(b"\xd3\x09\x44\x74\x65\x73\x74\x02") == ([1, "test", False], b"") assert unpack(b"\xd1\xd1\x01") == ([[True]], b"")
def test_unpack_longer_strings(): assert unpack(b"\x61\x21" + (33 * b"\x61")) == (33 * "a", b"") assert unpack(b"\x62\x00\x01" + (256 * b"\x61")) == (256 * "a", b"")
def test_unpack_small_integers(): assert unpack(b"\x08") == (0, b"") assert unpack(b"\x17") == (0xF, b"") assert unpack(b"\x2F") == (0x27, b"")