def write(request, expected): # TODO document me body = tlv8.encode(request) logger.debug('entering write function %s', tlv8.format_string(tlv8.decode(body))) request_tlv = tlv8.encode([ tlv8.Entry(AdditionalParameterTypes.ParamReturnResponse, bytearray(b'\x01')), tlv8.Entry(AdditionalParameterTypes.Value, body) ]) transaction_id = random.randrange(0, 255) # construct a hap characteristic write request following chapter 7.3.4.4 page 94 spec R2 data = bytearray([0x00, HapBleOpCodes.CHAR_WRITE, transaction_id]) data.extend(characteristic_id.to_bytes(length=2, byteorder='little')) data.extend(len(request_tlv).to_bytes(length=2, byteorder='little')) data.extend(request_tlv) logger.debug('sent %s', bytes(data).hex()) # write the request to the characteristic characteristic.write_value(value=data) # reading hap characteristic write response following chapter 7.3.4.5 page 95 spec R2 data = [] while len(data) == 0: time.sleep(1) logger.debug('reading characteristic') data = characteristic.read_value() resp_data = [b for b in data] expected_length = int.from_bytes(bytes(resp_data[3:5]), byteorder='little') logger.debug( 'control field: {c:x}, tid: {t:x}, status: {s:x}, length: {length}' .format(c=resp_data[0], t=resp_data[1], s=resp_data[2], length=expected_length)) while len(resp_data[3:]) < expected_length: time.sleep(1) logger.debug('reading characteristic') data = characteristic.read_value() resp_data.extend([b for b in data]) logger.debug('data %s of %s', len(resp_data[3:]), expected_length) logger.debug('received %s', bytes(resp_data).hex()) logger.debug('decode %s', bytes(resp_data[5:]).hex()) resp_tlv = tlv8.decode( bytes([int(a) for a in resp_data[5:]]), expected={AdditionalParameterTypes.Value: tlv8.DataType.BYTES}) result = tlv8.decode( resp_tlv.first_by_id(AdditionalParameterTypes.Value).data, expected) logger.debug('leaving write function %s', tlv8.format_string(result)) return result
def test_decode_example_3(self): input_data = b'\x01\x15\x01\x10e\xad\x8b\xe8\xb3fD\xcb\xbde#\xccc\n\xb8\xef\x02\x01\x01' structure = { 1: { 1: tlv8.DataType.BYTES, 2: tlv8.DataType.INTEGER, }, 2: { 1: tlv8.DataType.INTEGER, 2: tlv8.DataType.BYTES, 3: tlv8.DataType.BYTES, 4: tlv8.DataType.BYTES }, 3: { 1: tlv8.DataType.INTEGER, 2: tlv8.DataType.INTEGER, 3: tlv8.DataType.INTEGER, 4: tlv8.DataType.INTEGER, 5: tlv8.DataType.INTEGER } } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, tlv8.EntryList([ tlv8.Entry(1, b'e\xad\x8b\xe8\xb3fD\xcb\xbde#\xccc\n\xb8\xef'), tlv8.Entry(2, 1), ])), ]) self.assertEqual(expected, result)
def makePartitionCsv(bytes_data, csvFile): dict = {} containerId = "" for entry in tlv8.decode(bytes_data): if entry.type_id == HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_CONTAINER_ID.value: containerId = entry.data.__str__() if entry.type_id in dict: dict[entry.type_id] = dict[entry.type_id] + entry.data else: dict[entry.type_id] = entry.data lines = [] lines.append("key,type,encoding,value") lines.append("keystore,namespace,,") lines.append("isValid,data,u8,1") for key, value in dict.items(): if key is HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_SIGNATURE.value: pass elif key is HAP_KEYSTORE_TYPE.HAP_KEYSTORE_TYPE_CONTAINER_ID.value: lines.append("0x{:02X},data,u8,{}".format(key, int(value.hex(), 16))) else: lines.append("0x{:02X},data,hex2bin,{}".format(key, value.hex())) with open(csvFile, "w") as fp: for line in lines: fp.write("%s\n" % line) return containerId
def test_decode_2_entries_1_expected(self): input_data = b'\x02\x01\x23\x03\x01\x42' structure = { 2: tlv8.DataType.INTEGER } result = tlv8.decode(input_data, structure) self.assertEqual(tlv8.EntryList([tlv8.Entry(2, 0x23)]), result)
def test_decode_error_4(self): data = b'\x01\x02Hi' result = tlv8.decode(data, {1: tlv8.DataType.STRING}) expected_data = tlv8.EntryList([ tlv8.Entry(1, 'Hi') ]) self.assertEqual(result, expected_data)
def decoder(bytes_data): return tlv8.decode( bytes_data, { SupportedVideoStreamConfigurationKeys.VIDEO_CODEC_CONFIGURATION: { VideoCodecConfigurationKeys.VIDEO_CODEC_TYPE: VideoCodecTypeValues, VideoCodecConfigurationKeys.VIDEO_CODEC_PARAMETERS: { VideoCodecParametersKeys.PROFILE_ID: ProfileIdValues, VideoCodecParametersKeys.LEVEL: LevelValues, VideoCodecParametersKeys.PACKETIZATION_MODE: PacketizationModeValues, VideoCodecParametersKeys.CVO_ENABLED: PacketizationModeValues, VideoCodecParametersKeys.CVO_ID: tlv8.DataType.UNSIGNED_INTEGER }, VideoCodecConfigurationKeys.VIDEO_ATTRIBUTES: { VideoAttributesKeys.IMAGE_WIDTH: tlv8.DataType.UNSIGNED_INTEGER, VideoAttributesKeys.IMAGE_HEIGHT: tlv8.DataType.UNSIGNED_INTEGER, VideoAttributesKeys.FRAME_RATE: tlv8.DataType.UNSIGNED_INTEGER, }, } })
def do_char_write(self, tid, value): """The value is actually a TLV with a command to perform""" request = { entry.type_id: entry.data for entry in tlv8.decode( value, { TlvTypes.State: tlv8.DataType.INTEGER, TlvTypes.Method: tlv8.DataType.INTEGER, TlvTypes.Identifier: tlv8.DataType.BYTES, }) } logging.debug('%s', request) assert request[TlvTypes.State] == States.M1 if request[TlvTypes.Method] == Methods.RemovePairing: ident = request[TlvTypes.Identifier].decode() self.service.device.peers.pop(ident, None) # If ident == this session then disconnect it # self.service.device.disconnect() response = bytearray([0x02, tid, 0x00]) inner = tlv8.encode([ tlv8.Entry(TlvTypes.State, States.M2), ]) outer = tlv8.encode( [tlv8.Entry(AdditionalParameterTypes.Value, inner)]) response.extend(len(outer).to_bytes(length=2, byteorder='little')) response.extend(outer) self.queue_read_response(self.encrypt_value(bytes(response)))
def add_pairing(self, additional_controller_pairing_identifier, ios_device_ltpk, permissions): if not self.session: self.session = IpSession(self.pairing_data) if permissions == 'User': permissions = TlvTypes.Permission_RegularUser elif permissions == 'Admin': permissions = TlvTypes.Permission_AdminUser else: print('UNKNOWN') request_tlv = tlv8.encode([ tlv8.Entry(TlvTypes.State, States.M1), tlv8.Entry(TlvTypes.Method, Methods.AddPairing), tlv8.Entry(TlvTypes.Identifier, additional_controller_pairing_identifier.encode()), tlv8.Entry(TlvTypes.PublicKey, bytes.fromhex(ios_device_ltpk)), tlv8.Entry(TlvTypes.Permissions, permissions) ]) response = self.session.sec_http.post('/pairings', request_tlv) data = response.read() data = tlv8.decode( data, { TlvTypes.State: tlv8.DataType.INTEGER, TlvTypes.Error: tlv8.DataType.BYTES, }) # TODO handle the response properly self.session.close()
def check_convert_value(val, target_type): """ Checks if the given value is of the given type or is convertible into the type. If the value is not convertible, a HomeKitTypeException is thrown. :param val: the original value :param target_type: the target type of the conversion :return: the converted value :raises FormatError: if the input value could not be converted to the target type """ if target_type == CharacteristicFormats.bool: try: val = strtobool(str(val)) except ValueError: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=target_type)) if target_type in [ CharacteristicFormats.uint64, CharacteristicFormats.uint32, CharacteristicFormats.uint16, CharacteristicFormats.uint8, CharacteristicFormats.int ]: try: val = int(val) except ValueError: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=target_type)) if target_type == CharacteristicFormats.float: try: val = float(val) except ValueError: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=target_type)) if target_type == CharacteristicFormats.data: try: base64.decodebytes(val.encode()) except binascii.Error: raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=target_type)) if target_type == CharacteristicFormats.tlv8: try: tmp_bytes = base64.decodebytes(val.encode()) tlv8.decode(tmp_bytes, {}) except (binascii.Error, ValueError): raise FormatError('"{v}" is no valid "{t}"!'.format(v=val, t=target_type)) return val
def test_decode_key(self): class TestKeys(enum.IntEnum): KEY_1 = 1 KEY_2 = 2 data = b'\x01\x03foo' result = tlv8.decode(data, {TestKeys.KEY_1: tlv8.DataType.STRING}) expected = tlv8.EntryList([tlv8.Entry(TestKeys.KEY_1, 'foo')]) self.assertIsInstance(result[0].type_id, TestKeys) self.assertEqual(expected, result)
def test_decode_float(self): input_data = b'\x01\x04' + pack('<f', 3.141) structure = { 1: tlv8.DataType.FLOAT, } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, 3.141), ]) self.assertEqual(expected, result)
def test_decode_example_2(self): input_data = b'\x01\x01\x00' structure = { 1: tlv8.DataType.INTEGER, } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, 0), ]) self.assertEqual(expected, result)
def test_decode_value(self): class TestValues(enum.IntEnum): VALUE_1 = 1 VALUE_2 = 2 data = b'\x01\x01\x02' result = tlv8.decode(data, {1: TestValues}) expected = tlv8.EntryList([tlv8.Entry(1, 2)]) self.assertIsInstance(result[0].data, TestValues) self.assertEqual(expected, result)
def test_decode_int2_un(self): input_data = b'\x01\x02' + pack('<H', 12345) structure = { 1: tlv8.DataType.UNSIGNED_INTEGER, } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, 12345), ]) self.assertEqual(expected, result)
def test_decode_int1_pos(self): input_data = b'\x01\x01' + pack('<b', 123) structure = { 1: tlv8.DataType.INTEGER, } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, 123), ]) self.assertEqual(expected, result)
def test_decode_supported_rtp_configs(self): data = \ b'\x02\x01\x00' + \ b'\x00\x00' + \ b'\x02\x01\x01' result = tlv8.decode(data, expected={2: tlv8.DataType.INTEGER}) expected_data = tlv8.EntryList([ tlv8.Entry(2, 0), tlv8.Entry(2, 1) ]) self.assertEqual(result, expected_data)
def test_decode_int4_neg(self): input_data = b'\x01\x04' + pack('<i', -12345) structure = { 1: tlv8.DataType.INTEGER, } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, -12345), ]) self.assertEqual(expected, result)
def test_decode_int8_un(self): input_data = b'\x01\x08' + pack('<q', 4611686018427387904) structure = { 1: tlv8.DataType.UNSIGNED_INTEGER, } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, 4611686018427387904), ]) self.assertEqual(expected, result)
def write_http(request, expected): body = tlv8.encode(request) logging.debug('write message: %s', tlv8.format_string(tlv8.deep_decode(body))) connection.putrequest('POST', '/pair-setup', skip_accept_encoding=True) connection.putheader('Content-Type', 'application/pairing+tlv8') connection.putheader('Content-Length', len(body)) connection.endheaders(body) resp = connection.getresponse() response_tlv = tlv8.decode(resp.read(), expected) logging.debug('response: %s', tlv8.format_string(response_tlv)) return response_tlv
def test_entrylist_decode_nested(self): data = b'\x01\x04%\x06I@\x02\x0e\x03\x05hello\x04\x05world\x03\x01\x02' result = tlv8.decode(data, { 1: tlv8.DataType.FLOAT, 2: { 3: tlv8.DataType.STRING, 4: tlv8.DataType.STRING, }, 3: tlv8.DataType.INTEGER }) self.assertIsInstance(result, tlv8.EntryList) self.assertIsInstance(result[1].data, tlv8.EntryList)
def test_decode_257bytes(self): data = b'' for i in range(0, 257): data += pack('<B', i % 256) input_data = b'\x17\xff' + data[0:255] + b'\x17\x02' + data[255:] result = tlv8.decode(input_data) expected = tlv8.EntryList([ tlv8.Entry(23, data) ]) self.assertEqual(result, expected)
def decoder(bytes_data): return tlv8.decode( bytes_data, { SupportedAudioStreamConfigurationKeys.AUDIO_CODEC_CONFIGURATION: { AudioCodecConfigurationKeys.CODEC_TYPE: AudioCodecTypeValues, AudioCodecConfigurationKeys.AUDIO_CODEC_PARAMETERS: { AudioCodecParametersKeys.AUDIO_CHANNELS: tlv8.DataType.UNSIGNED_INTEGER, AudioCodecParametersKeys.BIT_RATE: BitRateValues, AudioCodecParametersKeys.SAMPLE_RATE: SampleRateValues, AudioCodecParametersKeys.RTP_TIME: RtpTimeValues } }, SupportedAudioStreamConfigurationKeys.COMFORT_NOISE_SUPPORT: {} })
def test_decode_enum(self): class TestKeys(enum.IntEnum): KEY_1 = 1 KEY_2 = 2 class TestValues(enum.IntEnum): VALUE_1 = 1 VALUE_2 = 2 data = b'\x01\x01\x02' result = tlv8.decode(data, {TestKeys.KEY_1: TestValues}) expected = tlv8.EntryList( [tlv8.Entry(TestKeys.KEY_1, TestValues.VALUE_2)]) self.assertIsInstance(result[0].type_id, TestKeys) self.assertIsInstance(result[0].data, TestValues) self.assertEqual(expected, result)
def test_decode_4(self): input_data = b'\x01\x01\x23\x02\x03\x04\x01\x42\x01\x01\x23' structure = { 1: tlv8.DataType.INTEGER, 2: { 4: tlv8.DataType.INTEGER, } } result = tlv8.decode(input_data, structure) expected = tlv8.EntryList([ tlv8.Entry(1, 0x23), tlv8.Entry(2, tlv8.EntryList([ tlv8.Entry(4, 0x42), ])), tlv8.Entry(1, 0x23), ]) self.assertEqual(expected, result)
def write_value(self, value): assert value[0] == 0 opcode = value[1] if opcode == 2: outer = { entry.type_id: entry.data for entry in tlv8.decode(value[7:]) } assert outer[ AdditionalParameterTypes.ParamReturnResponse] == b'\x01' value = self.rh.process_verify( value[2], outer[AdditionalParameterTypes.Value]) self.values.append(value) else: super().write_value(value)
def list_pairings(self): if not self.session: self.session = BleSession(self.pairing_data, self.adapter) request_tlv = tlv8.encode([ tlv8.Entry(TlvTypes.State, States.M1), tlv8.Entry(TlvTypes.Method, Methods.ListPairings) ]) request_tlv = tlv8.encode([ tlv8.Entry(AdditionalParameterTypes.ParamReturnResponse, bytearray(b'\x01')), tlv8.Entry(AdditionalParameterTypes.Value, request_tlv) ]) body = len(request_tlv).to_bytes(length=2, byteorder='little') + request_tlv cid = -1 for a in self.pairing_data['accessories']: for s in a['services']: for c in s['characteristics']: if CharacteristicsTypes.get_short_uuid(c['type'].upper( )) == CharacteristicsTypes.PAIRING_PAIRINGS: cid = c['iid'] fc, _ = self.session.find_characteristic_by_iid(cid) response = self.session.request(fc, cid, HapBleOpCodes.CHAR_WRITE, body) response = tlv8.decode( response.first_by_id(AdditionalParameterTypes.Value).data) tmp = [] r = {} for d in response[1:]: if d.type_id == TlvTypes.Identifier: r = {} tmp.append(r) r['pairingId'] = d.data.decode() if d.type_id == TlvTypes.PublicKey: r['publicKey'] = d.data.hex() if d.type_id == TlvTypes.Permissions: controller_type = 'regular' if d.data == b'\x01': controller_type = 'admin' r['permissions'] = int.from_bytes(d.data, byteorder='little') r['controllerType'] = controller_type tmp.sort(key=lambda x: x['pairingId']) return tmp
def decoder(bytes_data): return tlv8.decode(bytes_data, { SelectedRtpStreamConfigurationKeys.SESSION_CONTROL: { SessionControlKeys.SESSION_IDENTIFIER: tlv8.DataType.BYTES, SessionControlKeys.COMMAND: CommandValues, }, SelectedRtpStreamConfigurationKeys.SELECTED_AUDIO_PARAMS: { SelectedAudioParametersKeys.SELECTED_AUDIO_CODEC_TYPE: AudioCodecTypeValues, SelectedAudioParametersKeys.SELECTED_AUDIO_CODEC_PARAMETERS: { AudioCodecParametersKeys.AUDIO_CHANNELS: tlv8.DataType.INTEGER, AudioCodecParametersKeys.BIT_RATE: BitRateValues, AudioCodecParametersKeys.SAMPLE_RATE: SampleRateValues, AudioCodecParametersKeys.RTP_TIME: RtpTimeValues, }, SelectedAudioParametersKeys.SELECTED_AUDIO_RTP_PARAMETERS: { AudioRtpParametersKeys.PAYLOAD_TYPE: tlv8.DataType.INTEGER, AudioRtpParametersKeys.SSRC_FOR_AUDIO: tlv8.DataType.BYTES, AudioRtpParametersKeys.MAX_BITRATE: tlv8.DataType.INTEGER, AudioRtpParametersKeys.MIN_RTCP: tlv8.DataType.FLOAT, AudioRtpParametersKeys.COMFORT_NOISE: tlv8.DataType.INTEGER, }, SelectedAudioParametersKeys.COMFORT_NOISE: tlv8.DataType.INTEGER, }, SelectedRtpStreamConfigurationKeys.SELECTED_VIDEO_PARAMS: { SelectedVideoParametersKeys.SELECTED_VIDEO_CODEC_TYPE: VideoCodecTypeValues, SelectedVideoParametersKeys.SELECTED_VIDEO_CODEC_PARAMETERS: { VideoCodecParametersKeys.PROFILE_ID: ProfileIdValues, VideoCodecParametersKeys.LEVEL: LevelValues, VideoCodecParametersKeys.PACKETIZATION_MODE: PacketizationModeValues, VideoCodecParametersKeys.CVO_ENABLED: CVOEnabledValue, }, SelectedVideoParametersKeys.SELECTED_VIDEO_ATTRIBUTES: { VideoAttributesKeys.IMAGE_WIDTH: tlv8.DataType.INTEGER, VideoAttributesKeys.IMAGE_HEIGHT: tlv8.DataType.INTEGER, VideoAttributesKeys.FRAME_RATE: tlv8.DataType.INTEGER, }, SelectedVideoParametersKeys.SELECTED_VIDEO_RTP_PARAMETERS: { VideoRTPParametersKeys.PAYLOAD_TYPE: tlv8.DataType.INTEGER, VideoRTPParametersKeys.SSRC_FOR_VIDEO: tlv8.DataType.BYTES, VideoRTPParametersKeys.MAX_BITRATE: tlv8.DataType.INTEGER, VideoRTPParametersKeys.MIN_RTCP: tlv8.DataType.FLOAT, } } })
def decoder(bytes_data): srtp_params = { SrtpParameterKeys.SRTP_CRYPTO_SUITE: CameraSRTPCryptoSuiteValues, SrtpParameterKeys.SRTP_MASTER_KEY: tlv8.DataType.BYTES, SrtpParameterKeys.SRTP_MASTER_SALT: tlv8.DataType.BYTES, } return tlv8.decode(bytes_data, { SetupEndpointsKeys.SESSION_ID: tlv8.DataType.BYTES, SetupEndpointsKeys.STATUS: EndpointStatusValues, SetupEndpointsKeys.ADDRESS: { ControllerAddressKeys.IP_ADDRESS_VERSION: IPVersionValues, ControllerAddressKeys.IP_ADDRESS: tlv8.DataType.STRING, ControllerAddressKeys.VIDEO_RTP_PORT: tlv8.DataType.UNSIGNED_INTEGER, ControllerAddressKeys.AUDIO_RTP_PORT: tlv8.DataType.UNSIGNED_INTEGER, }, SetupEndpointsKeys.SRTP_PARAMETERS_FOR_VIDEO: srtp_params, SetupEndpointsKeys.SRTP_PARAMETERS_FOR_AUDIO: srtp_params, SetupEndpointsKeys.VIDEO_RTP_SSRC: tlv8.DataType.BYTES, SetupEndpointsKeys.AUDIO_RTP_SSRC: tlv8.DataType.BYTES, })
def parse_sig_read_response(data, expected_tid): # TODO document me # parse header and check stuff logger.debug('parse sig read response %s', bytes([int(a) for a in data]).hex()) # handle the header data cf = data[0] logger.debug('control field %d', cf) tid = data[1] logger.debug('transaction id %d (expected was %d)', tid, expected_tid) status = data[2] logger.debug('status code %d (%s)', status, HapBleStatusCodes[status]) assert cf == 0x02 assert tid == expected_tid assert status == HapBleStatusCodes.SUCCESS # get body length length = int.from_bytes(data[3:5], byteorder='little') logger.debug('expected body length %d (got %d)', length, len(data[5:])) # parse tlvs and analyse information tlv = tlv8.decode(bytes([int(a) for a in data[5:]])) description = '' characteristic_format = '' characteristic_range = None characteristic_step = None for t in tlv: if t.type_id == AdditionalParameterTypes.CharacteristicType: chr_type = [int(a) for a in t.data] chr_type.reverse() chr_type = str(uuid.UUID(''.join('%02x' % b for b in chr_type))) if t.type_id == AdditionalParameterTypes.ServiceInstanceId: svc_id = int.from_bytes(t.data, byteorder='little') if t.type_id == AdditionalParameterTypes.ServiceType: svc_type = [int(a) for a in t.data] svc_type.reverse() svc_type = str(uuid.UUID(''.join('%02x' % b for b in svc_type))) if t.type_id == AdditionalParameterTypes.HAPCharacteristicPropertiesDescriptor: chr_prop_int = int.from_bytes(t.data, byteorder='little') if t.type_id == AdditionalParameterTypes.GATTUserDescriptionDescriptor: description = t.data.decode() if t.type_id == AdditionalParameterTypes.HAPValidValuesDescriptor: print('valid values', t.data) if t.type_id == AdditionalParameterTypes.HAPValidValuesRangeDescriptor: print('valid values range', t.data) if t.type_id == AdditionalParameterTypes.GATTPresentationFormatDescriptor: unit_bytes = bytearray(t.data[2:4]) unit_bytes.reverse() characteristic_format = BleCharacteristicFormats.get( int(t.data[0]), 'unknown') unit = BleCharacteristicUnits.get( int.from_bytes(unit_bytes, byteorder='big'), 'unknown') if t.type_id == AdditionalParameterTypes.GATTValidRange: logger.debug('range: %s', t.data.hex()) lower = None upper = None if characteristic_format == 'int32' or characteristic_format == 'int': (lower, upper) = struct.unpack('ii', t.data) if characteristic_format == 'uint8': (lower, upper) = struct.unpack('BB', t.data) if characteristic_format == 'float': (lower, upper) = struct.unpack('ff', t.data) # TODO include all formats! characteristic_range = (lower, upper) if t.type_id == AdditionalParameterTypes.HAPStepValueDescriptor: characteristic_step = None if characteristic_format == 'int32': characteristic_step = struct.unpack('i', t.data)[0] if characteristic_format == 'uint8': characteristic_step = struct.unpack('B', t.data)[0] # TODO include all formats! # parse permissions # TODO refactor! perms = [] if (chr_prop_int & 0x0001) > 0: perms.append('r') if (chr_prop_int & 0x0002) > 0: perms.append('w') if (chr_prop_int & 0x0004) > 0: perms.append('aad') if (chr_prop_int & 0x0008) > 0: perms.append('tw') if (chr_prop_int & 0x0010) > 0: perms.append('pr') if (chr_prop_int & 0x0020) > 0: perms.append('pw') if (chr_prop_int & 0x0040) > 0: perms.append('hd') if (chr_prop_int & 0x0080) > 0: perms.append('evc') if (chr_prop_int & 0x0100) > 0: perms.append('evd') result = { 'description': description, 'perms': perms, 'format': characteristic_format, 'unit': unit, 'range': characteristic_range, 'step': characteristic_step, 'type': chr_type.upper(), 'sid': svc_id, 'service_type': svc_type } logger.debug('result: %s', str(result)) return result
def request(self, feature_char, feature_char_id, op, body=None): transaction_id = random.randrange(0, 255) data = bytearray([0x00, op, transaction_id]) data.extend(feature_char_id.to_bytes(length=2, byteorder='little')) if body: logger.debug('body: %s', body) data.extend(body) logger.debug('data: %s', data) cnt_bytes = self.c2a_counter.to_bytes(8, byteorder='little') cipher_and_mac = chacha20_aead_encrypt(bytes(), self.c2a_key, cnt_bytes, bytes([0, 0, 0, 0]), data) cipher_and_mac[0].extend(cipher_and_mac[1]) data = cipher_and_mac[0] logger.debug('cipher and mac %s', cipher_and_mac[0].hex()) result = feature_char.write_value(value=data) logger.debug('write resulted in: %s', result) self.c2a_counter += 1 data = [] while not data or len(data) == 0: time.sleep(1) logger.debug('reading characteristic') data = feature_char.read_value() if not data and not self.device.is_connected(): raise AccessoryDisconnectedError('Characteristic read failed') resp_data = bytearray([b for b in data]) logger.debug('read: %s', bytearray(resp_data).hex()) data = chacha20_aead_decrypt( bytes(), self.a2c_key, self.a2c_counter.to_bytes(8, byteorder='little'), bytes([0, 0, 0, 0]), resp_data) logger.debug('decrypted: %s', bytearray(data).hex()) if not data: return {} # parse header and check stuff logger.debug('parse sig read response %s', bytes([int(a) for a in data]).hex()) # handle the header data cf = data[0] logger.debug('control field %d', cf) tid = data[1] logger.debug('transaction id %d (expected was %d)', tid, transaction_id) status = data[2] logger.debug('status code %d (%s)', status, HapBleStatusCodes[status]) assert cf == 0x02 assert tid == transaction_id if status != HapBleStatusCodes.SUCCESS: raise RequestRejected(status, HapBleStatusCodes[status]) self.a2c_counter += 1 # get body length length = int.from_bytes(data[3:5], byteorder='little') logger.debug('expected body length %d (got %d)', length, len(data[5:])) # parse tlvs and analyse information tlv = tlv8.decode(data[5:]) logger.debug('received TLV: %s', tlv8.format_string(tlv)) return tlv