diff --git a/tests/multi_bluetooth/ble_gap_pair.py b/tests/multi_bluetooth/ble_gap_pair.py new file mode 100644 index 000000000..728513405 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair.py @@ -0,0 +1,129 @@ +# Test BLE GAP connect/disconnect with pairing, and read an encrypted characteristic +# TODO: add gap_passkey testing + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 4000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_ENCRYPTION_UPDATE = const(28) + +_FLAG_READ = const(0x0002) +_FLAG_READ_ENCRYPTED = const(0x0200) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = (CHAR_UUID, _FLAG_READ | _FLAG_READ_ENCRYPTED) +SERVICE = (SERVICE_UUID, (CHAR,)) + +waiting_events = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_READ_REQUEST: + print("_IRQ_GATTS_READ_REQUEST") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_READ_RESULT: + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_ENCRYPTION_UPDATE: + print("_IRQ_ENCRYPTION_UPDATE", data[1], data[2], data[3]) + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services((SERVICE,)) + ble.gatts_write(char_handle, "encrypted") + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect. + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + + # Wait for pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Wait for GATTS read request. + wait_for_event(_IRQ_GATTS_READ_REQUEST, TIMEOUT_MS) + + # Wait for central to disconnect. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + # Connect to peripheral. + print("gap_connect") + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + # Discover characteristics (before pairing, doesn't need to be encrypted). + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + # Pair with the peripheral. + print("gap_pair") + ble.gap_pair(conn_handle) + + # Wait for the pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Read the peripheral's characteristic, should be encrypted. + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + + # Disconnect from the peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.config(mitm=True, le_secure=True, bond=False) +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_gap_pair.py.exp b/tests/multi_bluetooth/ble_gap_pair.py.exp new file mode 100644 index 000000000..9efe0fb22 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair.py.exp @@ -0,0 +1,17 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_ENCRYPTION_UPDATE 1 0 0 +_IRQ_GATTS_READ_REQUEST +_IRQ_CENTRAL_DISCONNECT +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gap_pair +_IRQ_ENCRYPTION_UPDATE 1 0 0 +gattc_read +_IRQ_GATTC_READ_RESULT b'encrypted' +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT diff --git a/tests/multi_bluetooth/ble_gap_pair_bond.py b/tests/multi_bluetooth/ble_gap_pair_bond.py new file mode 100644 index 000000000..a29c21788 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair_bond.py @@ -0,0 +1,134 @@ +# Test BLE GAP connect/disconnect with pairing and bonding, and read an encrypted +# characteristic +# TODO: reconnect after bonding to test that the secrets persist + +from micropython import const +import time, machine, bluetooth + +TIMEOUT_MS = 4000 + +_IRQ_CENTRAL_CONNECT = const(1) +_IRQ_CENTRAL_DISCONNECT = const(2) +_IRQ_GATTS_READ_REQUEST = const(4) +_IRQ_PERIPHERAL_CONNECT = const(7) +_IRQ_PERIPHERAL_DISCONNECT = const(8) +_IRQ_GATTC_CHARACTERISTIC_RESULT = const(11) +_IRQ_GATTC_CHARACTERISTIC_DONE = const(12) +_IRQ_GATTC_READ_RESULT = const(15) +_IRQ_ENCRYPTION_UPDATE = const(28) +_IRQ_SET_SECRET = const(30) + +_FLAG_READ = const(0x0002) +_FLAG_READ_ENCRYPTED = const(0x0200) + +SERVICE_UUID = bluetooth.UUID("A5A5A5A5-FFFF-9999-1111-5A5A5A5A5A5A") +CHAR_UUID = bluetooth.UUID("00000000-1111-2222-3333-444444444444") +CHAR = (CHAR_UUID, _FLAG_READ | _FLAG_READ_ENCRYPTED) +SERVICE = (SERVICE_UUID, (CHAR,)) + +waiting_events = {} +secrets = {} + + +def irq(event, data): + if event == _IRQ_CENTRAL_CONNECT: + print("_IRQ_CENTRAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_CENTRAL_DISCONNECT: + print("_IRQ_CENTRAL_DISCONNECT") + elif event == _IRQ_GATTS_READ_REQUEST: + print("_IRQ_GATTS_READ_REQUEST") + elif event == _IRQ_PERIPHERAL_CONNECT: + print("_IRQ_PERIPHERAL_CONNECT") + waiting_events[event] = data[0] + elif event == _IRQ_PERIPHERAL_DISCONNECT: + print("_IRQ_PERIPHERAL_DISCONNECT") + elif event == _IRQ_GATTC_CHARACTERISTIC_RESULT: + if data[-1] == CHAR_UUID: + print("_IRQ_GATTC_CHARACTERISTIC_RESULT", data[-1]) + waiting_events[event] = data[2] + else: + return + elif event == _IRQ_GATTC_CHARACTERISTIC_DONE: + print("_IRQ_GATTC_CHARACTERISTIC_DONE") + elif event == _IRQ_GATTC_READ_RESULT: + print("_IRQ_GATTC_READ_RESULT", bytes(data[-1])) + elif event == _IRQ_ENCRYPTION_UPDATE: + print("_IRQ_ENCRYPTION_UPDATE", data[1], data[2], data[3]) + elif event == _IRQ_SET_SECRET: + return True + + if event not in waiting_events: + waiting_events[event] = None + + +def wait_for_event(event, timeout_ms): + t0 = time.ticks_ms() + while time.ticks_diff(time.ticks_ms(), t0) < timeout_ms: + if event in waiting_events: + return waiting_events.pop(event) + machine.idle() + raise ValueError("Timeout waiting for {}".format(event)) + + +# Acting in peripheral role. +def instance0(): + multitest.globals(BDADDR=ble.config("mac")) + ((char_handle,),) = ble.gatts_register_services((SERVICE,)) + ble.gatts_write(char_handle, "encrypted") + print("gap_advertise") + ble.gap_advertise(20_000, b"\x02\x01\x06\x04\xffMPY") + multitest.next() + try: + # Wait for central to connect. + wait_for_event(_IRQ_CENTRAL_CONNECT, TIMEOUT_MS) + + # Wait for pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Wait for GATTS read request. + wait_for_event(_IRQ_GATTS_READ_REQUEST, TIMEOUT_MS) + + # Wait for central to disconnect. + wait_for_event(_IRQ_CENTRAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +# Acting in central role. +def instance1(): + multitest.next() + try: + # Connect to peripheral. + print("gap_connect") + ble.gap_connect(*BDADDR) + conn_handle = wait_for_event(_IRQ_PERIPHERAL_CONNECT, TIMEOUT_MS) + + # Discover characteristics (before pairing, doesn't need to be encrypted). + ble.gattc_discover_characteristics(conn_handle, 1, 65535) + value_handle = wait_for_event(_IRQ_GATTC_CHARACTERISTIC_RESULT, TIMEOUT_MS) + wait_for_event(_IRQ_GATTC_CHARACTERISTIC_DONE, TIMEOUT_MS) + + # Pair with the peripheral. + print("gap_pair") + ble.gap_pair(conn_handle) + + # Wait for the pairing event. + wait_for_event(_IRQ_ENCRYPTION_UPDATE, TIMEOUT_MS) + + # Read the peripheral's characteristic, should be encrypted. + print("gattc_read") + ble.gattc_read(conn_handle, value_handle) + wait_for_event(_IRQ_GATTC_READ_RESULT, TIMEOUT_MS) + + # Disconnect from the peripheral. + print("gap_disconnect:", ble.gap_disconnect(conn_handle)) + wait_for_event(_IRQ_PERIPHERAL_DISCONNECT, TIMEOUT_MS) + finally: + ble.active(0) + + +ble = bluetooth.BLE() +ble.config(mitm=True, le_secure=True, bond=True) +ble.active(1) +ble.irq(irq) diff --git a/tests/multi_bluetooth/ble_gap_pair_bond.py.exp b/tests/multi_bluetooth/ble_gap_pair_bond.py.exp new file mode 100644 index 000000000..271403a71 --- /dev/null +++ b/tests/multi_bluetooth/ble_gap_pair_bond.py.exp @@ -0,0 +1,17 @@ +--- instance0 --- +gap_advertise +_IRQ_CENTRAL_CONNECT +_IRQ_ENCRYPTION_UPDATE 1 0 1 +_IRQ_GATTS_READ_REQUEST +_IRQ_CENTRAL_DISCONNECT +--- instance1 --- +gap_connect +_IRQ_PERIPHERAL_CONNECT +_IRQ_GATTC_CHARACTERISTIC_RESULT UUID('00000000-1111-2222-3333-444444444444') +_IRQ_GATTC_CHARACTERISTIC_DONE +gap_pair +_IRQ_ENCRYPTION_UPDATE 1 0 1 +gattc_read +_IRQ_GATTC_READ_RESULT b'encrypted' +gap_disconnect: True +_IRQ_PERIPHERAL_DISCONNECT