1# Test cases for Wi-Fi Aware unsynchronized service discovery (NAN USD)
2# Copyright (c) 2024, Qualcomm Innovation Center, Inc.
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import time
8
9import logging
10logger = logging.getLogger()
11
12import hostapd
13from utils import *
14
15def check_nan_usd_capab(dev):
16    capa = dev.request("GET_CAPABILITY nan")
17    if "USD" not in capa:
18        raise HwsimSkip("NAN USD not supported")
19
20def test_nan_usd_publish_invalid_param(dev):
21    """NAN USD Publish with invalid parameters"""
22    check_nan_usd_capab(dev[0])
23
24    # Both solicited and unsolicited disabled is invalid
25    cmd = "NAN_PUBLISH service_name=_test solicited=0 unsolicited=0"
26    id0 = dev[0].request(cmd)
27    if "FAIL" not in id0:
28        raise Exception("NAN_PUBLISH accepts both solicited=0 and unsolicited=0")
29
30def test_nan_usd_publish(dev, apdev):
31    """NAN USD Publish"""
32    check_nan_usd_capab(dev[0])
33    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677"
34    id0 = dev[0].request(cmd)
35    if "FAIL" in id0:
36        raise Exception("NAN_PUBLISH failed")
37
38    cmd = "NAN_UPDATE_PUBLISH publish_id=" + id0 + " ssi=1122334455"
39    if "FAIL" in dev[0].request(cmd):
40        raise Exception("NAN_UPDATE_PUBLISH failed")
41
42    cmd = "NAN_CANCEL_PUBLISH publish_id=" + id0
43    if "FAIL" in dev[0].request(cmd):
44        raise Exception("NAN_CANCEL_PUBLISH failed")
45
46    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=1)
47    if ev is None:
48        raise Exception("PublishTerminated event not seen")
49    if "publish_id=" + id0 not in ev:
50        raise Exception("Unexpected publish_id: " + ev)
51    if "reason=user-request" not in ev:
52        raise Exception("Unexpected reason: " + ev)
53
54    cmd = "NAN_PUBLISH service_name=_test"
55    count = 0
56    for i in range(256):
57        if "FAIL" in dev[0].request(cmd):
58            break
59        count += 1
60    logger.info("Maximum services: %d" % count)
61    for i in range(count):
62        cmd = "NAN_CANCEL_PUBLISH publish_id=%s" % (i + 1)
63        if "FAIL" in dev[0].request(cmd):
64            raise Exception("NAN_CANCEL_PUBLISH failed")
65
66        ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=1)
67        if ev is None:
68            raise Exception("PublishTerminated event not seen")
69
70def test_nan_usd_subscribe(dev, apdev):
71    """NAN USD Subscribe"""
72    check_nan_usd_capab(dev[0])
73    cmd = "NAN_SUBSCRIBE service_name=_test active=1 srv_proto_type=2 ssi=1122334455"
74    id0 = dev[0].request(cmd)
75    if "FAIL" in id0:
76        raise Exception("NAN_SUBSCRIBE failed")
77
78    cmd = "NAN_CANCEL_SUBSCRIBE subscribe_id=" + id0
79    if "FAIL" in dev[0].request(cmd):
80        raise Exception("NAN_CANCEL_SUBSCRIBE failed")
81
82    ev = dev[0].wait_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=1)
83    if ev is None:
84        raise Exception("SubscribeTerminated event not seen")
85    if "subscribe_id=" + id0 not in ev:
86        raise Exception("Unexpected subscribe_id: " + ev)
87    if "reason=user-request" not in ev:
88        raise Exception("Unexpected reason: " + ev)
89
90def test_nan_usd_match(dev, apdev):
91    """NAN USD Publish/Subscribe match"""
92    check_nan_usd_capab(dev[0])
93    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455"
94    id0 = dev[0].request(cmd)
95    if "FAIL" in id0:
96        raise Exception("NAN_SUBSCRIBE failed")
97
98    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 ttl=5"
99    id1 = dev[1].request(cmd)
100    if "FAIL" in id1:
101        raise Exception("NAN_PUBLISH failed")
102
103    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
104    if ev is None:
105        raise Exception("DiscoveryResult event not seen")
106    if "srv_proto_type=2" not in ev.split(' '):
107        raise Exception("Unexpected srv_proto_type: " + ev)
108    if "ssi=6677" not in ev.split(' '):
109        raise Exception("Unexpected ssi: " + ev)
110
111    dev[0].request("NAN_CANCEL_SUBSCRIBE id=" + id0)
112    dev[1].request("NAN_CANCEL_PUBLISH id=" + id1)
113
114def test_nan_usd_match2(dev, apdev):
115    """NAN USD Publish/Subscribe match (2)"""
116    check_nan_usd_capab(dev[0])
117    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 ttl=10 fsd=0"
118    id0 = dev[1].request(cmd)
119    if "FAIL" in id0:
120        raise Exception("NAN_PUBLISH failed")
121
122    time.sleep(1)
123
124    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455 active=1"
125    id0 = dev[0].request(cmd)
126    if "FAIL" in id0:
127        raise Exception("NAN_SUBSCRIBE failed")
128
129    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
130    if ev is None:
131        raise Exception("DiscoveryResult event not seen")
132    if "srv_proto_type=2" not in ev.split(' '):
133        raise Exception("Unexpected srv_proto_type: " + ev)
134    if "ssi=6677" not in ev.split(' '):
135        raise Exception("Unexpected ssi: " + ev)
136
137    # Check for publisher and subscriber functionality to time out
138    ev = dev[0].wait_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=2)
139    if ev is None:
140        raise Exception("Subscribe not terminated")
141    ev = dev[1].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
142    if ev is None:
143        raise Exception("Publish not terminated")
144
145def test_nan_usd_match3(dev, apdev):
146    """NAN USD Publish/Subscribe match (3)"""
147    check_nan_usd_capab(dev[0])
148    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455 active=1"
149    id0 = dev[0].request(cmd)
150    if "FAIL" in id0:
151        raise Exception("NAN_SUBSCRIBE failed")
152
153    time.sleep(0.05)
154
155    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 ttl=10"
156    id1 = dev[1].request(cmd)
157    if "FAIL" in id1:
158        raise Exception("NAN_PUBLISH failed")
159
160    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
161    if ev is None:
162        raise Exception("DiscoveryResult event not seen")
163    if "srv_proto_type=2" not in ev.split(' '):
164        raise Exception("Unexpected srv_proto_type: " + ev)
165    if "ssi=6677" not in ev.split(' '):
166        raise Exception("Unexpected ssi: " + ev)
167
168    dev[0].request("NAN_CANCEL_SUBSCRIBE id=" + id0)
169    dev[1].request("NAN_CANCEL_PUBLISH id=" + id1)
170
171def split_nan_event(ev):
172    vals = dict()
173    for p in ev.split(' ')[1:]:
174        name, val = p.split('=')
175        vals[name] = val
176    return vals
177
178def test_nan_usd_followup(dev, apdev):
179    """NAN USD Publish/Subscribe match and follow-up"""
180    check_nan_usd_capab(dev[0])
181    run_nan_usd_followup(dev[0], dev[1])
182
183def test_nan_usd_followup_multi_chan(dev, apdev):
184    """NAN USD Publish/Subscribe match and follow-up with multi channels"""
185    check_nan_usd_capab(dev[0])
186    run_nan_usd_followup(dev[0], dev[1], multi_chan=True)
187
188def test_nan_usd_followup_hostapd(dev, apdev):
189    """NAN USD Publish/Subscribe match and follow-up with hostapd"""
190    check_nan_usd_capab(dev[0])
191    hapd = hostapd.add_ap(apdev[0], {"ssid": "open",
192                                     "channel": "6"})
193    run_nan_usd_followup(hapd, dev[1])
194
195def run_nan_usd_followup(dev0, dev1, multi_chan=False):
196    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=3 ssi=1122334455"
197    if multi_chan:
198        cmd += " freq=2462"
199    id0 = dev0.request(cmd)
200    if "FAIL" in id0:
201        raise Exception("NAN_SUBSCRIBE failed")
202
203    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=3 ssi=6677 ttl=10"
204    if multi_chan:
205        cmd += " freq=2412 freq_list=2437,2462"
206    id1 = dev1.request(cmd)
207    if "FAIL" in id1:
208        raise Exception("NAN_PUBLISH failed")
209
210    ev = dev0.wait_event(["NAN-DISCOVERY-RESULT"], timeout=10)
211    if ev is None:
212        raise Exception("DiscoveryResult event not seen")
213    vals = split_nan_event(ev)
214    if vals['srv_proto_type'] != '3':
215        raise Exception("Unexpected srv_proto_type: " + ev)
216    if vals['ssi'] != '6677':
217        raise Exception("Unexpected ssi: " + ev)
218    if vals['subscribe_id'] != id0:
219        raise Exception("Unexpected subscribe_id: " + ev)
220    if vals['publish_id'] != id1:
221        raise Exception("Unexpected publish_id: " + ev)
222    addr1 = vals['address']
223
224    # Automatically sent Follow-up message without ssi
225    ev = dev1.wait_event(["NAN-RECEIVE"], timeout=5)
226    if ev is None:
227        raise Exception("Receive event not seen")
228    vals2 = split_nan_event(ev)
229    if vals2['ssi'] != '':
230        raise Exception("Unexpected ssi in Follow-up: " + ev)
231
232    # Follow-up from subscriber to publisher
233    time.sleep(0.2)
234    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=8899".format(vals['subscribe_id'], vals['publish_id'], addr1)
235    if "FAIL" in dev0.request(cmd):
236        raise Exception("NAN_TRANSMIT failed")
237
238    ev = dev1.wait_event(["NAN-RECEIVE"], timeout=5)
239    if ev is None:
240        raise Exception("Receive event not seen")
241    vals = split_nan_event(ev)
242    if vals['ssi'] != '8899':
243        raise Exception("Unexpected ssi in Follow-up: " + ev)
244    if vals['id'] != id1:
245        raise Exception("Unexpected id: " + ev)
246    if vals['peer_instance_id'] != id0:
247        raise Exception("Unexpected peer_instance_id: " + ev)
248    addr0 = vals['address']
249
250    # Follow-up from publisher to subscriber
251    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=aabbccdd".format(id1, vals['peer_instance_id'], addr0)
252    if "FAIL" in dev1.request(cmd):
253        raise Exception("NAN_TRANSMIT failed")
254
255    ev = dev0.wait_event(["NAN-RECEIVE"], timeout=5)
256    if ev is None:
257        raise Exception("Receive event not seen")
258    vals = split_nan_event(ev)
259    if vals['ssi'] != 'aabbccdd':
260        raise Exception("Unexpected ssi in Follow-up: " + ev)
261    if vals['id'] != id0:
262        raise Exception("Unexpected id: " + ev)
263    if vals['peer_instance_id'] != id1:
264        raise Exception("Unexpected peer_instance_id: " + ev)
265
266    # Another Follow-up message from publisher to subscriber
267    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=eeff".format(id1, vals['peer_instance_id'], addr0)
268    if "FAIL" in dev1.request(cmd):
269        raise Exception("NAN_TRANSMIT failed")
270
271    ev = dev0.wait_event(["NAN-RECEIVE"], timeout=5)
272    if ev is None:
273        raise Exception("Receive event not seen")
274    vals = split_nan_event(ev)
275    if vals['ssi'] != 'eeff':
276        raise Exception("Unexpected ssi in Follow-up: " + ev)
277    if vals['id'] != id0:
278        raise Exception("Unexpected id: " + ev)
279    if vals['peer_instance_id'] != id1:
280        raise Exception("Unexpected peer_instance_id: " + ev)
281
282    # And one more Follow-up message from publisher to subscriber after some
283    # delay.
284    time.sleep(0.5)
285    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=22334455".format(id1, vals['peer_instance_id'], addr0)
286    if "FAIL" in dev1.request(cmd):
287        raise Exception("NAN_TRANSMIT failed")
288
289    ev = dev0.wait_event(["NAN-RECEIVE"], timeout=5)
290    if ev is None:
291        raise Exception("Receive event not seen")
292    vals = split_nan_event(ev)
293    if vals['ssi'] != '22334455':
294        raise Exception("Unexpected ssi in Follow-up: " + ev)
295    if vals['id'] != id0:
296        raise Exception("Unexpected id: " + ev)
297    if vals['peer_instance_id'] != id1:
298        raise Exception("Unexpected peer_instance_id: " + ev)
299
300    dev0.request("NAN_CANCEL_SUBSCRIBE id=" + id0)
301    dev1.request("NAN_CANCEL_PUBLISH id=" + id1)
302
303def test_nan_usd_solicited_publisher(dev, apdev):
304    """NAN USD Publish/Subscribe match with solicited-only Publisher"""
305    check_nan_usd_capab(dev[0])
306    cmd = "NAN_PUBLISH service_name=_test unsolicited=0 srv_proto_type=2 ssi=6677"
307    id1 = dev[1].request(cmd)
308    if "FAIL" in id1:
309        raise Exception("NAN_PUBLISH failed")
310
311    cmd = "NAN_SUBSCRIBE service_name=_test active=1 srv_proto_type=2 ssi=1122334455"
312    id0 = dev[0].request(cmd)
313    if "FAIL" in id0:
314        raise Exception("NAN_SUBSCRIBE failed")
315
316    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
317    if ev is None:
318        raise Exception("DiscoveryResult event not seen")
319    vals = split_nan_event(ev)
320    if vals['srv_proto_type'] != "2":
321        raise Exception("Unexpected ssi: " + ev)
322    if vals['ssi'] != "6677":
323        raise Exception("Unexpected ssi: " + ev)
324
325    ev = dev[1].wait_event(["NAN-REPLIED"], timeout=5)
326    if ev is None:
327        raise Exception("Replied event not seen")
328    vals = split_nan_event(ev)
329    if vals['publish_id'] != id1:
330        raise Exception("Unexpected publish_id: " + ev)
331    if vals['subscribe_id'] != id0:
332        raise Exception("Unexpected subscribe_id: " + ev)
333    if vals['address'] != dev[0].own_addr():
334        raise Exception("Unexpected address: " + ev)
335    if vals['srv_proto_type'] != "2":
336        raise Exception("Unexpected ssi: " + ev)
337    if vals['ssi'] != "1122334455":
338        raise Exception("Unexpected ssi: " + ev)
339
340def test_nan_usd_solicited_publisher_timeout(dev, apdev):
341    """NAN USD solicited Publisher timeout"""
342    check_nan_usd_capab(dev[0])
343    cmd = "NAN_PUBLISH service_name=_test unsolicited=0 ttl=10 srv_proto_type=2 ssi=6677"
344    id = dev[0].request(cmd)
345    if "FAIL" in id:
346        raise Exception("NAN_PUBLISH failed")
347    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=2)
348    if ev is not None:
349        raise Exception("Too quick Publish termination")
350
351    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
352    if ev is None:
353        raise Exception("Publish not terminated")
354    if "reason=timeout" not in ev:
355        raise Exception("Unexpected reason: " + ev)
356
357def test_nan_usd_unsolicited_publisher_timeout(dev, apdev):
358    """NAN USD unsolicited Publisher timeout"""
359    check_nan_usd_capab(dev[0])
360    cmd = "NAN_PUBLISH service_name=_test solicited=0 ttl=10 srv_proto_type=2 ssi=6677"
361    id = dev[0].request(cmd)
362    if "FAIL" in id:
363        raise Exception("NAN_PUBLISH failed")
364    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=2)
365    if ev is not None:
366        raise Exception("Too quick Publish termination")
367
368    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
369    if ev is None:
370        raise Exception("Publish not terminated")
371    if "reason=timeout" not in ev:
372        raise Exception("Unexpected reason: " + ev)
373
374def test_nan_usd_publish_all_chans(dev, apdev):
375    """NAN USD Publish - all channels"""
376    check_nan_usd_capab(dev[0])
377    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 freq_list=all ttl=10"
378    id0 = dev[0].request(cmd)
379    if "FAIL" in id0:
380        raise Exception("NAN_PUBLISH failed")
381
382    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
383    if ev is None:
384        raise Exception("PublishTerminated event not seen")
385
386def test_nan_usd_publish_multi_chan(dev, apdev):
387    """NAN USD Publish - multi channel"""
388    check_nan_usd_capab(dev[0])
389    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 freq_list=2412,2462 ttl=10"
390    id0 = dev[0].request(cmd)
391    if "FAIL" in id0:
392        raise Exception("NAN_PUBLISH failed")
393
394    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
395    if ev is None:
396        raise Exception("PublishTerminated event not seen")
397
398def test_nan_usd_publish_multi_chan_solicited(dev, apdev):
399    """NAN USD Publish - multi channel - solicited"""
400    check_nan_usd_capab(dev[0])
401    cmd = "NAN_PUBLISH service_name=_test unsolicited=0 srv_proto_type=2 ssi=6677 freq_list=2412,2462 ttl=10"
402    id0 = dev[0].request(cmd)
403    if "FAIL" in id0:
404        raise Exception("NAN_PUBLISH failed")
405
406    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
407    if ev is None:
408        raise Exception("PublishTerminated event not seen")
409
410def test_nan_usd_publish_multi_chan_pause(dev, apdev):
411    """NAN USD Publish - multi channel"""
412    check_nan_usd_capab(dev[0])
413    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 freq_list=2412,2462 ttl=10"
414    id0 = dev[0].request(cmd)
415    if "FAIL" in id0:
416        raise Exception("NAN_PUBLISH failed")
417
418    time.sleep(1)
419
420    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455"
421    id1 = dev[1].request(cmd)
422    if "FAIL" in id1:
423        raise Exception("NAN_SUBSCRIBE failed")
424
425    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=8899 active=1"
426    id2 = dev[2].request(cmd)
427    if "FAIL" in id2:
428        raise Exception("NAN_SUBSCRIBE failed")
429
430    ev = dev[0].wait_event(["NAN-RECEIVE"], timeout=10)
431    if ev is None:
432        raise Exception("Receive event not seen")
433    if "address=" + dev[1].own_addr() in ev.split():
434        dev1 = dev[1]
435        dev2 = dev[2]
436    elif "address=" + dev[2].own_addr() in ev.split():
437        dev1 = dev[2]
438        dev2 = dev[1]
439    else:
440        raise Exception("Unexpected address in NAN-RECEIVE: " + ev)
441
442    ev = dev1.wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
443    if ev is None:
444        raise Exception("DiscoveryResult event not seen (1)")
445    vals = split_nan_event(ev)
446
447    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=8899".format(vals['subscribe_id'], vals['publish_id'], dev[0].own_addr())
448    if "FAIL" in dev1.request(cmd):
449        raise Exception("NAN_TRANSMIT failed")
450    ev = dev[0].wait_event(["NAN-RECEIVE"], timeout=5)
451    if ev is None:
452        raise Exception("Receive event not seen for follow-up (1)")
453    vals = split_nan_event(ev)
454    cmd = "NAN_UNPAUSE_PUBLISH publish_id={} peer_instance_id={} peer={}".format(vals['id'], vals['peer_instance_id'], vals['address'])
455    if "OK" not in dev[0].request(cmd):
456        raise Exception("NAN_UNPAUSE_PUBLISH failed")
457
458    ev = dev2.wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
459    if ev is None:
460        raise Exception("DiscoveryResult event not seen (2)")
461    vals = split_nan_event(ev)
462    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=8899".format(vals['subscribe_id'], vals['publish_id'], dev[0].own_addr())
463    if "FAIL" in dev2.request(cmd):
464        raise Exception("NAN_TRANSMIT failed")
465    ev = dev[0].wait_event(["NAN-RECEIVE"], timeout=5)
466    if ev is None:
467        raise Exception("Receive event not seen for follow-up (2)")
468
469    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
470    if ev is None:
471        raise Exception("PublishTerminated event not seen")
472