Ejemplo n.º 1
0
    def set_advertise_data(self,
                           advertise_data=AdvertisingData(),
                           scan_response=AdvertisingData()):
        """
        Sets the advertising and scan response data which will be broadcasted to peers during advertising

        Note: BLE Restricts advertise and scan response data to an encoded length of 31 bytes each.
        Use AdvertisingData.check_encoded_length() to determine if the

        :param advertise_data: The advertise data to use
        :type advertise_data: AdvertisingData
        :param scan_response: The scan response data to use
        :type scan_response: AdvertisingData
        """
        adv_len, adv_pass = advertise_data.check_encoded_length()
        scan_len, scan_pass = advertise_data.check_encoded_length()

        if not adv_pass:
            raise exceptions.InvalidOperationException(
                "Encoded Advertising data length is too long ({} bytes). "
                "Max: {} bytes".format(adv_len,
                                       advertise_data.MAX_ENCODED_LENGTH))

        if not scan_pass:
            raise exceptions.InvalidOperationException(
                "Encoded Scan Response data length is too long ({} bytes). "
                "Max: {} bytes".format(scan_len,
                                       advertise_data.MAX_ENCODED_LENGTH))

        self.ble_device.ble_driver.ble_gap_adv_data_set(
            advertise_data.to_ble_adv_data(), scan_response.to_ble_adv_data())
Ejemplo n.º 2
0
 def setUp(self) -> None:
     self.adv_interval_ms = 20
     self.adv_mac_addr = self.dev1.address
     self.adv_mode = AdvertisingMode.scanable_undirected
     self.scan_params = ScanParameters(MIN_SCAN_INTERVAL_MS, MIN_SCAN_WINDOW_MS, 4)
     self.flags = AdvertisingFlags.GENERAL_DISCOVERY_MODE | AdvertisingFlags.BR_EDR_NOT_SUPPORTED
     self.uuid16s = [Uuid16(0xABCD), Uuid16(0xDEF0)]
     self.default_adv_data = AdvertisingData(flags=self.flags, local_name="Blatann Test")
     self.default_adv_data_bytes = self.default_adv_data.to_bytes()
     self.default_scan_data = AdvertisingData(service_uuid16s=self.uuid16s)
     self.default_scan_data_bytes = self.default_scan_data.to_bytes()
Ejemplo n.º 3
0
 def setUp(self) -> None:
     self.adv_interval_ms = 50
     self.adv_duration = 5
     self.adv_mode = AdvertisingMode.scanable_undirected
     self.flags = AdvertisingFlags.GENERAL_DISCOVERY_MODE | AdvertisingFlags.BR_EDR_NOT_SUPPORTED
     self._configure_adv()
     self._configure_scan()
     self.adv_mac_addr = self.dev1.address
     self.uuid16s = [Uuid16(0xABCD), Uuid16(0xDEF0)]
     self.uuid128 = NORDIC_UART_SERVICE_UUID
     self.default_adv_data = AdvertisingData(flags=self.flags,
                                             local_name="Blatann Test")
     self.default_adv_data_bytes = self.default_adv_data.to_bytes()
     self.default_scan_data = AdvertisingData(service_uuid16s=self.uuid16s)
     self.default_scan_data_bytes = self.default_scan_data.to_bytes()
Ejemplo n.º 4
0
    def _setup_connection(cls):
        cls.periph.client.preferred_mtu_size = cls.periph.max_mtu_size
        conn_params = ConnectionParameters(conn_interval_range.min,
                                           conn_interval_range.min, 4000)

        cls.periph.set_default_peripheral_connection_params(
            conn_params.min_conn_interval_ms, conn_params.max_conn_interval_ms,
            conn_params.conn_sup_timeout_ms)
        cls.periph.advertiser.set_advertise_data(
            AdvertisingData(flags=0x06, local_name="Blatann Test"))
        adv_addr = cls.periph.address

        # Start advertising, then initiate connection from central.
        # Once central reports its connected wait for the peripheral to be connected before continuing
        waitable = cls.periph.advertiser.start(timeout_sec=30)
        cls.central_conn.peer = cls.central.connect(adv_addr,
                                                    conn_params).wait(10)
        cls.periph_conn.peer = waitable.wait(10)

        cls.central_conn.peer.exchange_mtu(cls.central.max_mtu_size).wait(10)
        cls.central_conn.peer.update_data_length().wait(10)
        cls.central_conn.peer.update_phy(Phy.two_mbps).wait(10)
        cls.central_conn.peer.discover_services().wait(10)
        cls.central_conn.write_char = cls.central_conn.db.find_characteristic(
            cls.write_char_uuid)
        cls.central_conn.write_no_resp_char = cls.central_conn.db.find_characteristic(
            cls.write_no_resp_char_uuid)
Ejemplo n.º 5
0
    def test_dynamic_adv_data_update(self):
        self._configure_adv(
            duration=20, adv_mode=AdvertisingMode.non_connectable_undirected)
        self._configure_scan(10)
        service_data = [0xAB, 0xCD, 0x00]
        service_data_preamble = service_data[:-1]
        iterations = 10
        adv_data = AdvertisingData(service_data=service_data)
        self.dev1.advertiser.set_advertise_data(adv_data)
        self.dev1.advertiser.start()
        self.dev2.scanner.start_scan()

        for i in range(iterations):
            time.sleep(0.5)
            adv_data.service_data[-1] += 1
            self.dev1.advertiser.set_advertise_data(adv_data)

        time.sleep(0.5)
        self.dev2.scanner.stop()
        self.dev1.advertiser.stop()

        results = self.dev2.scanner.scan_report

        all_packets, adv_packets, scan_response_packets = self._get_packets_for_adv(
            results)
        self.assertGreater(len(all_packets), 0)
        self.assertEqual(len(all_packets), len(adv_packets))
        self.assertEqual(0, len(scan_response_packets))
        non_dupes = [p for p in adv_packets if not p.duplicate]

        self.assertEqual(iterations + 1, len(non_dupes))
        for i, packet in enumerate(non_dupes):
            expected_service_data = bytes(service_data_preamble + [i])
            self.assertEqual(expected_service_data,
                             packet.advertise_data.service_data)
Ejemplo n.º 6
0
    def setUp(self) -> None:
        self.adv_interval_ms = 50
        self.adv_duration = 5
        self.adv_mode = AdvertisingMode.non_connectable_undirected
        self.adv_data = AdvertisingData(flags=0x06, local_name="Blatann Test")

        self.dev1.advertiser.set_advertise_data(self.adv_data)
        self.dev1.advertiser.set_default_advertise_params(self.adv_interval_ms, self.adv_duration, self.adv_mode)
Ejemplo n.º 7
0
 def setUpClass(cls) -> None:
     super(TestSecurity, cls).setUpClass()
     cls.periph_dev = cls.dev1
     cls.central_dev = cls.dev2
     cls.periph_dev.advertiser.set_advertise_data(AdvertisingData(flags=0x06, local_name="BlatannTest"))
     cls.periph_dev.advertiser.set_default_advertise_params(30, 0)
     cls.central_dev.scanner.set_default_scan_params(50, 50, 10, False)
     cls.central_dev.set_default_peripheral_connection_params(10, 10, 4000)
     cls.peer_cen = cls.periph_dev.client
Ejemplo n.º 8
0
    def set_advertise_data(self,
                           advertise_data: AdvertisingData = AdvertisingData(),
                           scan_response: AdvertisingData = AdvertisingData()):
        """
        Sets the advertising and scan response data which will be broadcasted to peers during advertising

        .. note:: BLE Restricts advertise and scan response data to an encoded length of 31 bytes each.
           Use :meth:`AdvertisingData.check_encoded_length() <blatann.gap.advertise_data.AdvertiseData.check_encoded_length>`
           to determine if the payload is too large

        :param advertise_data: The advertising data to use
        :param scan_response: The scan response data to use.
                              This data is only sent when a scanning device requests the scan response packet (active scanning)
        :raises: InvalidOperationException if one of the payloads is too large
        """
        adv_len, adv_pass = advertise_data.check_encoded_length()
        scan_len, scan_pass = scan_response.check_encoded_length()

        if not adv_pass:
            raise exceptions.InvalidOperationException(
                "Encoded Advertising data length is too long ({} bytes). "
                "Max: {} bytes".format(adv_len,
                                       advertise_data.MAX_ENCODED_LENGTH))

        if not scan_pass:
            raise exceptions.InvalidOperationException(
                "Encoded Scan Response data length is too long ({} bytes). "
                "Max: {} bytes".format(scan_len,
                                       advertise_data.MAX_ENCODED_LENGTH))

        self.ble_device.ble_driver.ble_gap_adv_data_set(
            advertise_data.to_ble_adv_data(), scan_response.to_ble_adv_data())
Ejemplo n.º 9
0
class TestScanner(BlatannTestCase):
    def setUp(self) -> None:
        self.adv_interval_ms = 20
        self.adv_mac_addr = self.dev1.address
        self.adv_mode = AdvertisingMode.scanable_undirected
        self.scan_params = ScanParameters(MIN_SCAN_INTERVAL_MS, MIN_SCAN_WINDOW_MS, 4)
        self.flags = AdvertisingFlags.GENERAL_DISCOVERY_MODE | AdvertisingFlags.BR_EDR_NOT_SUPPORTED
        self.uuid16s = [Uuid16(0xABCD), Uuid16(0xDEF0)]
        self.default_adv_data = AdvertisingData(flags=self.flags, local_name="Blatann Test")
        self.default_adv_data_bytes = self.default_adv_data.to_bytes()
        self.default_scan_data = AdvertisingData(service_uuid16s=self.uuid16s)
        self.default_scan_data_bytes = self.default_scan_data.to_bytes()

    def tearDown(self) -> None:
        self.dev1.advertiser.stop()
        self.dev2.scanner.stop()

    def _get_packets_for_adv(self, results):
        all_packets = [p for p in results.all_scan_reports if p.peer_address == self.adv_mac_addr]
        adv_packets = [p for p in all_packets if p.packet_type == self.adv_mode]
        scan_response_packets = [p for p in all_packets if p.packet_type == AdvertisingPacketType.scan_response]
        return all_packets, adv_packets, scan_response_packets

    @TestParams([dict(duration=x) for x in [1, 2, 4, 10]], long_running_params=
                [dict(duration=x) for x in [60, 120]])
    def test_scan_duration(self, duration):
        acceptable_delta = 0.100
        on_timeout_event = threading.Event()
        self.scan_params.timeout_s = duration

        self.dev1.advertiser.start(self.adv_interval_ms, duration+2)

        def on_timeout(*args, **kwargs):
            on_timeout_event.set()

        with self.dev2.scanner.on_scan_timeout.register(on_timeout):
            with Stopwatch() as stopwatch:
                self.dev2.scanner.start_scan(self.scan_params)
                on_timeout_event.wait(duration + 2)

        self.assertTrue(on_timeout_event.is_set())
        self.assertFalse(self.dev2.scanner.is_scanning)

        actual_delta = abs(duration - stopwatch.elapsed)
        self.assertLessEqual(actual_delta, acceptable_delta)
        self.logger.info("Delta: {:.3f}".format(actual_delta))

    def test_scan_iterator(self):
        acceptable_delta = 0.100
        self.scan_params.timeout_s = 5

        self.dev1.advertiser.start(self.adv_interval_ms, self.scan_params.timeout_s+2)

        adv_address = self.dev1.address
        report_count_from_advertiser = 0
        with Stopwatch() as stopwatch:
            for report in self.dev2.scanner.start_scan(self.scan_params).scan_reports:
                if report.peer_address == adv_address:
                    report_count_from_advertiser += 1

        self.assertGreater(report_count_from_advertiser, 0)
        self.assertDeltaWithin(self.scan_params.timeout_s, stopwatch.elapsed, acceptable_delta)

    def test_non_active_scanning_no_scan_response_packets_received(self):
        self.dev1.advertiser.set_advertise_data(self.default_adv_data, self.default_scan_data)
        self.dev1.advertiser.start(advertise_mode=self.adv_mode)
        self.scan_params.active = False
        results = self.dev2.scanner.start_scan(self.scan_params).wait(10)

        # Get the list of all advertising packets from the advertiser
        all_packets, adv_packets, scan_response_packets = self._get_packets_for_adv(results)
        self.assertGreater(len(all_packets), 0)
        self.assertEqual(len(all_packets), len(adv_packets))
        self.assertEqual(0, len(scan_response_packets))

        for p in adv_packets:
            self.assertEqual(self.default_adv_data_bytes, p.raw_bytes)
Ejemplo n.º 10
0
class TestAdvertisingData(BlatannTestCase):
    def setUp(self) -> None:
        self.adv_interval_ms = 50
        self.adv_duration = 5
        self.adv_mode = AdvertisingMode.scanable_undirected
        self.flags = AdvertisingFlags.GENERAL_DISCOVERY_MODE | AdvertisingFlags.BR_EDR_NOT_SUPPORTED
        self._configure_adv()
        self._configure_scan()
        self.adv_mac_addr = self.dev1.address
        self.uuid16s = [Uuid16(0xABCD), Uuid16(0xDEF0)]
        self.uuid128 = NORDIC_UART_SERVICE_UUID
        self.default_adv_data = AdvertisingData(flags=self.flags,
                                                local_name="Blatann Test")
        self.default_adv_data_bytes = self.default_adv_data.to_bytes()
        self.default_scan_data = AdvertisingData(service_uuid16s=self.uuid16s)
        self.default_scan_data_bytes = self.default_scan_data.to_bytes()

    def _configure_adv(self,
                       duration=10,
                       adv_mode=AdvertisingMode.scanable_undirected):
        self.adv_mode = adv_mode
        self.dev1.advertiser.set_default_advertise_params(
            25, duration, self.adv_mode)

    def _configure_scan(self, duration=4, active_scan=True):
        self.dev2.scanner.set_default_scan_params(100, 100, duration, True)

    def _get_packets_for_adv(self, results):
        all_packets = [
            p for p in results.all_scan_reports
            if p.peer_address == self.adv_mac_addr
        ]
        adv_packets = [
            p for p in all_packets if p.packet_type == self.adv_mode
        ]
        scan_response_packets = [
            p for p in all_packets
            if p.packet_type == AdvertisingPacketType.scan_response
        ]

        return all_packets, adv_packets, scan_response_packets

    def tearDown(self) -> None:
        self.dev1.advertiser.stop()
        self.dev2.scanner.stop()
        time.sleep(0.5)

    def test_advertising_no_scan_data(self):
        self.dev1.advertiser.set_advertise_data(self.default_adv_data)
        self.dev1.advertiser.start()
        results = self.dev2.scanner.start_scan(
            clear_scan_reports=True).wait(10)

        # Get the list of all advertising packets from the advertiser
        all_packets, adv_packets, scan_response_packets = self._get_packets_for_adv(
            results)
        self.assertGreater(len(all_packets), 0)
        self.assertGreater(len(adv_packets), 0)
        self.assertGreater(len(scan_response_packets), 0)
        self.assertEqual(len(scan_response_packets),
                         len(all_packets) - len(adv_packets))

        # Check the contents of the received advertising packets to make sure they are the same
        for packet in adv_packets:
            if packet.packet_type == AdvertisingPacketType.scan_response:
                # Scan responses should be empty
                self.assertEqual(b"", packet.raw_bytes)
            else:
                self.assertEqual(self.default_adv_data_bytes, packet.raw_bytes)

    def test_advertising_scan_data(self):
        self.dev1.advertiser.set_advertise_data(self.default_adv_data,
                                                self.default_scan_data)
        self.dev1.advertiser.start()

        results = self.dev2.scanner.start_scan(
            clear_scan_reports=True).wait(10)

        # Get the list of all advertising packets from the advertiser
        all_packets, adv_packets, scan_response_packets = self._get_packets_for_adv(
            results)
        self.assertGreater(len(all_packets), 0)
        self.assertGreater(len(adv_packets), 0)
        self.assertGreater(len(scan_response_packets), 0)
        self.assertEqual(len(scan_response_packets),
                         len(all_packets) - len(adv_packets))

        # Check the contents of the received advertising packets to make sure they are the same
        for packet in adv_packets:
            if packet.packet_type == AdvertisingPacketType.scan_response:
                self.assertEqual(self.default_scan_data_bytes,
                                 packet.raw_bytes)
            else:
                self.assertEqual(self.default_adv_data_bytes, packet.raw_bytes)

        # Check the combined report that all the fields are included
        combined_report = [
            p for p in results.advertising_peers_found
            if p.peer_address == self.adv_mac_addr
        ]
        self.assertEqual(1, len(combined_report))
        adv_data = combined_report[0].advertise_data
        self.assertEqual(self.default_adv_data.flags, adv_data.flags)
        self.assertEqual(self.default_adv_data.local_name, adv_data.local_name)
        self.assertEqual(self.default_scan_data.service_uuid16s,
                         adv_data.service_uuid16s)

    def test_non_connectable_undirected_no_scan_response_packets_received(
            self):
        self._configure_adv(
            adv_mode=AdvertisingMode.non_connectable_undirected)
        self.dev1.advertiser.set_advertise_data(self.default_adv_data,
                                                self.default_scan_data)
        self.dev1.advertiser.start()
        self.dev2.scanner.set_default_scan_params(100,
                                                  100,
                                                  5,
                                                  active_scanning=True)
        results = self.dev2.scanner.start_scan().wait(10)

        # Get the list of all advertising packets from the advertiser
        all_packets, adv_packets, scan_response_packets = self._get_packets_for_adv(
            results)
        self.assertGreater(len(all_packets), 0)
        self.assertEqual(len(all_packets), len(adv_packets))
        self.assertEqual(0, len(scan_response_packets))

        for p in adv_packets:
            self.assertEqual(self.default_adv_data_bytes, p.raw_bytes)

    def test_dynamic_adv_data_update(self):
        self._configure_adv(
            duration=20, adv_mode=AdvertisingMode.non_connectable_undirected)
        self._configure_scan(10)
        service_data = [0xAB, 0xCD, 0x00]
        service_data_preamble = service_data[:-1]
        iterations = 10
        adv_data = AdvertisingData(service_data=service_data)
        self.dev1.advertiser.set_advertise_data(adv_data)
        self.dev1.advertiser.start()
        self.dev2.scanner.start_scan()

        for i in range(iterations):
            time.sleep(0.5)
            adv_data.service_data[-1] += 1
            self.dev1.advertiser.set_advertise_data(adv_data)

        time.sleep(0.5)
        self.dev2.scanner.stop()
        self.dev1.advertiser.stop()

        results = self.dev2.scanner.scan_report

        all_packets, adv_packets, scan_response_packets = self._get_packets_for_adv(
            results)
        self.assertGreater(len(all_packets), 0)
        self.assertEqual(len(all_packets), len(adv_packets))
        self.assertEqual(0, len(scan_response_packets))
        non_dupes = [p for p in adv_packets if not p.duplicate]

        self.assertEqual(iterations + 1, len(non_dupes))
        for i, packet in enumerate(non_dupes):
            expected_service_data = bytes(service_data_preamble + [i])
            self.assertEqual(expected_service_data,
                             packet.advertise_data.service_data)