diff --git a/examples/platforms/simulation/openthread-core-simulation-config.h b/examples/platforms/simulation/openthread-core-simulation-config.h index f6f953b1e..665713101 100644 --- a/examples/platforms/simulation/openthread-core-simulation-config.h +++ b/examples/platforms/simulation/openthread-core-simulation-config.h @@ -232,4 +232,16 @@ #define OPENTHREAD_CONFIG_LOG_PREPEND_UPTIME 1 #endif +/** + * @def OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_MAX_SERVICES + * + * Specifies number of service entries in the SRP client service pool. + * + * This config is applicable only when `OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_ENABLE` is enabled. + * + */ +#ifndef OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_MAX_SERVICES +#define OPENTHREAD_CONFIG_SRP_CLIENT_BUFFERS_MAX_SERVICES 20 +#endif + #endif // OPENTHREAD_CORE_SIMULATION_CONFIG_H_ diff --git a/script/test b/script/test index 69a657201..d92edc39e 100755 --- a/script/test +++ b/script/test @@ -62,8 +62,9 @@ build_simulation() "-DOT_DNS_CLIENT=ON" "-DOT_DNSSD_SERVER=ON" "-DOT_ECDSA=ON" + "-DOT_EXTERNAL_HEAP=ON" "-DOT_HISTORY_TRACKER=ON" - "-DOT_MESSAGE_USE_HEAP=ON" + "-DOT_MESSAGE_USE_HEAP=OFF" "-DOT_NETDATA_PUBLISHER=ON" "-DOT_PING_SENDER=ON" "-DOT_REFERENCE_DEVICE=ON" diff --git a/tests/scripts/thread-cert/border_router/test_srp_register_500_services_br.py b/tests/scripts/thread-cert/border_router/test_srp_register_500_services_br.py new file mode 100644 index 000000000..5576cc876 --- /dev/null +++ b/tests/scripts/thread-cert/border_router/test_srp_register_500_services_br.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import unittest + +from test_srp_register_500_services import CLIENT_IDS, INSTANCE_IDS, SrpRegister500Services, SERVER, SERVICE_NAME + +# Test description: +# This test verifies SRP function that SRP clients can register up to 500 services to one SRP server. +# +# This test is same as test_srp_register_500_services.py, with the only difference that the SRP server +# is running on an OTBR (Docker). +# + +HOST = SERVER + 1 + + +class SrpRegister500ServicesBR(SrpRegister500Services): + TOPOLOGY = SrpRegister500Services.TOPOLOGY + TOPOLOGY[SERVER]['is_otbr'] = True + TOPOLOGY[HOST] = { + 'name': 'Host', + 'is_host': True, + } + + def test(self): + self.nodes[HOST].start(start_radvd=False) + + self._test_impl() + + # Verify all SRP services are resolvable via mDNS + host = self.nodes[HOST] + + browse_retry = 3 + for i in range(browse_retry): + try: + service_instances = set(host.browse_mdns_services(SERVICE_NAME, timeout=10)) + print(service_instances) + + for clientid in CLIENT_IDS: + for instanceid in INSTANCE_IDS: + self.assertIn(f'client{clientid}_{instanceid}', service_instances) + + break + + except Exception: + if i < browse_retry - 1: + self.simulator.go(3) + continue + + +del SrpRegister500Services + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/test_srp_register_500_services.py b/tests/scripts/thread-cert/test_srp_register_500_services.py new file mode 100644 index 000000000..8e41196d4 --- /dev/null +++ b/tests/scripts/thread-cert/test_srp_register_500_services.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2021, The OpenThread Authors. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# 1. Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# 2. Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# 3. Neither the name of the copyright holder nor the +# names of its contributors may be used to endorse or promote products +# derived from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# + +import unittest + +import thread_cert + +# Test description: +# This test verifies SRP function that SRP clients can register up to 500 services to one SRP server. +# +# Topology: +# LEADER (SRP server) +# | +# | +# ROUTER1 ... ROUTER12 ROUTER13 (SRP clients) +# | | +# FED14 ... FED25 (SRP clients) +# + +NUM_SRP_CLIENTS = 25 +INSTANCE_NUM_PER_CLIENT = 20 +NUM_ROUTERS = (NUM_SRP_CLIENTS // 2) + (NUM_SRP_CLIENTS % 2) +NUM_FEDS = NUM_SRP_CLIENTS - NUM_ROUTERS +assert (NUM_FEDS <= NUM_ROUTERS) + +SERVER = NUM_SRP_CLIENTS + 1 +CLIENT1 = 1 + +ROUTER_IDS = tuple(range(1, NUM_ROUTERS + 1)) +FED_IDS = tuple(range(NUM_ROUTERS + 1, NUM_SRP_CLIENTS + 1)) +assert len(FED_IDS) == NUM_FEDS +CLIENT_IDS = tuple(range(1, NUM_SRP_CLIENTS + 1)) +INSTANCE_IDS = tuple(range(1, INSTANCE_NUM_PER_CLIENT + 1)) +SERVICE_NAME = '_srpclient._tcp' +SERVICE_PORT = 12345 + + +class SrpRegister500Services(thread_cert.TestCase): + USE_MESSAGE_FACTORY = False + SUPPORT_NCP = False + + TOPOLOGY = { + SERVER: { + 'name': 'SRP_SERVER', + 'mode': 'rdn', + 'allowlist': [], + }, + } + + for nodeid in ROUTER_IDS: + TOPOLOGY[nodeid] = { + 'name': f'SRP_CLIENT_{nodeid}', + 'mode': 'rdn', + 'allowlist': [SERVER], + } + TOPOLOGY[SERVER]['allowlist'].append(nodeid) + + for nodeid in FED_IDS: + parentid = nodeid - NUM_ROUTERS + TOPOLOGY[nodeid] = { + 'name': f'SRP_CLIENT_{nodeid}', + 'mode': 'rdn', + 'router_eligible': False, + 'allowlist': [parentid], + } + TOPOLOGY[parentid]['allowlist'].append(nodeid) + + def test(self): + self._test_impl() + + def _test_impl(self): + server = self.nodes[SERVER] + + def routers(): + for nodeid in ROUTER_IDS: + yield self.nodes[nodeid] + + def feds(): + for nodeid in FED_IDS: + yield self.nodes[nodeid] + + def clients(): + for nodeid in CLIENT_IDS: + yield self.nodes[nodeid] + + # Start the server & clients. + server.srp_server_set_enabled(True) + server.start() + self.simulator.go(5) + self.assertEqual(server.get_state(), 'leader') + + for router in routers(): + router.srp_server_set_enabled(False) + router.start() + self.simulator.go(3) + + for router in routers(): + self.assertEqual(router.get_state(), 'router') + + for fed in feds(): + fed.srp_server_set_enabled(False) + fed.start() + self.simulator.go(3) + + for fed in feds(): + self.assertEqual(fed.get_state(), 'child') + + server_addr = server.get_rloc() + + for clientid in CLIENT_IDS: + client = self.nodes[clientid] + client.srp_client_set_host_name(f'client{clientid}') + client.srp_client_set_host_address(f'2001::{clientid}') + client.srp_client_start(server_addr, client.get_srp_server_port()) + + for instanceid in INSTANCE_IDS: + client.srp_client_add_service(f'client{clientid}_{instanceid}', SERVICE_NAME, SERVICE_PORT) + + if instanceid % 10 == 0 or instanceid == INSTANCE_IDS[-1]: + self._wait_client_all_services_registered(client) + + self._check_client_host_and_services(client, clientid) + + # Make sure all service are registered successfully + self._check_server_hosts_and_services(server) + + def _wait_client_all_services_registered(self, client, timeout=60): + all_registered = False + while timeout > 0: + self.simulator.go(3) + timeout -= 3 + + client_services = client.srp_client_get_services() + all_registered = all(service['state'] == 'Registered' for service in client_services) + if all_registered: + break + + self.assertTrue(all_registered, "Some services are not registered successfully") + + def _check_client_host_and_services(self, client, clientid): + client_services = client.srp_client_get_services() + self.assertEqual(len(client_services), INSTANCE_NUM_PER_CLIENT) + service_instance_names = {f'client{clientid}_{instanceid}' for instanceid in INSTANCE_IDS} + for service in client_services: + self.assertEqual(service['name'], SERVICE_NAME) + self.assertEqual(service['state'], 'Registered') + self.assertEqual(int(service['port']), SERVICE_PORT) + self.assertIn(service['instance'], service_instance_names) + service_instance_names.discard(service['instance']) + + def _check_server_hosts_and_services(self, server): + server_services = server.srp_server_get_services() + self.assertEqual(len(server_services), NUM_SRP_CLIENTS * INSTANCE_NUM_PER_CLIENT) + + service_instance_names = { + f'client{clientid}_{instanceid}' for clientid in CLIENT_IDS for instanceid in INSTANCE_IDS + } + + for service in server_services: + self.assertEqual(service['name'], SERVICE_NAME) + self.assertIn(service['instance'], service_instance_names) + service_instance_names.discard(service['instance']) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/scripts/thread-cert/thread_cert.py b/tests/scripts/thread-cert/thread_cert.py index d8f76a5e4..0695e5f7a 100755 --- a/tests/scripts/thread-cert/thread_cert.py +++ b/tests/scripts/thread-cert/thread_cert.py @@ -269,13 +269,14 @@ class TestCase(NcpSupportMixin, unittest.TestCase): self.simulator.stop() - if self._has_backbone_traffic(): - self._remove_backbone_network() - pcap_filename = self._merge_thread_backbone_pcaps() - else: - pcap_filename = self._get_thread_pcap_filename() - if self._do_packet_verification: + + if self._has_backbone_traffic(): + self._remove_backbone_network() + pcap_filename = self._merge_thread_backbone_pcaps() + else: + pcap_filename = self._get_thread_pcap_filename() + self._test_info['pcap'] = pcap_filename test_info_path = self._output_test_info()