[srp-server] add support for service subtypes (#6760)

This commit adds support for service subtypes in SRP server. It
updates the internal data model to store services in `Srp::Server`.
Every `Host` now has a list of `Service` entries along with a list of
`Service::Description` entries. These types mirror the SRP update
message format and the set of instructions that form the SRP message.
The `Service` entries represent the "Service Discovery Instructions",
i.e., the PTR records mapping a service name or a subtype name to a
service instance. A `Service::Description` entry represents the the
SRV and TXT records. A `Service` entry is always associated with a
`Service::Description` and the subtypes of the same service instance
all share the same `Service::Description` entry.

This commit also adds a new method `Host::FindNextService()` and the
public API `otSrpServerHostFindNextService()` which is very flexible
and can be used in different ways. It can be used to iterate over the
full list of services, or over a specific subset of services matching
certain conditions, e.g., iterate over all base services excluding
subtypes, or over all subtypes of an instance, or over all deleted
services, etc. It can also be used to find a specific service with a
given instance and service names.

This commit also simplifies and enhances the logging in `Srp::Server`.
In particular, when a new host is added, we now also log the list of
services being added along with it. Also a change to a `Service` is
only logged if the `Service` is marked as committed. This ensures
that temporary `Service` entries associated with a newly received SRP
update message are not logged (e.g., when an associated temporary
`Host` object is being freed after its content is merged with an
existing `Host` entry).

Finally, this commit adds a test `test_srp_sub_type.py` to cover the
subtype service registration on SRP client and server.
This commit is contained in:
Abtin Keshavarzian
2021-07-02 09:35:03 -07:00
committed by GitHub
parent fbe3ffce87
commit 0faa3fd4fe
14 changed files with 1065 additions and 381 deletions
+2
View File
@@ -180,6 +180,7 @@ EXTRA_DIST = \
test_srp_lease.py \
test_srp_name_conflicts.py \
test_srp_register_single_service.py \
test_srp_sub_type.py \
thread_cert.py \
tlvs_parsing.py \
thread_cert.py \
@@ -238,6 +239,7 @@ check_SCRIPTS = \
test_srp_lease.py \
test_srp_name_conflicts.py \
test_srp_register_single_service.py \
test_srp_sub_type.py \
Cert_5_1_01_RouterAttach.py \
Cert_5_1_02_ChildAddressTimeout.py \
Cert_5_1_03_RouterAddressReallocation.py \
+3 -3
View File
@@ -890,8 +890,8 @@ class NodeImpl:
service_list.append(service)
continue
# 'port', 'priority', 'weight'
for i in range(0, 3):
# 'subtypes', port', 'priority', 'weight'
for i in range(0, 4):
key_value = lines.pop(0).strip().split(':')
service[key_value[0].strip()] = key_value[1].strip()
@@ -1044,7 +1044,7 @@ class NodeImpl:
Note that value of 'port', 'priority' and 'weight' are represented
as strings but not integers.
"""
key_values = [word.strip().split(':') for word in line.split(',')]
key_values = [word.strip().split(':') for word in line.split(', ')]
keys = [key_value[0] for key_value in key_values]
values = [key_value[1].strip('"') for key_value in key_values]
return dict(zip(keys, values))
@@ -195,6 +195,7 @@ class SrpRegisterSingleService(thread_cert.TestCase):
self.assertEqual(server_service['deleted'], 'false')
self.assertEqual(server_service['instance'], client_service['instance'])
self.assertEqual(server_service['name'], client_service['name'])
self.assertEqual(server_service['subtypes'], '(null)')
self.assertEqual(int(server_service['port']), int(client_service['port']))
self.assertEqual(int(server_service['priority']), int(client_service['priority']))
self.assertEqual(int(server_service['weight']), int(client_service['weight']))
@@ -166,6 +166,7 @@ class SrpServerRebootPort(thread_cert.TestCase):
self.assertEqual(server_service['deleted'], 'false')
self.assertEqual(server_service['instance'], client_service['instance'])
self.assertEqual(server_service['name'], client_service['name'])
self.assertEqual(server_service['subtypes'], '(null)')
self.assertEqual(int(server_service['port']), int(client_service['port']))
self.assertEqual(int(server_service['priority']), int(client_service['priority']))
self.assertEqual(int(server_service['weight']), int(client_service['weight']))
+140
View File
@@ -0,0 +1,140 @@
#!/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 ipaddress
import unittest
import command
import thread_cert
# Test description:
# This test verifies SRP client and server support for registering sub-type services.
#
# Topology:
#
# LEADER (SRP server)
# |
# |
# ROUTER (SRP client)
#
SERVER = 1
CLIENT = 2
class SrpSubType(thread_cert.TestCase):
USE_MESSAGE_FACTORY = False
SUPPORT_NCP = False
TOPOLOGY = {
SERVER: {
'name': 'SRP_SERVER',
'mode': 'rdn',
},
CLIENT: {
'name': 'SRP_CLIENT',
'mode': 'rdn',
},
}
def test(self):
server = self.nodes[SERVER]
client = self.nodes[CLIENT]
# Start the server & client devices.
server.start()
self.simulator.go(5)
self.assertEqual(server.get_state(), 'leader')
client.start()
self.simulator.go(5)
self.assertEqual(client.get_state(), 'router')
server.srp_server_set_enabled(True)
client.srp_client_enable_auto_start_mode()
# Register a single service with 3 subtypes and verify that it worked.
client.srp_client_set_host_name('host1')
client.srp_client_set_host_address('2001::1')
client.srp_client_add_service('ins1', '_srv._udp,_s1,_s2,_s3', 1977)
self.simulator.go(2)
self.check_service_on_client_and_server(server, client)
# Remove the service on client
client.srp_client_remove_service('ins1', '_srv._udp')
self.simulator.go(2)
client_services = client.srp_client_get_services()
self.assertEqual(len(client_services), 0)
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 1)
server_service = server_services[0]
self.assertEqual(server_service['fullname'], 'ins1._srv._udp.default.service.arpa.')
self.assertEqual(server_service['instance'], 'ins1')
self.assertEqual(server_service['name'], '_srv._udp')
self.assertEqual(server_service['deleted'], 'true')
# Register the same service again.
client.srp_client_add_service('ins1', '_srv._udp,_s1,_s2,_s3', 1977)
self.simulator.go(2)
self.check_service_on_client_and_server(server, client)
def check_service_on_client_and_server(self, server, client):
# Check the service on client
client_services = client.srp_client_get_services()
self.assertEqual(len(client_services), 1)
client_service = client_services[0]
self.assertEqual(client_service['instance'], 'ins1')
self.assertEqual(client_service['name'], '_srv._udp,_s1,_s2,_s3')
self.assertEqual(int(client_service['port']), 1977)
self.assertEqual(int(client_service['priority']), 0)
self.assertEqual(int(client_service['weight']), 0)
self.assertEqual(client_service['state'], 'Registered')
# Check the service on server
server_services = server.srp_server_get_services()
self.assertEqual(len(server_services), 1)
server_service = server_services[0]
self.assertEqual(server_service['fullname'], 'ins1._srv._udp.default.service.arpa.')
self.assertEqual(server_service['instance'], 'ins1')
self.assertEqual(server_service['name'], '_srv._udp')
self.assertEqual(server_service['deleted'], 'false')
self.assertEqual(set(server_service['subtypes'].split(',')), {'_s1', '_s2', '_s3'})
self.assertEqual(int(server_service['port']), 1977)
self.assertEqual(int(server_service['priority']), 0)
self.assertEqual(int(server_service['weight']), 0)
self.assertEqual(server_service['host'], 'host1')
if __name__ == '__main__':
unittest.main()