1# Test cases for MSCS
2# Copyright (c) 2021, Jouni Malinen <j@w1.fi>
3#
4# This software may be distributed under the terms of the BSD license.
5# See README for more details.
6
7import struct
8import time
9
10import hostapd
11from utils import *
12
13def register_mcsc_req(hapd):
14    type = 0x00d0
15    match = "1304"
16    if "OK" not in hapd.request("REGISTER_FRAME %04x %s" % (type, match)):
17        raise Exception("Could not register frame reception for Robust AV Streaming")
18
19def fill_change_params(dialog_token, status_code, params):
20    req_type = params['req_type'] if 'req_type' in params else 2
21    return struct.pack('<BBBHBBBBBBI', 19, 5, dialog_token, status_code,
22                       255, 8, 88, req_type, params['up_bitmap'],
23                       params['up_limit'], params['stream_timeout'])
24
25def handle_mscs_req(hapd, wrong_dialog=False, status_code=0,
26                    change_params=None):
27    msg = hapd.mgmt_rx()
28    if msg['subtype'] != 13:
29        logger.info("RX:" + str(msg))
30        raise Exception("Received unexpected Management frame")
31    categ, act, dialog_token = struct.unpack('BBB', msg['payload'][0:3])
32    if categ != 19 or act != 4:
33        logger.info("RX:" + str(msg))
34        raise Exception("Received unexpected Action frame")
35
36    if wrong_dialog:
37        dialog_token = (dialog_token + 1) % 256
38    msg['da'] = msg['sa']
39    msg['sa'] = hapd.own_addr()
40
41    if change_params is not None:
42        msg['payload'] = fill_change_params(dialog_token, status_code,
43                                            change_params)
44    else:
45        msg['payload'] = struct.pack('<BBBH', 19, 5, dialog_token, status_code)
46
47    hapd.mgmt_tx(msg)
48    ev = hapd.wait_event(["MGMT-TX-STATUS"], timeout=5)
49    if ev is None or "stype=13 ok=1" not in ev:
50        raise Exception("No TX status reported")
51
52def wait_mscs_result(dev, expect_status=0, change_params=None):
53    ev = dev.wait_event(["CTRL-EVENT-MSCS-RESULT"], timeout=1)
54    if ev is None:
55        raise Exception("No MSCS result reported")
56    if "status_code=%d" % expect_status not in ev:
57        raise Exception("Unexpected MSCS result: " + ev)
58    if change_params is None and 'change' in ev:
59        raise Exception("Unexpected 'change' in MSCS result: " + ev)
60    if change_params is not None and \
61        ("change" not in ev or
62         "up_limit=%d" % change_params['up_limit'] not in ev or
63         "up_bitmap=%d" % change_params['up_bitmap'] not in ev or
64         "stream_timeout=%d" % change_params['stream_timeout'] not in ev):
65        raise Exception("Unexpected MSCS result: " + ev)
66
67def add_mscs_ap(apdev, reg_mscs_req=True, mscs_supported=True,
68                assocresp_elements=None):
69    params = {"ssid": "mscs"}
70
71    if mscs_supported:
72        params["ext_capa"] = 10*"00" + "20"
73    else:
74        params["ext_capa_mask"] = 10*"00" + "20"
75
76    if assocresp_elements is not None:
77        params['assocresp_elements'] = assocresp_elements
78
79    hapd = hostapd.add_ap(apdev, params)
80
81    if reg_mscs_req:
82        register_mcsc_req(hapd)
83
84    return hapd
85
86def test_mscs_invalid_params(dev, apdev):
87    """MSCS command invalid parameters"""
88    tests = ["",
89             "add Xp_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F",
90             "add up_bitmap=F0 Xp_limit=7 stream_timeout=12345 frame_classifier=045F",
91             "add up_bitmap=F0 up_limit=7 Xtream_timeout=12345 frame_classifier=045F",
92             "add up_bitmap=F0 up_limit=7 stream_timeout=12345 Xrame_classifier=045F",
93             "add up_bitmap=X0 up_limit=7 stream_timeout=12345 frame_classifier=045F",
94             "add up_bitmap=F0 up_limit=7 stream_timeout=0 frame_classifier=045F",
95             "add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=X45F",
96             "change "]
97    for t in tests:
98        if "FAIL" not in dev[0].request("MSCS " + t):
99            raise Exception("Invalid MSCS parameters accepted: " + t)
100
101def mscs_run(dev, apdev, func):
102    try:
103        func(dev, apdev)
104    finally:
105        dev[0].request("MSCS remove")
106
107def test_mscs_without_ap_support(dev, apdev):
108    """MSCS without AP support"""
109    mscs_run(dev, apdev, run_mscs_without_ap_support)
110
111def run_mscs_without_ap_support(dev, apdev):
112    add_mscs_ap(apdev[0], reg_mscs_req=False, mscs_supported=False)
113
114    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
115    if "OK" not in dev[0].request(cmd):
116        raise Exception("Failed to configure MSCS")
117
118    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412")
119
120    cmd = "MSCS change up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
121    if "FAIL" not in dev[0].request(cmd):
122        raise Exception("MSCS change accepted unexpectedly")
123
124    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
125    if "FAIL" not in dev[0].request(cmd):
126        raise Exception("MSCS add accepted unexpectedly")
127
128def test_mscs_post_assoc(dev, apdev):
129    """MSCS configuration post-association"""
130    mscs_run(dev, apdev, run_mscs_post_assoc)
131
132def run_mscs_post_assoc(dev, apdev):
133    hapd = add_mscs_ap(apdev[0])
134
135    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412")
136
137    hapd.dump_monitor()
138    hapd.set("ext_mgmt_frame_handling", "1")
139
140    cmd = "MSCS change up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
141    if "FAIL" not in dev[0].request(cmd):
142        raise Exception("MSCS change accepted unexpectedly")
143
144    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
145    if "OK" not in dev[0].request(cmd):
146        raise Exception("MSCS add failed")
147
148    handle_mscs_req(hapd)
149    wait_mscs_result(dev[0])
150
151    cmd = "MSCS change up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
152    if "OK" not in dev[0].request(cmd):
153        raise Exception("MSCS change failed")
154
155    handle_mscs_req(hapd)
156    wait_mscs_result(dev[0])
157
158    cmd = "MSCS change up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
159    if "OK" not in dev[0].request(cmd):
160        raise Exception("MSCS change failed")
161
162    handle_mscs_req(hapd, status_code=23456)
163    wait_mscs_result(dev[0], expect_status=23456)
164
165def test_mscs_pre_assoc(dev, apdev):
166    """MSCS configuration pre-association"""
167    mscs_run(dev, apdev, run_mscs_pre_assoc)
168
169def run_mscs_pre_assoc(dev, apdev):
170    ies = "ff0c5800000000000000" + "01020000"
171    hapd = add_mscs_ap(apdev[0], assocresp_elements=ies)
172
173    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
174    if "OK" not in dev[0].request(cmd):
175        raise Exception("MSCS add failed")
176
177    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412",
178                   wait_connect=False)
179    wait_mscs_result(dev[0])
180    dev[0].wait_connected()
181
182    hapd.set("ext_mgmt_frame_handling", "1")
183
184    cmd = "MSCS change up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
185    if "OK" not in dev[0].request(cmd):
186        raise Exception("MSCS change failed")
187
188    handle_mscs_req(hapd)
189    wait_mscs_result(dev[0])
190
191    cmd = "MSCS change up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
192    if "OK" not in dev[0].request(cmd):
193        raise Exception("MSCS change failed")
194
195    handle_mscs_req(hapd, wrong_dialog=True)
196
197    ev = dev[0].wait_event(["CTRL-EVENT-MSCS-RESULT"], timeout=1)
198    if ev is not None:
199        raise Exception("Unexpected MSCS result reported")
200
201def test_mscs_assoc_failure(dev, apdev):
202    """MSCS configuration failure during association exchange"""
203    mscs_run(dev, apdev, run_mscs_assoc_failure)
204
205def run_mscs_assoc_failure(dev, apdev):
206    ies = "ff0c5800000000000000" + "01020001"
207    hapd = add_mscs_ap(apdev[0], assocresp_elements=ies)
208
209    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
210    if "OK" not in dev[0].request(cmd):
211        raise Exception("MSCS add failed")
212
213    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412",
214                   wait_connect=False)
215    wait_mscs_result(dev[0], expect_status=256)
216    dev[0].wait_connected()
217    dev[0].request("REMOVE_NETWORK all")
218    dev[0].wait_disconnected()
219
220    hapd.dump_monitor()
221    # No MSCS Status subelement
222    hapd.set("assocresp_elements", "ff085800000000000000")
223    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412",
224                   wait_connect=False)
225    ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED", "CTRL-EVENT-MSCS-RESULT"],
226                           timeout=10)
227    if ev is None:
228        raise Exception("No connection event")
229    if "CTRL-EVENT-MSCS-RESULT" in ev:
230        raise Exception("Unexpected MSCS result")
231
232def test_mscs_local_errors(dev, apdev):
233    """MSCS configuration local errors"""
234    mscs_run(dev, apdev, run_mscs_local_errors)
235
236def run_mscs_local_errors(dev, apdev):
237    hapd = add_mscs_ap(apdev[0])
238    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412")
239
240    hapd.dump_monitor()
241    hapd.set("ext_mgmt_frame_handling", "1")
242
243    for count in range(1, 3):
244        with alloc_fail(dev[0], count, "wpas_send_mscs_req"):
245            cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
246            if "FAIL" not in dev[0].request(cmd):
247                raise Exception("MSCS add succeeded in error case")
248
249def test_mscs_assoc_change_response(dev, apdev):
250    """MSCS configuration failure during assoc - AP response with change request"""
251    mscs_run(dev, apdev, run_mscs_assoc_change_response)
252
253def run_mscs_assoc_change_response(dev, apdev):
254    ies = "ff0c5802060701000000" + "01020001"
255    hapd = add_mscs_ap(apdev[0], assocresp_elements=ies)
256
257    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
258    if "OK" not in dev[0].request(cmd):
259        raise Exception("MSCS add failed")
260
261    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412",
262                   wait_connect=False)
263
264    # Based on the Association Response elements above
265    change_params = {
266        'up_bitmap': 6,
267        'up_limit': 7,
268        'stream_timeout': 1
269    }
270    wait_mscs_result(dev[0], expect_status=256, change_params=change_params)
271    dev[0].wait_connected()
272
273    hapd.dump_monitor()
274    hapd.set("ext_mgmt_frame_handling", "1")
275
276    # Verify we're able to send a new MSCS Request frame.
277    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345` frame_classifier=045F"
278    if "OK" not in dev[0].request(cmd):
279        raise Exception("MSCS add failed")
280
281    handle_mscs_req(hapd)
282    wait_mscs_result(dev[0])
283
284def test_mscs_post_assoc_change_response(dev, apdev):
285    """MSCS configuration failure post assoc - AP response with MSCS Response frame change request"""
286    mscs_run(dev, apdev, run_mscs_post_assoc_change_response)
287
288def run_mscs_post_assoc_change_response(dev, apdev):
289    hapd = add_mscs_ap(apdev[0])
290    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412")
291
292    hapd.dump_monitor()
293    hapd.set("ext_mgmt_frame_handling", "1")
294
295    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
296    if "OK" not in dev[0].request(cmd):
297        raise Exception("MSCS add failed")
298
299    change_params = {
300        'up_bitmap': 6,
301        'up_limit': 7,
302        'stream_timeout': 1
303    }
304
305    handle_mscs_req(hapd, status_code=256, change_params=change_params)
306    wait_mscs_result(dev[0], expect_status=256, change_params=change_params)
307
308    # Verify we're able to send new valid MSCS request
309    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
310    if "OK" not in dev[0].request(cmd):
311        raise Exception("MSCS add failed")
312
313    handle_mscs_req(hapd)
314    wait_mscs_result(dev[0])
315
316def test_mscs_invalid_req_type_response(dev, apdev):
317    """MSCS AP response with invalid request type"""
318    mscs_run(dev, apdev, run_mscs_invalid_req_type_response)
319
320def run_mscs_invalid_req_type_response(dev, apdev):
321    hapd = add_mscs_ap(apdev[0])
322    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412")
323
324    hapd.dump_monitor()
325    hapd.set("ext_mgmt_frame_handling", "1")
326
327    change_params = {
328        'up_bitmap': 6,
329        'up_limit': 7,
330        'stream_timeout': 1
331    }
332
333    # Verify frames with invalid MSCS decriptor request type dropped
334    for req_type in [3, 4, 10]:
335        cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
336        if "OK" not in dev[0].request(cmd):
337            raise Exception("MSCS add failed")
338
339        change_params['req_type'] = req_type
340        handle_mscs_req(hapd, status_code=256, change_params=change_params)
341        ev = dev[0].wait_event(["CTRL-EVENT-MSCS-RESULT"], timeout=1)
342        if ev is not None:
343            raise Exception("Unexpected MSCS result reported")
344
345def send_unsolicited_mscs_response(dev, hapd, status_code, params=None,
346                                   wrong_dialog=False):
347    dialog_token = 0 if not wrong_dialog else 10
348    if params is not None:
349        payload = fill_change_params(dialog_token, status_code, params)
350    else:
351        payload = struct.pack('<BBBH', 19, 5, dialog_token, status_code)
352
353    msg = {
354        'fc': 0xd0,
355        'sa': hapd.own_addr(),
356        'da': dev.own_addr(),
357        'bssid': hapd.own_addr(),
358        'payload': payload,
359    }
360
361    hapd.mgmt_tx(msg)
362    ev = hapd.wait_event(["MGMT-TX-STATUS"], timeout=5)
363    if ev is None or "stype=13 ok=1" not in ev:
364        raise Exception("No TX status reported")
365
366def test_mscs_unsolicited_response(dev, apdev):
367    """MSCS configured and AP sends unsolicited response to terminate / change the session"""
368    mscs_run(dev, apdev, run_mscs_unsolicited_response)
369
370def run_mscs_unsolicited_response(dev, apdev):
371    hapd = add_mscs_ap(apdev[0])
372    dev[0].connect("mscs", key_mgmt="NONE", scan_freq="2412")
373
374    hapd.dump_monitor()
375    hapd.set("ext_mgmt_frame_handling", "1")
376
377    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
378    if "OK" not in dev[0].request(cmd):
379        raise Exception("MSCS add failed")
380
381    handle_mscs_req(hapd)
382    wait_mscs_result(dev[0])
383
384    status_code = 256
385
386    # Verify unsolicited response with wrong dialog token (othar than 0) is
387    # dropped
388    send_unsolicited_mscs_response(dev[0], hapd, status_code=status_code,
389                                   wrong_dialog=True)
390    ev = dev[0].wait_event(["CTRL-EVENT-MSCS-RESULT"], timeout=1)
391    if ev is not None:
392        raise Exception("Unexpected MSCS result reported")
393
394    # Verify unsolicited response without change request (termination)
395    send_unsolicited_mscs_response(dev[0], hapd, status_code=status_code)
396    wait_mscs_result(dev[0], expect_status=status_code)
397
398    # Send new MSCS request
399    cmd = "MSCS add up_bitmap=F0 up_limit=7 stream_timeout=12345 frame_classifier=045F"
400    if "OK" not in dev[0].request(cmd):
401        raise Exception("MSCS add failed")
402
403    handle_mscs_req(hapd)
404    wait_mscs_result(dev[0])
405
406    params = {
407        'up_bitmap': 6,
408        'up_limit': 7,
409        'stream_timeout': 1
410    }
411
412    # Verify unsolicited response handling with change request
413    send_unsolicited_mscs_response(dev[0], hapd, status_code=status_code,
414                                   params=params)
415    wait_mscs_result(dev[0], expect_status=status_code, change_params=params)
416