From 78bf27da7432d5d46b5dee2ee30dbfdf7a7b7c42 Mon Sep 17 00:00:00 2001 From: John Carr Date: Sun, 25 Aug 2019 17:50:42 +0100 Subject: [PATCH 1/2] Convert perform_pair_setup_part* to be state machines --- homekit/controller/ble_impl/__init__.py | 16 ++------- homekit/controller/controller.py | 45 ++++++++++++++++++++++--- homekit/protocol/__init__.py | 31 ++++++++--------- 3 files changed, 58 insertions(+), 34 deletions(-) diff --git a/homekit/controller/ble_impl/__init__.py b/homekit/controller/ble_impl/__init__.py index e10e11f8..fea44440 100644 --- a/homekit/controller/ble_impl/__init__.py +++ b/homekit/controller/ble_impl/__init__.py @@ -613,10 +613,11 @@ def find_characteristic_by_uuid(device, service_uuid, char_uuid): def create_ble_pair_setup_write(characteristic, characteristic_id): def write(request, expected): # TODO document me - logger.debug('entering write function %s', TLV.to_string(TLV.decode_bytes(request))) + body = TLV.encode_list(request) + logger.debug('entering write function %s', TLV.to_string(TLV.decode_bytes(body))) request_tlv = TLV.encode_list([ (TLV.kTLVHAPParamParamReturnResponse, bytearray(b'\x01')), - (TLV.kTLVHAPParamValue, request) + (TLV.kTLVHAPParamValue, body) ]) transaction_id = random.randrange(0, 255) data = bytearray([0x00, HapBleOpCodes.CHAR_WRITE, transaction_id]) @@ -654,17 +655,6 @@ def write(request, expected): return write -def create_ble_pair_verify_write(characteristic, characteristic_id): - # This is a temporary wrapper until the pairing functions are also migrated to - # the new state machine approach - pair_setup_write = create_ble_pair_setup_write(characteristic, characteristic_id) - - def write(request, expected): - return pair_setup_write(TLV.encode_list(request), expected) - - return write - - class ResolvingManager(DeviceManager): """ DeviceManager implementation that stops running after a given device was discovered. diff --git a/homekit/controller/controller.py b/homekit/controller/controller.py index ffce3010..6e16e7e4 100644 --- a/homekit/controller/controller.py +++ b/homekit/controller/controller.py @@ -380,7 +380,17 @@ def start_pairing(self, alias, accessory_id): try: write_fun = create_ip_pair_setup_write(conn) - salt, pub_key = perform_pair_setup_part1(write_fun) + + state_machine = perform_pair_setup_part1() + request, expected = state_machine.send(None) + while True: + try: + response = write_fun(request, expected) + request, expected = state_machine.send(response) + except StopIteration as result: + salt, pub_key = result.value + break + except Exception: conn.close() raise @@ -388,9 +398,18 @@ def start_pairing(self, alias, accessory_id): def finish_pairing(pin): Controller.check_pin_format(pin) try: - pairing = perform_pair_setup_part2(pin, str(uuid.uuid4()), write_fun, salt, pub_key) + state_machine = perform_pair_setup_part2(pin, str(uuid.uuid4()), salt, pub_key) + request, expected = state_machine.send(None) + while True: + try: + response = write_fun(request, expected) + request, expected = state_machine.send(response) + except StopIteration as result: + pairing = result.value + break finally: conn.close() + pairing['AccessoryIP'] = connection_data['ip'] pairing['AccessoryPort'] = connection_data['port'] pairing['Connection'] = 'IP' @@ -456,11 +475,29 @@ def start_pairing_ble(self, alias, accessory_mac, adapter='hci0'): logging.debug('setup char: %s %s', pair_setup_char, pair_setup_char.service.device) write_fun = create_ble_pair_setup_write(pair_setup_char, pair_setup_char_id) - salt, pub_key = perform_pair_setup_part1(write_fun) + + state_machine = perform_pair_setup_part1() + request, expected = state_machine.send(None) + while True: + try: + response = write_fun(request, expected) + request, expected = state_machine.send(response) + except StopIteration as result: + salt, pub_key = result.value + break def finish_pairing(pin): Controller.check_pin_format(pin) - pairing = perform_pair_setup_part2(pin, str(uuid.uuid4()), write_fun, salt, pub_key) + + state_machine = perform_pair_setup_part2(pin, str(uuid.uuid4()), salt, pub_key) + request, expected = state_machine.send(None) + while True: + try: + response = write_fun(request, expected) + request, expected = state_machine.send(response) + except StopIteration as result: + pairing = result.value + break pairing['AccessoryMAC'] = accessory_mac pairing['Connection'] = 'BLE' diff --git a/homekit/protocol/__init__.py b/homekit/protocol/__init__.py index b736c86b..bc70788a 100644 --- a/homekit/protocol/__init__.py +++ b/homekit/protocol/__init__.py @@ -57,11 +57,12 @@ def error_handler(error, stage): def create_ip_pair_setup_write(connection): def write_http(request, expected): - logging.debug('write message: %s', TLV.to_string(TLV.decode_bytes(request))) + body = TLV.encode_list(request) + logging.debug('write message: %s', TLV.to_string(TLV.decode_bytes(body))) connection.putrequest('POST', '/pair-setup', skip_accept_encoding=True) connection.putheader('Content-Type', 'application/pairing+tlv8') - connection.putheader('Content-Length', len(request)) - connection.endheaders(request) + connection.putheader('Content-Length', len(body)) + connection.endheaders(body) resp = connection.getresponse() response_tlv = TLV.decode_bytes(resp.read(), expected) logging.debug('response: %s', TLV.to_string(response_tlv)) @@ -86,12 +87,10 @@ def write_http(request, expected): return write_http -def perform_pair_setup_part1(write_fun): +def perform_pair_setup_part1(): """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. - :param write_fun: a function that takes a bytes representation of a TLV, the expected keys as list and returns - decoded TLV as list :return: a tuple of salt and server's public key :raises UnavailableError: if the device is already paired :raises MaxTriesError: if the device received more than 100 unsuccessful pairing attempts @@ -105,13 +104,13 @@ def perform_pair_setup_part1(write_fun): # Step #1 ios --> accessory (send SRP start Request) (see page 39) # logging.debug('#1 ios -> accessory: send SRP start request') - request_tlv = TLV.encode_list([ + request_tlv = [ (TLV.kTLVType_State, TLV.M1), (TLV.kTLVType_Method, TLV.PairSetup) - ]) + ] step2_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_PublicKey, TLV.kTLVType_Salt] - response_tlv = write_fun(request_tlv, step2_expectations) + response_tlv = yield (request_tlv, step2_expectations) # # Step #3 ios --> accessory (send SRP verify request) (see page 41) @@ -129,17 +128,16 @@ def perform_pair_setup_part1(write_fun): assert response_tlv[1][0] == TLV.kTLVType_PublicKey, 'perform_pair_setup: Not a public key' assert response_tlv[2][0] == TLV.kTLVType_Salt, 'perform_pair_setup: Not a salt' + return response_tlv[2][1], response_tlv[1][1] -def perform_pair_setup_part2(pin, ios_pairing_id, write_fun, salt, server_public_key): +def perform_pair_setup_part2(pin, ios_pairing_id, salt, server_public_key): """ Performs a pair setup operation as described in chapter 4.7 page 39 ff. :param pin: the setup code from the accessory :param ios_pairing_id: the id of the simulated ios device - :param write_fun: a function that takes a bytes representation of a TLV, the expected keys as list and returns - decoded TLV as list :return: a dict with the ios device's part of the pairing information :raises UnavailableError: if the device is already paired :raises MaxTriesError: if the device received more than 100 unsuccessful pairing attempts @@ -155,14 +153,14 @@ def perform_pair_setup_part2(pin, ios_pairing_id, write_fun, salt, server_public client_pub_key = srp_client.get_public_key() client_proof = srp_client.get_proof() - response_tlv = TLV.encode_list([ + response_tlv = [ (TLV.kTLVType_State, TLV.M3), (TLV.kTLVType_PublicKey, SrpClient.to_byte_array(client_pub_key)), (TLV.kTLVType_Proof, SrpClient.to_byte_array(client_proof)), - ]) + ] step4_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_Proof] - response_tlv = write_fun(response_tlv, step4_expectations) + response_tlv = yield (response_tlv, step4_expectations) # # Step #5 ios --> accessory (Exchange Request) (see page 43) @@ -220,10 +218,9 @@ def perform_pair_setup_part2(pin, ios_pairing_id, write_fun, salt, server_public (TLV.kTLVType_State, TLV.M5), (TLV.kTLVType_EncryptedData, tmp) ] - body = TLV.encode_list(response_tlv) step6_expectations = [TLV.kTLVType_State, TLV.kTLVType_Error, TLV.kTLVType_EncryptedData] - response_tlv = write_fun(body, step6_expectations) + response_tlv = yield (response_tlv, step6_expectations) # # Step #7 ios (Verification) (page 47) From ad8ea00e0f106a4639df9251bd3af1ebf6b7c960 Mon Sep 17 00:00:00 2001 From: John Carr Date: Sun, 25 Aug 2019 17:56:43 +0100 Subject: [PATCH 2/2] Remove use of create_ble_pair_setup_write --- homekit/controller/ble_impl/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/homekit/controller/ble_impl/__init__.py b/homekit/controller/ble_impl/__init__.py index fea44440..d3bca898 100644 --- a/homekit/controller/ble_impl/__init__.py +++ b/homekit/controller/ble_impl/__init__.py @@ -471,7 +471,7 @@ def __init__(self, pairing_data, adapter): # TODO Have exception here sys.exit(-1) - write_fun = create_ble_pair_verify_write(pair_verify_char, pair_verify_char_info['iid']) + write_fun = create_ble_pair_setup_write(pair_verify_char, pair_verify_char_info['iid']) state_machine = get_session_keys(self.pairing_data) request, expected = state_machine.send(None)