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 
7 import time
8 
9 import logging
10 logger = logging.getLogger()
11 
12 import hostapd
13 from utils import *
14 
15 def 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 
20 def 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 
30 def 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 
70 def 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 
90 def 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 
114 def 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 
145 def 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 
171 def 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 
178 def 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 
183 def 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 
188 def 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 
195 def 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 
303 def 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 
340 def 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 
357 def 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 
374 def 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 
386 def 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 
398 def 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 
410 def 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