Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 4 additions & 14 deletions homekit/controller/ble_impl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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'])
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we do not have separate functions for create_ble_pair_verify_write and create_ble_pair_setup_write anymore, can we find a better name? perhaps create_characteristic_write_function?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can actually factor out the need for the create_ part entirely really, but i didn't want to over complicate this PR.

Now that we aren't passing it into the state machine we don't need to capture the scope. So it could just be a generic write helper like (pseudo-code, won't work as is):

def write_hap_param_value(char, iid, request, expected):
    pass

...

def setup_session():
    char, iid = find_char_by_type(PAIR_VERIFY)
    state_machine = ...
    while True:
        response = write_hap_param_value(char, iid, request, expected)
        request, expected = state_machine.send(response)

state_machine = get_session_keys(self.pairing_data)

request, expected = state_machine.send(None)
Expand Down Expand Up @@ -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])
Expand Down Expand Up @@ -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.
Expand Down
45 changes: 41 additions & 4 deletions homekit/controller/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,17 +380,36 @@ 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

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'
Expand Down Expand Up @@ -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'
Expand Down
31 changes: 14 additions & 17 deletions homekit/protocol/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand All @@ -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
Expand All @@ -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)
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down