def test_clients_read_descriptor(central, peripheral, dual_role): """It should be possible for a client to read a user defined descriptor.""" value = "00112233" descriptor_handle = instantiate_descriptor(dual_role, 0xDEAD, value) central_handle, dual_handle_as_peripheral = gap_connect(central, dual_role) dual_handle_as_central, peripheral_handle = gap_connect(dual_role, peripheral) verify_client_descriptor_value(central, central_handle, descriptor_handle, value) verify_client_descriptor_value(peripheral, peripheral_handle, descriptor_handle, value)
def test_cccd_not_shared_between_clients(central, peripheral, dual_role): """Client configuration descriptor values should not be shared across several clients""" cccd_uuid = 0x2902 # CCCD is in the characteristic, there is no need to redeclare it, instantiate another random descriptor instantiate_descriptor(dual_role, 0xDEAD, "FEED") central_handle, dual_handle_as_peripheral = gap_connect(central, dual_role) dual_handle_as_central, peripheral_handle = gap_connect(dual_role, peripheral) services = discover_user_services(central, central_handle) characteristic = services[0]["characteristics"][0] # discover this descriptor descriptors = discover_descriptors( central, central_handle, characteristic["start_handle"], characteristic["end_handle"] ) for descriptor in descriptors: if descriptor["UUID"] == cccd_uuid: descriptor_handle = descriptor["handle"] break def verify_cccds(expected_value_client_0, expected_value_client_1): verify_client_descriptor_value(central, central_handle, descriptor_handle, expected_value_client_0) assert expected_value_client_0 == dual_role.gattServer.read(descriptor_handle, dual_handle_as_peripheral).result verify_client_descriptor_value(peripheral, peripheral_handle, descriptor_handle, expected_value_client_1) assert expected_value_client_1 == dual_role.gattServer.read(descriptor_handle, dual_handle_as_central).result def write_value(client, handle, new_value): client.gattClient.writeCharacteristicDescriptor(handle, descriptor_handle, new_value) # at the beginning, CCCD should be equal to 0000 verify_cccds("0000", "0000") # enable notifications for client 0 (note, send in little endian) write_value(central, central_handle, "0100") verify_cccds("0100", "0000") # enable indications for client 1 (note, send in little endian) write_value(peripheral, peripheral_handle, "0200") verify_cccds("0100", "0200") # disable notifications for client 0 (note, send in little endian) write_value(central, central_handle, "0000") verify_cccds("0000", "0200") # enable notification for client 1 (note, send in little endian) write_value(peripheral, peripheral_handle, "0100") verify_cccds("0000", "0100")
def test_client_fail_write_not_writable_descriptor(central, peripheral): """It should not be possible for a client to write a descriptor not writable.""" # user description descriptor is not writable by default, use this property to simulate this behavior initial_value = "001122" new_value = "112233" user_description_uuid = 0x2901 # note: the descriptor handle returned by BLE_API is incorrect for user description descriptor_handle = instantiate_descriptor(dual_role, user_description_uuid, initial_value) central_handle, peripheral_handle = gap_connect(central, peripheral) gap_connect(dual_role, peripheral) central.gattClient.writeCharacteristicDescriptor(central_handle, descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, initial_value)
def test_gap_service(central, peripheral): """A GATT peripheral should provide a well formed Generic Attribute Profile service""" connection_handle, _ = gap_connect(central, peripheral) services = central.gattClient.discoverAllServicesAndCharacteristics( connection_handle).result # check that there is only one definition of the generic attribute service ga_services = [ s for s in services if s["UUID"] == GENERIC_ATTRIBUTE_SERVICE_UUID ] assert 1 == len(ga_services) ga_service = ga_services[0] ga_characteristics = ga_service["characteristics"] # there should be only one characteristic in this service assert 1 == len(ga_characteristics) # check that there is only one instance of the service changed characteristic # and that this characteristic is well formed service_changed_char = [ s for s in ga_characteristics if s["UUID"] == SERVICE_CHANGED_CHARACTERISTIC_UUID ] assert 1 == len(service_changed_char) assert ["indicate"] == service_changed_char[0]["properties"]
def characteristics_properties(central, peripheral, characteristic_uuid, set_property): # test all combinations except this illegal set if set_property == ["authSignedWrite"]: return peripheral.gattServer.declareService(0xFFFB) peripheral.gattServer.declareCharacteristic(characteristic_uuid) peripheral.gattServer.setCharacteristicProperties(*set_property) # if the characteristic is readable, the characteristic value can't be 0 # TODO: investigate if it is a bug or not peripheral.gattServer.setCharacteristicValue("AA") declared_service = peripheral.gattServer.commitService().result # assert that properties set match expectations assert set_property.sort( ) == declared_service["characteristics"][0]["properties"].sort() connection_handle_central, connection_handle_peripheral = gap_connect( central, peripheral) discovered_services = discover_user_services(central, connection_handle_central) assert set_property.sort( ) == discovered_services[0]["characteristics"][0]["properties"].sort() central.gap.disconnect(connection_handle_central, "USER_TERMINATION")
def test_fail_to_read_unreadable(central, peripheral): """It should not be possible for a client to read the value of a characteristic which is not readable.""" characteristic_handle = instantiate_service(peripheral, ["write"], "00112233") client_connection_handle, server_connection_handle = gap_connect( central, peripheral) central.gattClient.readCharacteristicValue.withRetcode(-1)( client_connection_handle, characteristic_handle)
def test_fail_to_write_not_writable(central, peripheral): """It should not be possible for a client to write the value of a characteristic which is not writable.""" characteristic_handle = instantiate_service(peripheral, ["read"], "00112233") client_connection_handle, server_connection_handle = gap_connect( central, peripheral) _ = central.gattClient.write.withRetcode(-1)(client_connection_handle, characteristic_handle, "44556677").result
def test_clients_write_descriptor(central, peripheral, dual_role): """It should be possible for a client to write a user defined descriptor.""" initial_value = "00112233" new_value_client1 = "11223344" new_value_client2 = "44332211" descriptor_handle = instantiate_descriptor(dual_role, 0xDEAD, initial_value) central_handle, dual_handle_as_peripheral = gap_connect(central, dual_role) dual_handle_as_central, peripheral_handle = gap_connect(dual_role, peripheral) def client_write(client, client_handle, new_value): client.gattClient.writeCharacteristicDescriptor(client_handle, descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, new_value) verify_client_descriptor_value(peripheral, peripheral_handle, descriptor_handle, new_value) assert new_value == dual_role.gattServer.read(descriptor_handle).result client_write(central, central_handle, new_value_client1) client_write(peripheral, peripheral_handle, new_value_client2)
def test_server_write_descriptor(central, peripheral): """It should be possible for a server to write a user defined descriptor.""" initial_value = "00112233" new_value = "11223344" descriptor_handle = instantiate_descriptor(peripheral, 0xDEAD, initial_value) central_handle, peripheral_handle = gap_connect(central, peripheral) peripheral.gattServer.write(descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, new_value) assert new_value == peripheral.gattServer.read(descriptor_handle).result
def test_read_characteristic(central, peripheral): """Reading a characteristic after its instantiation should return the value set during its instantiation""" characteristic_value = "00112233" characteristic_handle = instantiate_service(peripheral, ["read"], characteristic_value) assert characteristic_value == peripheral.gattServer.read( characteristic_handle).result client_connection_handle, server_connection_handle = gap_connect( central, peripheral) value_read_from_client = central.gattClient.readCharacteristicValue( client_connection_handle, characteristic_handle).result["data"] assert characteristic_value == value_read_from_client
def test_client_fail_write_descriptor_too_big(central, peripheral): """It should not be possible for a client to write a value larger than what a descriptor can accept""" initial_value = "00112233" new_value = "1122334455" descriptor_handle = instantiate_descriptor(peripheral, 0xDEAD, initial_value, max_length=4) central_handle, peripheral_handle = gap_connect(central, peripheral) central.gattClient.writeCharacteristicDescriptor.withRetcode(-1)(central_handle, descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, initial_value) assert initial_value == peripheral.gattServer.read(descriptor_handle).result
def test_write_not_writable_by_server(central, peripheral): """It should be possible for a server to write the value of a characteristic not writable by a client.""" characteristic_handle = instantiate_service(peripheral, ["read"], "00112233") client_connection_handle, server_connection_handle = gap_connect( central, peripheral) new_value = "11223344" peripheral.gattServer.write(characteristic_handle, new_value) assert new_value == peripheral.gattServer.read( characteristic_handle).result value_read_from_client = central.gattClient.readCharacteristicValue( client_connection_handle, characteristic_handle).result["data"] assert new_value == value_read_from_client
def test_server_catch_write_to_descriptor(central, peripheral): """It should be possible for a server to catch when a client write one of its descriptor.""" initial_value = "00112233" new_value = "11223344" descriptor_handle = instantiate_descriptor(peripheral, 0xDEAD, initial_value) central_handle, peripheral_handle = gap_connect(central, peripheral) data_written_event = peripheral.gattServer.waitForDataWritten.setAsync()(peripheral_handle, descriptor_handle, 5000) central.gattClient.writeCharacteristicDescriptor(central_handle, descriptor_handle, new_value) assert 0 == data_written_event.status
def test_client_write_descriptor_within_size(central, peripheral): """If the size of a descriptor is variable then it should be possible for a client to write this descriptor with a value which fit in the length of the descriptor.""" initial_value = "00112233" descriptor_length = 8 descriptor_handle = instantiate_descriptor(peripheral, 0xDEAD, initial_value, False, descriptor_length) central_handle, peripheral_handle = gap_connect(central, peripheral) for i in range(1, descriptor_length): new_value = "AA" * i central.gattClient.writeCharacteristicDescriptor(central_handle, descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, new_value) assert new_value == peripheral.gattServer.read(descriptor_handle).result
def test_server_write_not_writable_descriptor(central, peripheral): """It should be possible for a server to write a descriptor not writable by a client.""" # user description descriptor is not writable by default, use this property to simulate this behavior initial_value = "001122" new_value = "112233" user_description_uuid = 0x2901 # note: the descriptor handle returned by BLE_API is incorrect for user description descriptor_handle = instantiate_descriptor(peripheral, user_description_uuid, initial_value) central_handle, peripheral_handle = gap_connect(central, peripheral) peripheral.gattServer.write(descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, new_value) assert new_value == peripheral.gattServer.read(descriptor_handle).result
def test_cancel_connect_too_late(central, peripheral): """Cancelling a connection already established should have no effect""" peripheral_address = peripheral.gap.getAddress().result central_handle, peripheral_handle = gap_connect(central, peripheral) # we cancel after the connection completes and it should have no effect central.gap.cancelConnect().result sleep(2) # we make sure we're still connected by disconnecting now and checking the remote reason disconnection_cmd = peripheral.gap.waitForDisconnection.setAsync()(10000) central.gap.disconnect(central_handle, "USER_TERMINATION") disconnection_cmd.result assert disconnection_cmd.result['reason'] == 'REMOTE_USER_TERMINATED_CONNECTION'
def test_callback_on_write(central, peripheral): """It should be possible for a server to catch when a client write one of its characteristic.""" characteristic_handle = instantiate_service(peripheral, ["read", "write"], "00112233") client_connection_handle, server_connection_handle = gap_connect( central, peripheral) # write the new value new_value = "11223344" write_event = peripheral.gattServer.waitForDataWritten.setAsync()( server_connection_handle, characteristic_handle, 10000) central.gattClient.write(client_connection_handle, characteristic_handle, new_value) assert 0 == write_event.status assert server_connection_handle == write_event.result["connection_handle"] assert characteristic_handle == write_event.result["attribute_handle"] assert new_value == write_event.result["data"]
def test_write_characteristic_over_mtu(central, peripheral, length): """It should be possible for a client to write the value of a characteristic if the characteristic is writable.""" characteristic_handle = instantiate_service(peripheral, ["read", "write"], "AA" * length) client_connection_handle, server_connection_handle = gap_connect( central, peripheral) # write the new value new_value = "BB" * length central.gattClient.write(client_connection_handle, characteristic_handle, new_value) # check that the value is correct value_read_from_client = central.gattClient.readCharacteristicValue( client_connection_handle, characteristic_handle).result["data"] assert new_value == value_read_from_client
def test_client_write_within_variable_size(central, peripheral): """It should be possible for a client to write any characteristic value which fit in the length of the characteristic if the size of the characteristic is variable.""" initial_value = "00112233" characteristic_length = 8 # The characteristic len is not fixed and its length is twice the length of the initial value. characteristic_handle = instantiate_service(peripheral, ["read", "write"], initial_value, False, characteristic_length) client_connection_handle, server_connection_handle = gap_connect( central, peripheral) for i in range(1, characteristic_length): new_value = "AA" * i central.gattClient.write(client_connection_handle, characteristic_handle, new_value) assert new_value == peripheral.gattServer.read( characteristic_handle).result
def test_client_fail_write_fixed_size_with_wrong_size(central, peripheral): """If a characteristic has been instantiated with a fixed size then it should not be possible for a client to write this characteristic with a value of a different size.""" initial_value = "00112233" characteristic_length = len(initial_value) // 2 characteristic_handle = instantiate_service(peripheral, ["read", "write"], initial_value, True, characteristic_length) client_connection_handle, server_connection_handle = gap_connect( central, peripheral) for i in range(1, characteristic_length * 2): if i == characteristic_length: continue new_value = "AA" * i central.gattClient.write.withRetcode(-1)(client_connection_handle, characteristic_handle, new_value) assert initial_value == peripheral.gattServer.read( characteristic_handle).result
def test_client_fail_to_write_value_larger_than_max(central, peripheral): """It should not be possible for a client to write a value larger than what a characteristic can accept""" initial_value = "00112233" fixed_characteristic_handle = instantiate_service(peripheral, ["read", "write"], initial_value) variable_characteristic_handle = instantiate_service( peripheral, ["read", "write"], initial_value, True, len(initial_value) // 2) client_connection_handle, server_connection_handle = gap_connect( central, peripheral) central.gattClient.write.withRetcode(-1)(client_connection_handle, fixed_characteristic_handle, initial_value + "55") central.gattClient.write.withRetcode(-1)(client_connection_handle, variable_characteristic_handle, initial_value + "55") assert initial_value == peripheral.gattServer.read( fixed_characteristic_handle).result assert initial_value == peripheral.gattServer.read( variable_characteristic_handle).result
def write_fixed_value(central, peripheral): initial_value = "00112233" partial_updates = ["AA112233", "AAAA2233", "AAAAAA33", "AAAAAAAA"] descriptor_length = 4 descriptor_handle = instantiate_descriptor(peripheral, 0xDEAD, initial_value, True, descriptor_length) if central: central_handle, peripheral_handle = gap_connect(central, peripheral) # writes within fixed size should work by merging the values for i in range(1, descriptor_length): new_value = "AA" * i merged_value = partial_updates[i - 1] if central: central.gattClient.writeCharacteristicDescriptor(central_handle, descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, merged_value) else: peripheral.gattServer.write(descriptor_handle, new_value) assert merged_value == peripheral.gattServer.read(descriptor_handle).result # revert to initial value peripheral.gattServer.write(descriptor_handle, initial_value) if central: verify_client_descriptor_value(central, central_handle, descriptor_handle, initial_value) # writes larger than fixed size should be completely rejected for i in range(descriptor_length + 1, 8): new_value = "AA" * i if central: central.gattClient.writeCharacteristicDescriptor.withRetcode(-1)(central_handle, descriptor_handle, new_value) verify_client_descriptor_value(central, central_handle, descriptor_handle, initial_value) else: peripheral.gattServer.write.withRetcode(-1)(descriptor_handle, new_value) # expect no update to value assert initial_value == peripheral.gattServer.read(descriptor_handle).result
def test_allow_characteristic(central, peripheral, characteristic, descriptor): log.info('Testing {} with {} is allowed'.format(characteristic[0], descriptor[0])) characteristics = [{ "uuid": characteristic[1], "descriptors_uuid": descriptor[1] }] services_uuids = [0xBEAF, make_uuid()] declared_services = [] # declare services and commit them for uuid in services_uuids: peripheral.gattServer.declareService(uuid) for characteristic in characteristics: peripheral.gattServer.declareCharacteristic(characteristic["uuid"]) for descriptor_uuid in characteristic["descriptors_uuid"]: peripheral.gattServer.declareDescriptor(descriptor_uuid) declared_service = peripheral.gattServer.commitService().result # assert that characteristics UUID match and that services UUID match assert_uuid_equals(uuid, declared_service["UUID"]) assert len(characteristics) == len(declared_service["characteristics"]) for i in range(len(characteristics)): characteristic = characteristics[i] declared_characteristic = declared_service["characteristics"][i] assert_uuid_equals(characteristic["uuid"], declared_characteristic["UUID"]) descriptors_uuid = characteristic["descriptors_uuid"] declared_descriptors = declared_characteristic["descriptors"] for j in range(len(descriptors_uuid)): assert_uuid_equals(descriptors_uuid[j], declared_descriptors[j]["UUID"]) declared_services.append(declared_service) # connect central and peripheral and discover user services central_connection_handle, _ = gap_connect(central, peripheral) discovered_services = discover_user_services(central, central_connection_handle) # the number of services discovered should be equal to the number of services registered assert len(services_uuids) == len(discovered_services) # services discovery should return services in declaration order for i in range(len(services_uuids)): declared_service = declared_services[i] discovered_service = discovered_services[i] assert_uuid_equals(declared_service["UUID"], discovered_service["UUID"]) assert declared_service["handle"] == discovered_service["start_handle"] # discovery process should report the same characteristics as the ones registered assert len(declared_service["characteristics"]) == len( discovered_service["characteristics"]) for j in range(len(declared_service["characteristics"])): declared_characteristic = declared_service["characteristics"][j] discovered_characteristic = discovered_service["characteristics"][ j] assert declared_characteristic[ "value_handle"] == discovered_characteristic["value_handle"] assert_uuid_equals(declared_characteristic["UUID"], discovered_characteristic["UUID"]) # discover descriptors and compare against what has been registered discovered_descriptors = discover_descriptors( central, central_connection_handle, discovered_characteristic["start_handle"], discovered_characteristic["end_handle"]) assert len(declared_characteristic["descriptors"]) == len( discovered_descriptors) for k in range(len(declared_characteristic["descriptors"])): declared_descriptor = declared_characteristic["descriptors"][k] discovered_descriptor = discovered_descriptors[k] assert_uuid_equals(declared_descriptor["UUID"], discovered_descriptor["UUID"]) assert declared_descriptor["handle"] == discovered_descriptor[ "handle"]
def test_client_write_invalid_attribute(central, peripheral): """If a client try to write an invalid attribute handle, it should return an error""" client_connection_handle, server_connection_handle = gap_connect( central, peripheral) central.gattClient.write.withRetcode(-1)(client_connection_handle, 0xFFEE, "AA")
def test_client_read_invalid_attribute(central, peripheral): """If a client try to read an invalid attribute handle, it should return an error""" client_connection_handle, server_connection_handle = gap_connect( central, peripheral) central.gattClient.readCharacteristicValue.withRetcode(-1)( client_connection_handle, 0xFFEE)
def test_characteristic_has_cccd(central, peripheral, char_property, descriptor): log.info( 'Testing characteristic with a property {} and {} has a CCCD available' .format(char_property[0], descriptor[0])) properties = char_property[1] descriptors = descriptor[1] services_uuids = [0xBEAF, make_uuid()] characteristics = [0xDEAD, make_uuid()] declared_services = [] # declare services and commit them for uuid in services_uuids: peripheral.gattServer.declareService(uuid) for characteristic in characteristics: peripheral.gattServer.declareCharacteristic(characteristic) peripheral.gattServer.setCharacteristicProperties(*properties) for descriptor in descriptors: peripheral.gattServer.declareDescriptor(descriptor) declared_service = peripheral.gattServer.commitService().result # assert that characteristics UUID match and that services UUID match assert_uuid_equals(uuid, declared_service["UUID"]) assert len(characteristics) == len(declared_service["characteristics"]) for i in range(len(characteristics)): characteristic = characteristics[i] declared_characteristic = declared_service["characteristics"][i] assert_uuid_equals(characteristic, declared_characteristic["UUID"]) declared_descriptors = declared_characteristic["descriptors"] for j in range(len(descriptors)): assert_uuid_equals(descriptors[j], declared_descriptors[j]["UUID"]) declared_services.append(declared_service) # connect central and peripheral and discover user services central_connection_handle, _ = gap_connect(central, peripheral) discovered_services = discover_user_services(central, central_connection_handle) # the number of services discovered should be equal to the number of services registered assert len(services_uuids) == len(discovered_services) # services discovery should return services in declaration order for i in range(len(services_uuids)): declared_service = declared_services[i] discovered_service = discovered_services[i] assert_uuid_equals(declared_service["UUID"], discovered_service["UUID"]) assert declared_service["handle"] == discovered_service["start_handle"] # discovery process should report the same characteristics as the ones registered assert len(declared_service["characteristics"]) == len( discovered_service["characteristics"]) for j in range(len(declared_service["characteristics"])): declared_characteristic = declared_service["characteristics"][j] discovered_characteristic = discovered_service["characteristics"][ j] assert declared_characteristic[ "value_handle"] == discovered_characteristic["value_handle"] assert_uuid_equals(declared_characteristic["UUID"], discovered_characteristic["UUID"]) # discover descriptors and compare against what has been registered discovered_descriptors = discover_descriptors( central, central_connection_handle, discovered_characteristic["start_handle"], discovered_characteristic["end_handle"]) # find the CCCD cccd = [x for x in discovered_descriptors if x["UUID"] == 0x2902] assert 1 == len(cccd) discovered_descriptors.remove(cccd[0]) # notify/indicate descriptor is implicit assert len(declared_characteristic["descriptors"]) == len( discovered_descriptors) for k in range(len(declared_characteristic["descriptors"])): declared_descriptor = declared_characteristic["descriptors"][k] discovered_descriptor = discovered_descriptors[k] assert_uuid_equals(declared_descriptor["UUID"], discovered_descriptor["UUID"]) assert declared_descriptor["handle"] == discovered_descriptor[ "handle"]
def connect(self, responder_session): central_handle, peripheral_handle = gap_connect(self.dut, responder_session.dut) self.connection_handle = central_handle responder_session.connection_handle = peripheral_handle return central_handle, peripheral_handle