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    id0 = dev[1].request(cmd)
100    if "FAIL" in id0:
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    # Check for publisher and subscriber functionality to time out
112    ev = dev[0].wait_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=5)
113    if ev is None:
114        raise Exception("Subscribe not terminated")
115    ev = dev[1].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=5)
116    if ev is None:
117        raise Exception("Publish not terminated")
118
119def test_nan_usd_match2(dev, apdev):
120    """NAN USD Publish/Subscribe match (2)"""
121    check_nan_usd_capab(dev[0])
122    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 ttl=10 fsd=0"
123    id0 = dev[1].request(cmd)
124    if "FAIL" in id0:
125        raise Exception("NAN_PUBLISH failed")
126
127    time.sleep(1)
128
129    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455 active=1"
130    id0 = dev[0].request(cmd)
131    if "FAIL" in id0:
132        raise Exception("NAN_SUBSCRIBE failed")
133
134    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
135    if ev is None:
136        raise Exception("DiscoveryResult event not seen")
137    if "srv_proto_type=2" not in ev.split(' '):
138        raise Exception("Unexpected srv_proto_type: " + ev)
139    if "ssi=6677" not in ev.split(' '):
140        raise Exception("Unexpected ssi: " + ev)
141
142    # Check for publisher and subscriber functionality to time out
143    ev = dev[0].wait_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=2)
144    if ev is None:
145        raise Exception("Subscribe not terminated")
146    ev = dev[1].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
147    if ev is None:
148        raise Exception("Publish not terminated")
149
150def test_nan_usd_match3(dev, apdev):
151    """NAN USD Publish/Subscribe match (3)"""
152    check_nan_usd_capab(dev[0])
153    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455 active=1"
154    id0 = dev[0].request(cmd)
155    if "FAIL" in id0:
156        raise Exception("NAN_SUBSCRIBE failed")
157
158    time.sleep(0.05)
159
160    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 ttl=10"
161    id0 = dev[1].request(cmd)
162    if "FAIL" in id0:
163        raise Exception("NAN_PUBLISH failed")
164
165    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
166    if ev is None:
167        raise Exception("DiscoveryResult event not seen")
168    if "srv_proto_type=2" not in ev.split(' '):
169        raise Exception("Unexpected srv_proto_type: " + ev)
170    if "ssi=6677" not in ev.split(' '):
171        raise Exception("Unexpected ssi: " + ev)
172
173    # Check for publisher and subscriber functionality to time out
174    ev = dev[0].wait_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=2)
175    if ev is None:
176        raise Exception("Subscribe not terminated")
177    ev = dev[1].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
178    if ev is None:
179        raise Exception("Publish not terminated")
180
181def split_nan_event(ev):
182    vals = dict()
183    for p in ev.split(' ')[1:]:
184        name, val = p.split('=')
185        vals[name] = val
186    return vals
187
188def test_nan_usd_followup(dev, apdev):
189    """NAN USD Publish/Subscribe match and follow-up"""
190    check_nan_usd_capab(dev[0])
191    run_nan_usd_followup(dev[0], dev[1])
192
193def test_nan_usd_followup_multi_chan(dev, apdev):
194    """NAN USD Publish/Subscribe match and follow-up with multi channels"""
195    check_nan_usd_capab(dev[0])
196    run_nan_usd_followup(dev[0], dev[1], multi_chan=True)
197
198def test_nan_usd_followup_hostapd(dev, apdev):
199    """NAN USD Publish/Subscribe match and follow-up with hostapd"""
200    check_nan_usd_capab(dev[0])
201    hapd = hostapd.add_ap(apdev[0], {"ssid": "open",
202                                     "channel": "6"})
203    run_nan_usd_followup(hapd, dev[1])
204
205def run_nan_usd_followup(dev0, dev1, multi_chan=False):
206    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=3 ssi=1122334455"
207    if multi_chan:
208        cmd += " freq=2462"
209    id0 = dev0.request(cmd)
210    if "FAIL" in id0:
211        raise Exception("NAN_SUBSCRIBE failed")
212
213    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=3 ssi=6677 ttl=10"
214    if multi_chan:
215        cmd += " freq=2412 freq_list=2437,2462"
216    id1 = dev1.request(cmd)
217    if "FAIL" in id1:
218        raise Exception("NAN_PUBLISH failed")
219
220    ev = dev0.wait_event(["NAN-DISCOVERY-RESULT"], timeout=10)
221    if ev is None:
222        raise Exception("DiscoveryResult event not seen")
223    vals = split_nan_event(ev)
224    if vals['srv_proto_type'] != '3':
225        raise Exception("Unexpected srv_proto_type: " + ev)
226    if vals['ssi'] != '6677':
227        raise Exception("Unexpected ssi: " + ev)
228    if vals['subscribe_id'] != id0:
229        raise Exception("Unexpected subscribe_id: " + ev)
230    if vals['publish_id'] != id1:
231        raise Exception("Unexpected publish_id: " + ev)
232    addr1 = vals['address']
233
234    # Automatically sent Follow-up message without ssi
235    ev = dev1.wait_event(["NAN-RECEIVE"], timeout=5)
236    if ev is None:
237        raise Exception("Receive event not seen")
238    vals2 = split_nan_event(ev)
239    if vals2['ssi'] != '':
240        raise Exception("Unexpected ssi in Follow-up: " + ev)
241
242    # Follow-up from subscriber to publisher
243    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=8899".format(vals['subscribe_id'], vals['publish_id'], addr1)
244    if "FAIL" in dev0.request(cmd):
245        raise Exception("NAN_TRANSMIT failed")
246
247    ev = dev1.wait_event(["NAN-RECEIVE"], timeout=5)
248    if ev is None:
249        raise Exception("Receive event not seen")
250    vals = split_nan_event(ev)
251    if vals['ssi'] != '8899':
252        raise Exception("Unexpected ssi in Follow-up: " + ev)
253    if vals['id'] != id1:
254        raise Exception("Unexpected id: " + ev)
255    if vals['peer_instance_id'] != id0:
256        raise Exception("Unexpected peer_instance_id: " + ev)
257    addr0 = vals['address']
258
259    # Follow-up from publisher to subscriber
260    cmd = "NAN_TRANSMIT handle={} req_instance_id={} address={} ssi=aabbccdd".format(id1, vals['peer_instance_id'], addr0)
261    if "FAIL" in dev1.request(cmd):
262        raise Exception("NAN_TRANSMIT failed")
263
264    ev = dev0.wait_event(["NAN-RECEIVE"], timeout=5)
265    if ev is None:
266        raise Exception("Receive event not seen")
267    vals = split_nan_event(ev)
268    if vals['ssi'] != 'aabbccdd':
269        raise Exception("Unexpected ssi in Follow-up: " + ev)
270    if vals['id'] != id0:
271        raise Exception("Unexpected id: " + ev)
272    if vals['peer_instance_id'] != id1:
273        raise Exception("Unexpected peer_instance_id: " + ev)
274
275    # Check for publisher and subscriber functionality to time out
276    ev = dev0.wait_event(["NAN-SUBSCRIBE-TERMINATED"], timeout=10)
277    if ev is None:
278        raise Exception("Subscribe not terminated")
279    ev = dev1.wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
280    if ev is None:
281        raise Exception("Publish not terminated")
282
283def test_nan_usd_solicited_publisher(dev, apdev):
284    """NAN USD Publish/Subscribe match with solicited-only Publisher"""
285    check_nan_usd_capab(dev[0])
286    cmd = "NAN_PUBLISH service_name=_test unsolicited=0 srv_proto_type=2 ssi=6677"
287    id1 = dev[1].request(cmd)
288    if "FAIL" in id1:
289        raise Exception("NAN_PUBLISH failed")
290
291    cmd = "NAN_SUBSCRIBE service_name=_test active=1 srv_proto_type=2 ssi=1122334455"
292    id0 = dev[0].request(cmd)
293    if "FAIL" in id0:
294        raise Exception("NAN_SUBSCRIBE failed")
295
296    ev = dev[0].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
297    if ev is None:
298        raise Exception("DiscoveryResult event not seen")
299    vals = split_nan_event(ev)
300    if vals['srv_proto_type'] != "2":
301        raise Exception("Unexpected ssi: " + ev)
302    if vals['ssi'] != "6677":
303        raise Exception("Unexpected ssi: " + ev)
304
305    ev = dev[1].wait_event(["NAN-REPLIED"], timeout=5)
306    if ev is None:
307        raise Exception("Replied event not seen")
308    vals = split_nan_event(ev)
309    if vals['publish_id'] != id1:
310        raise Exception("Unexpected publish_id: " + ev)
311    if vals['subscribe_id'] != id0:
312        raise Exception("Unexpected subscribe_id: " + ev)
313    if vals['address'] != dev[0].own_addr():
314        raise Exception("Unexpected address: " + ev)
315    if vals['srv_proto_type'] != "2":
316        raise Exception("Unexpected ssi: " + ev)
317    if vals['ssi'] != "1122334455":
318        raise Exception("Unexpected ssi: " + ev)
319
320def test_nan_usd_solicited_publisher_timeout(dev, apdev):
321    """NAN USD solicited Publisher timeout"""
322    check_nan_usd_capab(dev[0])
323    cmd = "NAN_PUBLISH service_name=_test unsolicited=0 ttl=10 srv_proto_type=2 ssi=6677"
324    id = dev[0].request(cmd)
325    if "FAIL" in id:
326        raise Exception("NAN_PUBLISH failed")
327    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=2)
328    if ev is not None:
329        raise Exception("Too quick Publish termination")
330
331    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
332    if ev is None:
333        raise Exception("Publish not terminated")
334    if "reason=timeout" not in ev:
335        raise Exception("Unexpected reason: " + ev)
336
337def test_nan_usd_unsolicited_publisher_timeout(dev, apdev):
338    """NAN USD unsolicited Publisher timeout"""
339    check_nan_usd_capab(dev[0])
340    cmd = "NAN_PUBLISH service_name=_test solicited=0 ttl=10 srv_proto_type=2 ssi=6677"
341    id = dev[0].request(cmd)
342    if "FAIL" in id:
343        raise Exception("NAN_PUBLISH failed")
344    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=2)
345    if ev is not None:
346        raise Exception("Too quick Publish termination")
347
348    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=10)
349    if ev is None:
350        raise Exception("Publish not terminated")
351    if "reason=timeout" not in ev:
352        raise Exception("Unexpected reason: " + ev)
353
354def test_nan_usd_publish_all_chans(dev, apdev):
355    """NAN USD Publish - all channels"""
356    check_nan_usd_capab(dev[0])
357    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 freq_list=all ttl=10"
358    id0 = dev[0].request(cmd)
359    if "FAIL" in id0:
360        raise Exception("NAN_PUBLISH failed")
361
362    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
363    if ev is None:
364        raise Exception("PublishTerminated event not seen")
365
366def test_nan_usd_publish_multi_chan(dev, apdev):
367    """NAN USD Publish - multi channel"""
368    check_nan_usd_capab(dev[0])
369    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 freq_list=2412,2462 ttl=10"
370    id0 = dev[0].request(cmd)
371    if "FAIL" in id0:
372        raise Exception("NAN_PUBLISH failed")
373
374    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
375    if ev is None:
376        raise Exception("PublishTerminated event not seen")
377
378def test_nan_usd_publish_multi_chan_solicited(dev, apdev):
379    """NAN USD Publish - multi channel - solicited"""
380    check_nan_usd_capab(dev[0])
381    cmd = "NAN_PUBLISH service_name=_test unsolicited=0 srv_proto_type=2 ssi=6677 freq_list=2412,2462 ttl=10"
382    id0 = dev[0].request(cmd)
383    if "FAIL" in id0:
384        raise Exception("NAN_PUBLISH failed")
385
386    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
387    if ev is None:
388        raise Exception("PublishTerminated event not seen")
389
390def test_nan_usd_publish_multi_chan_pause(dev, apdev):
391    """NAN USD Publish - multi channel"""
392    check_nan_usd_capab(dev[0])
393    cmd = "NAN_PUBLISH service_name=_test srv_proto_type=2 ssi=6677 freq_list=2412,2462 ttl=10"
394    id0 = dev[0].request(cmd)
395    if "FAIL" in id0:
396        raise Exception("NAN_PUBLISH failed")
397
398    time.sleep(1)
399
400    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=1122334455"
401    id1 = dev[1].request(cmd)
402    if "FAIL" in id1:
403        raise Exception("NAN_SUBSCRIBE failed")
404
405    cmd = "NAN_SUBSCRIBE service_name=_test srv_proto_type=2 ssi=8899 active=1"
406    id2 = dev[2].request(cmd)
407    if "FAIL" in id2:
408        raise Exception("NAN_SUBSCRIBE failed")
409
410    ev = dev[1].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
411    if ev is None:
412        raise Exception("DiscoveryResult event not seen (1)")
413
414    ev = dev[2].wait_event(["NAN-DISCOVERY-RESULT"], timeout=5)
415    if ev is None:
416        raise Exception("DiscoveryResult event not seen (2)")
417
418    ev = dev[0].wait_event(["NAN-PUBLISH-TERMINATED"], timeout=15)
419    if ev is None:
420        raise Exception("PublishTerminated event not seen")
421