1#!/usr/bin/env python3
2#
3#  Copyright (c) 2022, The OpenThread Authors.
4#  All rights reserved.
5#
6#  Redistribution and use in source and binary forms, with or without
7#  modification, are permitted provided that the following conditions are met:
8#  1. Redistributions of source code must retain the above copyright
9#     notice, this list of conditions and the following disclaimer.
10#  2. Redistributions in binary form must reproduce the above copyright
11#     notice, this list of conditions and the following disclaimer in the
12#     documentation and/or other materials provided with the distribution.
13#  3. Neither the name of the copyright holder nor the
14#     names of its contributors may be used to endorse or promote products
15#     derived from this software without specific prior written permission.
16#
17#  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18#  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19#  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20#  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
21#  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
22#  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
23#  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24#  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
25#  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
26#  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27#  POSSIBILITY OF SUCH DAMAGE.
28#
29
30import ipaddress
31import unittest
32
33import command
34import config
35import thread_cert
36
37# Test description:
38#
39#   This test verifies the SRP client and server behavior when services
40#   with different lease (and/or key lease) intervals are registered.
41#
42# Topology:
43#
44#     LEADER (SRP server)
45#       |
46#       |
47#     ROUTER (SRP client)
48#
49
50SERVER = 1
51CLIENT = 2
52
53
54class SrpRegisterServicesDiffLease(thread_cert.TestCase):
55    USE_MESSAGE_FACTORY = False
56    SUPPORT_NCP = False
57
58    TOPOLOGY = {
59        SERVER: {
60            'name': 'SRP_SERVER',
61            'mode': 'rdn',
62        },
63        CLIENT: {
64            'name': 'SRP_CLIENT',
65            'mode': 'rdn',
66        },
67    }
68
69    def test(self):
70        server = self.nodes[SERVER]
71        client = self.nodes[CLIENT]
72
73        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
74        # Start the server and client.
75
76        server.start()
77        self.simulator.go(config.LEADER_STARTUP_DELAY)
78        self.assertEqual(server.get_state(), 'leader')
79
80        client.start()
81        self.simulator.go(config.ROUTER_STARTUP_DELAY)
82        self.assertEqual(client.get_state(), 'router')
83
84        server.srp_server_set_enabled(True)
85        client.srp_client_enable_auto_start_mode()
86
87        self.simulator.go(5)
88
89        client.srp_client_set_host_name('host')
90        client.srp_client_enable_auto_host_address()
91
92        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
93        # Add a service with specific lease and key lease and verify that
94        # it is successfully registered and seen with same lease/key-lease
95        # on server.
96
97        client.srp_client_add_service('ins1', '_test._udp', 1111, lease=60, key_lease=800)
98
99        self.simulator.go(5)
100
101        self.check_services_on_client(client, 1)
102        services = server.srp_server_get_services()
103        self.assertEqual(len(services), 1)
104        service = services[0]
105        self.assertEqual(service['fullname'], 'ins1._test._udp.default.service.arpa.')
106        self.assertEqual(service['deleted'], 'false')
107        self.assertEqual(int(service['ttl']), 60)
108        self.assertEqual(int(service['lease']), 60)
109        self.assertEqual(int(service['key-lease']), 800)
110
111        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
112        # Register two more services with different lease intervals.
113
114        client.srp_client_add_service('ins2', '_test._udp', 2222, lease=30, key_lease=200)
115        client.srp_client_add_service('ins3', '_test._udp', 3333, lease=100, key_lease=1000)
116
117        self.simulator.go(10)
118
119        self.check_services_on_client(client, 3)
120        server_services = server.srp_server_get_services()
121        self.assertEqual(len(server_services), 3)
122        for service in server_services:
123            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
124                self.assertEqual(service['deleted'], 'false')
125                self.assertEqual(int(service['ttl']), 60)
126                self.assertEqual(int(service['lease']), 60)
127                self.assertEqual(int(service['key-lease']), 800)
128            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
129                self.assertEqual(service['deleted'], 'false')
130                self.assertEqual(int(service['ttl']), 30)
131                self.assertEqual(int(service['lease']), 30)
132                self.assertEqual(int(service['key-lease']), 200)
133            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
134                self.assertEqual(service['deleted'], 'false')
135                self.assertEqual(int(service['ttl']), 100)
136                self.assertEqual(int(service['lease']), 100)
137                self.assertEqual(int(service['key-lease']), 1000)
138            else:
139                self.assertTrue(False)
140
141        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
142        # Wait for longest lease time to validate that all services renew their
143        # lease successfully.
144
145        self.simulator.go(105)
146
147        self.check_services_on_client(client, 3)
148        server_services = server.srp_server_get_services()
149        self.assertEqual(len(server_services), 3)
150        for service in server_services:
151            self.assertEqual(service['deleted'], 'false')
152
153        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
154        # Remove two services.
155
156        client.srp_client_remove_service('ins2', '_test._udp')
157        client.srp_client_remove_service('ins3', '_test._udp')
158
159        self.simulator.go(10)
160
161        self.check_services_on_client(client, 1)
162        server_services = server.srp_server_get_services()
163        self.assertEqual(len(server_services), 3)
164        for service in server_services:
165            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
166                self.assertEqual(service['deleted'], 'false')
167                self.assertEqual(int(service['ttl']), 60)
168                self.assertEqual(int(service['lease']), 60)
169                self.assertEqual(int(service['key-lease']), 800)
170            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
171                self.assertEqual(service['deleted'], 'true')
172            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
173                self.assertEqual(service['deleted'], 'true')
174            else:
175                self.assertTrue(False)
176
177        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
178        # Wait for longer than key-lease of `ins2` service and check that it is
179        # removed on server.
180
181        self.simulator.go(201)
182
183        self.check_services_on_client(client, 1)
184        server_services = server.srp_server_get_services()
185        self.assertEqual(len(server_services), 2)
186        for service in server_services:
187            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
188                self.assertEqual(service['deleted'], 'false')
189                self.assertEqual(int(service['ttl']), 60)
190                self.assertEqual(int(service['lease']), 60)
191                self.assertEqual(int(service['key-lease']), 800)
192            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
193                self.assertEqual(service['deleted'], 'true')
194            else:
195                self.assertTrue(False)
196
197        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
198        # Add both services again now with same lease intervals.
199
200        client.srp_client_add_service('ins2', '_test._udp', 2222, lease=30, key_lease=100)
201        client.srp_client_add_service('ins3', '_test._udp', 3333, lease=30, key_lease=100)
202
203        self.simulator.go(10)
204
205        self.check_services_on_client(client, 3)
206        server_services = server.srp_server_get_services()
207        self.assertEqual(len(server_services), 3)
208        for service in server_services:
209            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
210                self.assertEqual(service['deleted'], 'false')
211                self.assertEqual(int(service['ttl']), 60)
212                self.assertEqual(int(service['lease']), 60)
213                self.assertEqual(int(service['key-lease']), 800)
214            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
215                self.assertEqual(service['deleted'], 'false')
216                self.assertEqual(int(service['ttl']), 30)
217                self.assertEqual(int(service['lease']), 30)
218                self.assertEqual(int(service['key-lease']), 100)
219            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
220                self.assertEqual(service['deleted'], 'false')
221                self.assertEqual(int(service['ttl']), 30)
222                self.assertEqual(int(service['lease']), 30)
223                self.assertEqual(int(service['key-lease']), 100)
224            else:
225                self.assertTrue(False)
226
227        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
228        # Remove `ins1` while adding a new service with same key-lease as
229        # `ins1` but different lease interval.
230
231        client.srp_client_remove_service('ins1', '_test._udp')
232        client.srp_client_add_service('ins4', '_test._udp', 4444, lease=90, key_lease=800)
233
234        self.simulator.go(5)
235
236        self.check_services_on_client(client, 3)
237        server_services = server.srp_server_get_services()
238        self.assertEqual(len(server_services), 4)
239        for service in server_services:
240            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
241                self.assertEqual(service['deleted'], 'true')
242            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
243                self.assertEqual(service['deleted'], 'false')
244                self.assertEqual(int(service['ttl']), 30)
245                self.assertEqual(int(service['lease']), 30)
246                self.assertEqual(int(service['key-lease']), 100)
247            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
248                self.assertEqual(service['deleted'], 'false')
249                self.assertEqual(int(service['ttl']), 30)
250                self.assertEqual(int(service['lease']), 30)
251                self.assertEqual(int(service['key-lease']), 100)
252            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
253                self.assertEqual(service['deleted'], 'false')
254                self.assertEqual(int(service['ttl']), 90)
255                self.assertEqual(int(service['lease']), 90)
256                self.assertEqual(int(service['key-lease']), 800)
257            else:
258                self.assertTrue(False)
259
260        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
261        # Remove two services `ins2` and `ins3` (they now have same key lease).
262
263        client.srp_client_remove_service('ins2', '_test._udp')
264        client.srp_client_remove_service('ins3', '_test._udp')
265
266        self.simulator.go(10)
267
268        self.check_services_on_client(client, 1)
269        server_services = server.srp_server_get_services()
270        self.assertEqual(len(server_services), 4)
271        for service in server_services:
272            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
273                self.assertEqual(service['deleted'], 'true')
274            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
275                self.assertEqual(service['deleted'], 'true')
276            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
277                self.assertEqual(service['deleted'], 'true')
278            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
279                self.assertEqual(service['deleted'], 'false')
280                self.assertEqual(int(service['ttl']), 90)
281                self.assertEqual(int(service['lease']), 90)
282                self.assertEqual(int(service['key-lease']), 800)
283            else:
284                self.assertTrue(False)
285
286        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
287        # Add `ins1` with key-lease smaller than lease and check that
288        # client handles this properly (uses the lease value for
289        # key-lease).
290
291        client.srp_client_add_service('ins1', '_test._udp', 1111, lease=100, key_lease=90)
292
293        self.simulator.go(10)
294
295        self.check_services_on_client(client, 2)
296        server_services = server.srp_server_get_services()
297        self.assertEqual(len(server_services), 4)
298        for service in server_services:
299            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
300                self.assertEqual(service['deleted'], 'false')
301                self.assertEqual(int(service['ttl']), 100)
302                self.assertEqual(int(service['lease']), 100)
303                self.assertEqual(int(service['key-lease']), 100)
304            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
305                self.assertEqual(service['deleted'], 'true')
306            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
307                self.assertEqual(service['deleted'], 'true')
308            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
309                self.assertEqual(service['deleted'], 'false')
310                self.assertEqual(int(service['ttl']), 90)
311                self.assertEqual(int(service['lease']), 90)
312                self.assertEqual(int(service['key-lease']), 800)
313            else:
314                self.assertTrue(False)
315
316        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
317        # Change default lease and key-lease intervals on client.
318
319        client.srp_client_set_lease_interval(40)
320        self.assertEqual(client.srp_client_get_lease_interval(), 40)
321
322        client.srp_client_set_key_lease_interval(330)
323        self.assertEqual(client.srp_client_get_key_lease_interval(), 330)
324
325        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
326        # Add `ins2` and `ins3`. `ins2` specifies the key-lease explicitly but
327        # leaves lease as default. `ins3` does the opposite.
328
329        client.srp_client_add_service('ins2', '_test._udp', 2222, key_lease=330)
330        client.srp_client_add_service('ins3', '_test._udp', 3333, lease=40)
331
332        self.simulator.go(10)
333
334        self.check_services_on_client(client, 4)
335        server_services = server.srp_server_get_services()
336        self.assertEqual(len(server_services), 4)
337        for service in server_services:
338            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
339                self.assertEqual(service['deleted'], 'false')
340                self.assertEqual(int(service['ttl']), 100)
341                self.assertEqual(int(service['lease']), 100)
342                self.assertEqual(int(service['key-lease']), 100)
343            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
344                self.assertEqual(service['deleted'], 'false')
345                self.assertEqual(int(service['ttl']), 40)
346                self.assertEqual(int(service['lease']), 40)
347                self.assertEqual(int(service['key-lease']), 330)
348            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
349                self.assertEqual(service['deleted'], 'false')
350                self.assertEqual(int(service['ttl']), 40)
351                self.assertEqual(int(service['lease']), 40)
352                self.assertEqual(int(service['key-lease']), 330)
353            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
354                self.assertEqual(service['deleted'], 'false')
355                self.assertEqual(int(service['ttl']), 90)
356                self.assertEqual(int(service['lease']), 90)
357                self.assertEqual(int(service['key-lease']), 800)
358            else:
359                self.assertTrue(False)
360
361        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
362        # Change the default lease to 50 and wait for long enough for `ins2`
363        # and `ins3` to do lease refresh. Validate that `ins2` now requests
364        # new default lease of 50 while `ins3` should stay as before.
365
366        client.srp_client_set_lease_interval(50)
367        self.assertEqual(client.srp_client_get_lease_interval(), 50)
368
369        self.simulator.go(45)
370
371        self.check_services_on_client(client, 4)
372        server_services = server.srp_server_get_services()
373        self.assertEqual(len(server_services), 4)
374        for service in server_services:
375            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
376                self.assertEqual(service['deleted'], 'false')
377                self.assertEqual(int(service['ttl']), 100)
378                self.assertEqual(int(service['lease']), 100)
379                self.assertEqual(int(service['key-lease']), 100)
380            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
381                self.assertEqual(service['deleted'], 'false')
382                self.assertEqual(int(service['ttl']), 50)
383                self.assertEqual(int(service['lease']), 50)
384                self.assertEqual(int(service['key-lease']), 330)
385            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
386                self.assertEqual(service['deleted'], 'false')
387                self.assertEqual(int(service['ttl']), 40)
388                self.assertEqual(int(service['lease']), 40)
389                self.assertEqual(int(service['key-lease']), 330)
390            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
391                self.assertEqual(service['deleted'], 'false')
392                self.assertEqual(int(service['ttl']), 90)
393                self.assertEqual(int(service['lease']), 90)
394                self.assertEqual(int(service['key-lease']), 800)
395            else:
396                self.assertTrue(False)
397
398        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
399        # Change the default key lease to 30. `ins3` should adopt this but
400        # since it is shorter than its explicitly specified lease the
401        # client should use same value for both lease and key-lease.
402
403        client.srp_client_set_key_lease_interval(35)
404        self.assertEqual(client.srp_client_get_key_lease_interval(), 35)
405
406        self.simulator.go(45)
407
408        self.check_services_on_client(client, 4)
409        server_services = server.srp_server_get_services()
410        self.assertEqual(len(server_services), 4)
411        for service in server_services:
412            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
413                self.assertEqual(service['deleted'], 'false')
414                self.assertEqual(int(service['ttl']), 100)
415                self.assertEqual(int(service['lease']), 100)
416                self.assertEqual(int(service['key-lease']), 100)
417            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
418                self.assertEqual(service['deleted'], 'false')
419                self.assertEqual(int(service['ttl']), 50)
420                self.assertEqual(int(service['lease']), 50)
421                self.assertEqual(int(service['key-lease']), 330)
422            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
423                self.assertEqual(service['deleted'], 'false')
424                self.assertEqual(int(service['ttl']), 40)
425                self.assertEqual(int(service['lease']), 40)
426                self.assertEqual(int(service['key-lease']), 40)
427            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
428                self.assertEqual(service['deleted'], 'false')
429                self.assertEqual(int(service['ttl']), 90)
430                self.assertEqual(int(service['lease']), 90)
431                self.assertEqual(int(service['key-lease']), 800)
432            else:
433                self.assertTrue(False)
434
435        #- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
436        # Change the requested TTL. Wait for long enough for all
437        # services to refresh and check that the new TTL is correctly
438        # requested by the client (when it is not larger than
439        # service lease).
440
441        client.srp_client_set_ttl(65)
442        self.assertEqual(client.srp_client_get_ttl(), 65)
443
444        self.simulator.go(110)
445
446        self.check_services_on_client(client, 4)
447        server_services = server.srp_server_get_services()
448        self.assertEqual(len(server_services), 4)
449        for service in server_services:
450            if service['fullname'] == 'ins1._test._udp.default.service.arpa.':
451                self.assertEqual(service['deleted'], 'false')
452                self.assertEqual(int(service['ttl']), 65)
453                self.assertEqual(int(service['lease']), 100)
454                self.assertEqual(int(service['key-lease']), 100)
455            elif service['fullname'] == 'ins2._test._udp.default.service.arpa.':
456                self.assertEqual(service['deleted'], 'false')
457                self.assertEqual(int(service['ttl']), 50)
458                self.assertEqual(int(service['lease']), 50)
459                self.assertEqual(int(service['key-lease']), 330)
460            elif service['fullname'] == 'ins3._test._udp.default.service.arpa.':
461                self.assertEqual(service['deleted'], 'false')
462                self.assertEqual(int(service['ttl']), 40)
463                self.assertEqual(int(service['lease']), 40)
464                self.assertEqual(int(service['key-lease']), 40)
465            elif service['fullname'] == 'ins4._test._udp.default.service.arpa.':
466                self.assertEqual(service['deleted'], 'false')
467                self.assertEqual(int(service['ttl']), 65)
468                self.assertEqual(int(service['lease']), 90)
469                self.assertEqual(int(service['key-lease']), 800)
470            else:
471                self.assertTrue(False)
472
473    def check_services_on_client(self, client, expected_num_services):
474        services = client.srp_client_get_services()
475        self.assertEqual(len(services), expected_num_services)
476        for service in client.srp_client_get_services():
477            self.assertIn(service['state'], ['Registered', 'ToRefresh', 'Refreshing'])
478
479
480if __name__ == '__main__':
481    unittest.main()
482