tests: Add wait_connected() and wait_disconnected() helpers
[mech_eap.git] / tests / hwsim / test_gas.py
1 # GAS tests
2 # Copyright (c) 2013, Qualcomm Atheros, Inc.
3 # Copyright (c) 2013-2014, Jouni Malinen <j@w1.fi>
4 #
5 # This software may be distributed under the terms of the BSD license.
6 # See README for more details.
7
8 import time
9 import binascii
10 import logging
11 logger = logging.getLogger()
12 import re
13 import struct
14
15 import hostapd
16 from wpasupplicant import WpaSupplicant
17
18 def hs20_ap_params():
19     params = hostapd.wpa2_params(ssid="test-gas")
20     params['wpa_key_mgmt'] = "WPA-EAP"
21     params['ieee80211w'] = "1"
22     params['ieee8021x'] = "1"
23     params['auth_server_addr'] = "127.0.0.1"
24     params['auth_server_port'] = "1812"
25     params['auth_server_shared_secret'] = "radius"
26     params['interworking'] = "1"
27     params['access_network_type'] = "14"
28     params['internet'] = "1"
29     params['asra'] = "0"
30     params['esr'] = "0"
31     params['uesa'] = "0"
32     params['venue_group'] = "7"
33     params['venue_type'] = "1"
34     params['venue_name'] = [ "eng:Example venue", "fin:Esimerkkipaikka" ]
35     params['roaming_consortium'] = [ "112233", "1020304050", "010203040506",
36                                      "fedcba" ]
37     params['domain_name'] = "example.com,another.example.com"
38     params['nai_realm'] = [ "0,example.com,13[5:6],21[2:4][5:7]",
39                             "0,another.example.com" ]
40     params['anqp_3gpp_cell_net'] = "244,91"
41     params['network_auth_type'] = "02http://www.example.com/redirect/me/here/"
42     params['ipaddr_type_availability'] = "14"
43     params['hs20'] = "1"
44     params['hs20_oper_friendly_name'] = [ "eng:Example operator", "fin:Esimerkkioperaattori" ]
45     params['hs20_wan_metrics'] = "01:8000:1000:80:240:3000"
46     params['hs20_conn_capab'] = [ "1:0:2", "6:22:1", "17:5060:0" ]
47     params['hs20_operating_class'] = "5173"
48     return params
49
50 def start_ap(ap):
51     params = hs20_ap_params()
52     params['hessid'] = ap['bssid']
53     hostapd.add_ap(ap['ifname'], params)
54     return hostapd.Hostapd(ap['ifname'])
55
56 def get_gas_response(dev, bssid, info, allow_fetch_failure=False,
57                      extra_test=False):
58     exp = r'<.>(GAS-RESPONSE-INFO) addr=([0-9a-f:]*) dialog_token=([0-9]*) status_code=([0-9]*) resp_len=([\-0-9]*)'
59     res = re.split(exp, info)
60     if len(res) < 6:
61         raise Exception("Could not parse GAS-RESPONSE-INFO")
62     if res[2] != bssid:
63         raise Exception("Unexpected BSSID in response")
64     token = res[3]
65     status = res[4]
66     if status != "0":
67         raise Exception("GAS query failed")
68     resp_len = res[5]
69     if resp_len == "-1":
70         raise Exception("GAS query reported invalid response length")
71     if int(resp_len) > 2000:
72         raise Exception("Unexpected long GAS response")
73
74     if extra_test:
75         if "FAIL" not in dev.request("GAS_RESPONSE_GET " + bssid + " 123456"):
76             raise Exception("Invalid dialog token accepted")
77         if "FAIL-Invalid range" not in dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 10000,10001"):
78             raise Exception("Invalid range accepted")
79         if "FAIL-Invalid range" not in dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 0,10000"):
80             raise Exception("Invalid range accepted")
81         if "FAIL" not in dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 0"):
82             raise Exception("Invalid GAS_RESPONSE_GET accepted")
83
84         res1_2 = dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 1,2")
85         res5_3 = dev.request("GAS_RESPONSE_GET " + bssid + " " + token + " 5,3")
86
87     resp = dev.request("GAS_RESPONSE_GET " + bssid + " " + token)
88     if "FAIL" in resp:
89         if allow_fetch_failure:
90             logger.debug("GAS response was not available anymore")
91             return
92         raise Exception("Could not fetch GAS response")
93     if len(resp) != int(resp_len) * 2:
94         raise Exception("Unexpected GAS response length")
95     logger.debug("GAS response: " + resp)
96     if extra_test:
97         if resp[2:6] != res1_2:
98             raise Exception("Unexpected response substring res1_2: " + res1_2)
99         if resp[10:16] != res5_3:
100             raise Exception("Unexpected response substring res5_3: " + res5_3)
101
102 def test_gas_generic(dev, apdev):
103     """Generic GAS query"""
104     bssid = apdev[0]['bssid']
105     params = hs20_ap_params()
106     params['hessid'] = bssid
107     hostapd.add_ap(apdev[0]['ifname'], params)
108
109     cmds = [ "foo",
110              "00:11:22:33:44:55",
111              "00:11:22:33:44:55 ",
112              "00:11:22:33:44:55  ",
113              "00:11:22:33:44:55 1",
114              "00:11:22:33:44:55 1 1234",
115              "00:11:22:33:44:55 qq",
116              "00:11:22:33:44:55 qq 1234",
117              "00:11:22:33:44:55 00      1",
118              "00:11:22:33:44:55 00 123",
119              "00:11:22:33:44:55 00 ",
120              "00:11:22:33:44:55 00 qq" ]
121     for cmd in cmds:
122         if "FAIL" not in dev[0].request("GAS_REQUEST " + cmd):
123             raise Exception("Invalid GAS_REQUEST accepted: " + cmd)
124
125     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
126     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101")
127     if "FAIL" in req:
128         raise Exception("GAS query request rejected")
129     ev = dev[0].wait_event(["GAS-RESPONSE-INFO"], timeout=10)
130     if ev is None:
131         raise Exception("GAS query timed out")
132     get_gas_response(dev[0], bssid, ev, extra_test=True)
133
134     if "FAIL" not in dev[0].request("GAS_RESPONSE_GET ff"):
135         raise Exception("Invalid GAS_RESPONSE_GET accepted")
136
137 def test_gas_concurrent_scan(dev, apdev):
138     """Generic GAS queries with concurrent scan operation"""
139     bssid = apdev[0]['bssid']
140     params = hs20_ap_params()
141     params['hessid'] = bssid
142     hostapd.add_ap(apdev[0]['ifname'], params)
143
144     # get BSS entry available to allow GAS query
145     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
146
147     logger.info("Request concurrent operations")
148     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101")
149     if "FAIL" in req:
150         raise Exception("GAS query request rejected")
151     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000801")
152     if "FAIL" in req:
153         raise Exception("GAS query request rejected")
154     dev[0].scan(no_wait=True)
155     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000201")
156     if "FAIL" in req:
157         raise Exception("GAS query request rejected")
158     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000501")
159     if "FAIL" in req:
160         raise Exception("GAS query request rejected")
161
162     responses = 0
163     for i in range(0, 5):
164         ev = dev[0].wait_event(["GAS-RESPONSE-INFO", "CTRL-EVENT-SCAN-RESULTS"],
165                                timeout=10)
166         if ev is None:
167             raise Exception("Operation timed out")
168         if "GAS-RESPONSE-INFO" in ev:
169             responses = responses + 1
170             get_gas_response(dev[0], bssid, ev, allow_fetch_failure=True)
171
172     if responses != 4:
173         raise Exception("Unexpected number of GAS responses")
174
175 def test_gas_concurrent_connect(dev, apdev):
176     """Generic GAS queries with concurrent connection operation"""
177     bssid = apdev[0]['bssid']
178     params = hs20_ap_params()
179     params['hessid'] = bssid
180     hostapd.add_ap(apdev[0]['ifname'], params)
181
182     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
183
184     logger.debug("Start concurrent connect and GAS request")
185     dev[0].connect("test-gas", key_mgmt="WPA-EAP", eap="TTLS",
186                    identity="DOMAIN\mschapv2 user", anonymous_identity="ttls",
187                    password="password", phase2="auth=MSCHAPV2",
188                    ca_cert="auth_serv/ca.pem", wait_connect=False,
189                    scan_freq="2412")
190     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101")
191     if "FAIL" in req:
192         raise Exception("GAS query request rejected")
193
194     ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED", "GAS-RESPONSE-INFO"],
195                            timeout=20)
196     if ev is None:
197         raise Exception("Operation timed out")
198     if "CTRL-EVENT-CONNECTED" not in ev:
199         raise Exception("Unexpected operation order")
200
201     ev = dev[0].wait_event(["CTRL-EVENT-CONNECTED", "GAS-RESPONSE-INFO"],
202                            timeout=20)
203     if ev is None:
204         raise Exception("Operation timed out")
205     if "GAS-RESPONSE-INFO" not in ev:
206         raise Exception("Unexpected operation order")
207     get_gas_response(dev[0], bssid, ev)
208
209     dev[0].request("DISCONNECT")
210     dev[0].wait_disconnected(timeout=5)
211
212     logger.debug("Wait six seconds for expiration of connect-without-scan")
213     time.sleep(6)
214     dev[0].dump_monitor()
215
216     logger.debug("Start concurrent GAS request and connect")
217     req = dev[0].request("GAS_REQUEST " + bssid + " 00 000102000101")
218     if "FAIL" in req:
219         raise Exception("GAS query request rejected")
220     dev[0].request("RECONNECT")
221
222     ev = dev[0].wait_event(["GAS-RESPONSE-INFO"], timeout=10)
223     if ev is None:
224         raise Exception("Operation timed out")
225     get_gas_response(dev[0], bssid, ev)
226
227     ev = dev[0].wait_event(["CTRL-EVENT-SCAN-RESULTS"], timeout=20)
228     if ev is None:
229         raise Exception("No new scan results reported")
230
231     ev = dev[0].wait_connected(timeout=20, error="Operation tiemd out")
232     if "CTRL-EVENT-CONNECTED" not in ev:
233         raise Exception("Unexpected operation order")
234
235 def test_gas_fragment(dev, apdev):
236     """GAS fragmentation"""
237     hapd = start_ap(apdev[0])
238     hapd.set("gas_frag_limit", "50")
239
240     dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412", force_scan=True)
241     dev[0].request("FETCH_ANQP")
242     for i in range(0, 13):
243         ev = dev[0].wait_event(["RX-ANQP", "RX-HS20-ANQP"], timeout=5)
244         if ev is None:
245             raise Exception("Operation timed out")
246
247 def test_gas_comeback_delay(dev, apdev):
248     """GAS fragmentation"""
249     hapd = start_ap(apdev[0])
250     hapd.set("gas_comeback_delay", "500")
251
252     dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412", force_scan=True)
253     dev[0].request("FETCH_ANQP")
254     for i in range(0, 6):
255         ev = dev[0].wait_event(["RX-ANQP"], timeout=5)
256         if ev is None:
257             raise Exception("Operation timed out")
258
259 def test_gas_stop_fetch_anqp(dev, apdev):
260     """Stop FETCH_ANQP operation"""
261     hapd = start_ap(apdev[0])
262
263     dev[0].scan_for_bss(apdev[0]['bssid'], freq="2412", force_scan=True)
264     hapd.set("ext_mgmt_frame_handling", "1")
265     dev[0].request("FETCH_ANQP")
266     dev[0].request("STOP_FETCH_ANQP")
267     hapd.set("ext_mgmt_frame_handling", "0")
268     ev = dev[0].wait_event(["RX-ANQP", "GAS-QUERY-DONE"], timeout=10)
269     if ev is None:
270         raise Exception("GAS-QUERY-DONE timed out")
271     if "RX-ANQP" in ev:
272         raise Exception("Unexpected ANQP response received")
273
274 def test_gas_anqp_get(dev, apdev):
275     """GAS/ANQP query for both IEEE 802.11 and Hotspot 2.0 elements"""
276     hapd = start_ap(apdev[0])
277     bssid = apdev[0]['bssid']
278
279     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
280     if "OK" not in dev[0].request("ANQP_GET " + bssid + " 258,268,hs20:3,hs20:4"):
281         raise Exception("ANQP_GET command failed")
282
283     ev = dev[0].wait_event(["GAS-QUERY-START"], timeout=5)
284     if ev is None:
285         raise Exception("GAS query start timed out")
286
287     ev = dev[0].wait_event(["GAS-QUERY-DONE"], timeout=10)
288     if ev is None:
289         raise Exception("GAS query timed out")
290
291     ev = dev[0].wait_event(["RX-ANQP"], timeout=1)
292     if ev is None or "Venue Name" not in ev:
293         raise Exception("Did not receive Venue Name")
294
295     ev = dev[0].wait_event(["RX-ANQP"], timeout=1)
296     if ev is None or "Domain Name list" not in ev:
297         raise Exception("Did not receive Domain Name list")
298
299     ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1)
300     if ev is None or "Operator Friendly Name" not in ev:
301         raise Exception("Did not receive Operator Friendly Name")
302
303     ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1)
304     if ev is None or "WAN Metrics" not in ev:
305         raise Exception("Did not receive WAN Metrics")
306
307     if "OK" not in dev[0].request("HS20_ANQP_GET " + bssid + " 3,4"):
308         raise Exception("ANQP_GET command failed")
309
310     ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1)
311     if ev is None or "Operator Friendly Name" not in ev:
312         raise Exception("Did not receive Operator Friendly Name")
313
314     ev = dev[0].wait_event(["RX-HS20-ANQP"], timeout=1)
315     if ev is None or "WAN Metrics" not in ev:
316         raise Exception("Did not receive WAN Metrics")
317
318     cmds = [ "",
319              "foo",
320              "00:11:22:33:44:55 258,hs20:-1",
321              "00:11:22:33:44:55 258,hs20:0",
322              "00:11:22:33:44:55 258,hs20:32",
323              "00:11:22:33:44:55 hs20:-1",
324              "00:11:22:33:44:55 hs20:0",
325              "00:11:22:33:44:55 hs20:32",
326              "00:11:22:33:44:55",
327              "00:11:22:33:44:55 ",
328              "00:11:22:33:44:55 0" ]
329     for cmd in cmds:
330         if "FAIL" not in dev[0].request("ANQP_GET " + cmd):
331             raise Exception("Invalid ANQP_GET accepted")
332
333     cmds = [ "",
334              "foo",
335              "00:11:22:33:44:55 -1",
336              "00:11:22:33:44:55 0",
337              "00:11:22:33:44:55 32",
338              "00:11:22:33:44:55",
339              "00:11:22:33:44:55 ",
340              "00:11:22:33:44:55 0" ]
341     for cmd in cmds:
342         if "FAIL" not in dev[0].request("HS20_ANQP_GET " + cmd):
343             raise Exception("Invalid HS20_ANQP_GET accepted")
344
345 def expect_gas_result(dev, result, status=None):
346     ev = dev.wait_event(["GAS-QUERY-DONE"], timeout=10)
347     if ev is None:
348         raise Exception("GAS query timed out")
349     if "result=" + result not in ev:
350         raise Exception("Unexpected GAS query result")
351     if status and "status_code=" + str(status) + ' ' not in ev:
352         raise Exception("Unexpected GAS status code")
353
354 def anqp_get(dev, bssid, id):
355     if "OK" not in dev.request("ANQP_GET " + bssid + " " + str(id)):
356         raise Exception("ANQP_GET command failed")
357     ev = dev.wait_event(["GAS-QUERY-START"], timeout=5)
358     if ev is None:
359         raise Exception("GAS query start timed out")
360
361 def test_gas_timeout(dev, apdev):
362     """GAS timeout"""
363     hapd = start_ap(apdev[0])
364     bssid = apdev[0]['bssid']
365
366     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
367     hapd.set("ext_mgmt_frame_handling", "1")
368
369     anqp_get(dev[0], bssid, 263)
370
371     ev = hapd.wait_event(["MGMT-RX"], timeout=5)
372     if ev is None:
373         raise Exception("MGMT RX wait timed out")
374
375     expect_gas_result(dev[0], "TIMEOUT")
376
377 MGMT_SUBTYPE_ACTION = 13
378 ACTION_CATEG_PUBLIC = 4
379
380 GAS_INITIAL_REQUEST = 10
381 GAS_INITIAL_RESPONSE = 11
382 GAS_COMEBACK_REQUEST = 12
383 GAS_COMEBACK_RESPONSE = 13
384 GAS_ACTIONS = [ GAS_INITIAL_REQUEST, GAS_INITIAL_RESPONSE,
385                 GAS_COMEBACK_REQUEST, GAS_COMEBACK_RESPONSE ]
386
387 def anqp_adv_proto():
388     return struct.pack('BBBB', 108, 2, 127, 0)
389
390 def anqp_initial_resp(dialog_token, status_code, comeback_delay=0):
391     return struct.pack('<BBBHH', ACTION_CATEG_PUBLIC, GAS_INITIAL_RESPONSE,
392                        dialog_token, status_code, comeback_delay) + anqp_adv_proto()
393
394 def anqp_comeback_resp(dialog_token, status_code=0, id=0, more=False, comeback_delay=0, bogus_adv_proto=False):
395     if more:
396         id |= 0x80
397     if bogus_adv_proto:
398         adv = struct.pack('BBBB', 108, 2, 127, 1)
399     else:
400         adv = anqp_adv_proto()
401     return struct.pack('<BBBHBH', ACTION_CATEG_PUBLIC, GAS_COMEBACK_RESPONSE,
402                        dialog_token, status_code, id, comeback_delay) + adv
403
404 def gas_rx(hapd):
405     count = 0
406     while count < 30:
407         count = count + 1
408         query = hapd.mgmt_rx()
409         if query is None:
410             raise Exception("Action frame not received")
411         if query['subtype'] != MGMT_SUBTYPE_ACTION:
412             continue
413         payload = query['payload']
414         if len(payload) < 2:
415             continue
416         (category, action) = struct.unpack('BB', payload[0:2])
417         if category != ACTION_CATEG_PUBLIC or action not in GAS_ACTIONS:
418             continue
419         return query
420     raise Exception("No Action frame received")
421
422 def parse_gas(payload):
423     pos = payload
424     (category, action, dialog_token) = struct.unpack('BBB', pos[0:3])
425     if category != ACTION_CATEG_PUBLIC:
426         return None
427     if action not in GAS_ACTIONS:
428         return None
429     gas = {}
430     gas['action'] = action
431     pos = pos[3:]
432
433     if len(pos) < 1 and action != GAS_COMEBACK_REQUEST:
434         return None
435
436     gas['dialog_token'] = dialog_token
437
438     if action == GAS_INITIAL_RESPONSE:
439         if len(pos) < 4:
440             return None
441         (status_code, comeback_delay) = struct.unpack('<HH', pos[0:4])
442         gas['status_code'] = status_code
443         gas['comeback_delay'] = comeback_delay
444
445     if action == GAS_COMEBACK_RESPONSE:
446         if len(pos) < 5:
447             return None
448         (status_code, frag, comeback_delay) = struct.unpack('<HBH', pos[0:5])
449         gas['status_code'] = status_code
450         gas['frag'] = frag
451         gas['comeback_delay'] = comeback_delay
452
453     return gas
454
455 def action_response(req):
456     resp = {}
457     resp['fc'] = req['fc']
458     resp['da'] = req['sa']
459     resp['sa'] = req['da']
460     resp['bssid'] = req['bssid']
461     return resp
462
463 def send_gas_resp(hapd, resp):
464     hapd.mgmt_tx(resp)
465     ev = hapd.wait_event(["MGMT-TX-STATUS"], timeout=5)
466     if ev is None:
467         raise Exception("Missing TX status for GAS response")
468     if "ok=1" not in ev:
469         raise Exception("GAS response not acknowledged")
470
471 def test_gas_invalid_response_type(dev, apdev):
472     """GAS invalid response type"""
473     hapd = start_ap(apdev[0])
474     bssid = apdev[0]['bssid']
475
476     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
477     hapd.set("ext_mgmt_frame_handling", "1")
478
479     anqp_get(dev[0], bssid, 263)
480
481     query = gas_rx(hapd)
482     gas = parse_gas(query['payload'])
483
484     resp = action_response(query)
485     # GAS Comeback Response instead of GAS Initial Response
486     resp['payload'] = anqp_comeback_resp(gas['dialog_token']) + struct.pack('<H', 0)
487     send_gas_resp(hapd, resp)
488
489     # station drops the invalid frame, so this needs to result in GAS timeout
490     expect_gas_result(dev[0], "TIMEOUT")
491
492 def test_gas_failure_status_code(dev, apdev):
493     """GAS failure status code"""
494     hapd = start_ap(apdev[0])
495     bssid = apdev[0]['bssid']
496
497     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
498     hapd.set("ext_mgmt_frame_handling", "1")
499
500     anqp_get(dev[0], bssid, 263)
501
502     query = gas_rx(hapd)
503     gas = parse_gas(query['payload'])
504
505     resp = action_response(query)
506     resp['payload'] = anqp_initial_resp(gas['dialog_token'], 61) + struct.pack('<H', 0)
507     send_gas_resp(hapd, resp)
508
509     expect_gas_result(dev[0], "FAILURE")
510
511 def test_gas_malformed(dev, apdev):
512     """GAS malformed response frames"""
513     hapd = start_ap(apdev[0])
514     bssid = apdev[0]['bssid']
515
516     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
517     hapd.set("ext_mgmt_frame_handling", "1")
518
519     anqp_get(dev[0], bssid, 263)
520
521     query = gas_rx(hapd)
522     gas = parse_gas(query['payload'])
523
524     resp = action_response(query)
525
526     resp['payload'] = struct.pack('<BBBH', ACTION_CATEG_PUBLIC,
527                                   GAS_COMEBACK_RESPONSE,
528                                   gas['dialog_token'], 0)
529     hapd.mgmt_tx(resp)
530
531     resp['payload'] = struct.pack('<BBBHB', ACTION_CATEG_PUBLIC,
532                                   GAS_COMEBACK_RESPONSE,
533                                   gas['dialog_token'], 0, 0)
534     hapd.mgmt_tx(resp)
535
536     hdr = struct.pack('<BBBHH', ACTION_CATEG_PUBLIC, GAS_INITIAL_RESPONSE,
537                       gas['dialog_token'], 0, 0)
538     resp['payload'] = hdr + struct.pack('B', 108)
539     hapd.mgmt_tx(resp)
540     resp['payload'] = hdr + struct.pack('BB', 108, 0)
541     hapd.mgmt_tx(resp)
542     resp['payload'] = hdr + struct.pack('BB', 108, 1)
543     hapd.mgmt_tx(resp)
544     resp['payload'] = hdr + struct.pack('BB', 108, 255)
545     hapd.mgmt_tx(resp)
546     resp['payload'] = hdr + struct.pack('BBB', 108, 1, 127)
547     hapd.mgmt_tx(resp)
548     resp['payload'] = hdr + struct.pack('BBB', 108, 2, 127)
549     hapd.mgmt_tx(resp)
550     resp['payload'] = hdr + struct.pack('BBBB', 0, 2, 127, 0)
551     hapd.mgmt_tx(resp)
552
553     resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<H', 1)
554     hapd.mgmt_tx(resp)
555
556     resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<HB', 2, 0)
557     hapd.mgmt_tx(resp)
558
559     resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<H', 65535)
560     hapd.mgmt_tx(resp)
561
562     resp['payload'] = anqp_initial_resp(gas['dialog_token'], 0) + struct.pack('<HBB', 1, 0, 0)
563     hapd.mgmt_tx(resp)
564
565     # Station drops invalid frames, but the last of the responses is valid from
566     # GAS view point even though it has an extra octet in the end and the ANQP
567     # part of the response is not valid. This is reported as successfulyl
568     # completed GAS exchange.
569     expect_gas_result(dev[0], "SUCCESS")
570
571 def init_gas(hapd, bssid, dev):
572     anqp_get(dev, bssid, 263)
573     query = gas_rx(hapd)
574     gas = parse_gas(query['payload'])
575     dialog_token = gas['dialog_token']
576
577     resp = action_response(query)
578     resp['payload'] = anqp_initial_resp(dialog_token, 0, comeback_delay=1) + struct.pack('<H', 0)
579     send_gas_resp(hapd, resp)
580
581     query = gas_rx(hapd)
582     gas = parse_gas(query['payload'])
583     if gas['action'] != GAS_COMEBACK_REQUEST:
584         raise Exception("Unexpected request action")
585     if gas['dialog_token'] != dialog_token:
586         raise Exception("Unexpected dialog token change")
587     return query, dialog_token
588
589 def test_gas_malformed_comeback_resp(dev, apdev):
590     """GAS malformed comeback response frames"""
591     hapd = start_ap(apdev[0])
592     bssid = apdev[0]['bssid']
593
594     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
595     hapd.set("ext_mgmt_frame_handling", "1")
596
597     logger.debug("Non-zero status code in comeback response")
598     query, dialog_token = init_gas(hapd, bssid, dev[0])
599     resp = action_response(query)
600     resp['payload'] = anqp_comeback_resp(dialog_token, status_code=2) + struct.pack('<H', 0)
601     send_gas_resp(hapd, resp)
602     expect_gas_result(dev[0], "FAILURE", status=2)
603
604     logger.debug("Different advertisement protocol in comeback response")
605     query, dialog_token = init_gas(hapd, bssid, dev[0])
606     resp = action_response(query)
607     resp['payload'] = anqp_comeback_resp(dialog_token, bogus_adv_proto=True) + struct.pack('<H', 0)
608     send_gas_resp(hapd, resp)
609     expect_gas_result(dev[0], "PEER_ERROR")
610
611     logger.debug("Non-zero frag id and comeback delay in comeback response")
612     query, dialog_token = init_gas(hapd, bssid, dev[0])
613     resp = action_response(query)
614     resp['payload'] = anqp_comeback_resp(dialog_token, id=1, comeback_delay=1) + struct.pack('<H', 0)
615     send_gas_resp(hapd, resp)
616     expect_gas_result(dev[0], "PEER_ERROR")
617
618     logger.debug("Unexpected frag id in comeback response")
619     query, dialog_token = init_gas(hapd, bssid, dev[0])
620     resp = action_response(query)
621     resp['payload'] = anqp_comeback_resp(dialog_token, id=1) + struct.pack('<H', 0)
622     send_gas_resp(hapd, resp)
623     expect_gas_result(dev[0], "PEER_ERROR")
624
625     logger.debug("Empty fragment and replay in comeback response")
626     query, dialog_token = init_gas(hapd, bssid, dev[0])
627     resp = action_response(query)
628     resp['payload'] = anqp_comeback_resp(dialog_token, more=True) + struct.pack('<H', 0)
629     send_gas_resp(hapd, resp)
630     query = gas_rx(hapd)
631     gas = parse_gas(query['payload'])
632     if gas['action'] != GAS_COMEBACK_REQUEST:
633         raise Exception("Unexpected request action")
634     if gas['dialog_token'] != dialog_token:
635         raise Exception("Unexpected dialog token change")
636     resp = action_response(query)
637     resp['payload'] = anqp_comeback_resp(dialog_token) + struct.pack('<H', 0)
638     send_gas_resp(hapd, resp)
639     resp['payload'] = anqp_comeback_resp(dialog_token, id=1) + struct.pack('<H', 0)
640     send_gas_resp(hapd, resp)
641     expect_gas_result(dev[0], "SUCCESS")
642
643     logger.debug("Unexpected initial response when waiting for comeback response")
644     query, dialog_token = init_gas(hapd, bssid, dev[0])
645     resp = action_response(query)
646     resp['payload'] = anqp_initial_resp(dialog_token, 0) + struct.pack('<H', 0)
647     send_gas_resp(hapd, resp)
648     ev = hapd.wait_event(["MGMT-RX"], timeout=1)
649     if ev is not None:
650         raise Exception("Unexpected management frame")
651     expect_gas_result(dev[0], "TIMEOUT")
652
653     logger.debug("Too short comeback response")
654     query, dialog_token = init_gas(hapd, bssid, dev[0])
655     resp = action_response(query)
656     resp['payload'] = struct.pack('<BBBH', ACTION_CATEG_PUBLIC,
657                                   GAS_COMEBACK_RESPONSE, dialog_token, 0)
658     send_gas_resp(hapd, resp)
659     ev = hapd.wait_event(["MGMT-RX"], timeout=1)
660     if ev is not None:
661         raise Exception("Unexpected management frame")
662     expect_gas_result(dev[0], "TIMEOUT")
663
664     logger.debug("Too short comeback response(2)")
665     query, dialog_token = init_gas(hapd, bssid, dev[0])
666     resp = action_response(query)
667     resp['payload'] = struct.pack('<BBBHBB', ACTION_CATEG_PUBLIC,
668                                   GAS_COMEBACK_RESPONSE, dialog_token, 0, 0x80,
669                                   0)
670     send_gas_resp(hapd, resp)
671     ev = hapd.wait_event(["MGMT-RX"], timeout=1)
672     if ev is not None:
673         raise Exception("Unexpected management frame")
674     expect_gas_result(dev[0], "TIMEOUT")
675
676     logger.debug("Maximum comeback response fragment claiming more fragments")
677     query, dialog_token = init_gas(hapd, bssid, dev[0])
678     resp = action_response(query)
679     resp['payload'] = anqp_comeback_resp(dialog_token, more=True) + struct.pack('<H', 0)
680     send_gas_resp(hapd, resp)
681     for i in range(1, 129):
682         query = gas_rx(hapd)
683         gas = parse_gas(query['payload'])
684         if gas['action'] != GAS_COMEBACK_REQUEST:
685             raise Exception("Unexpected request action")
686         if gas['dialog_token'] != dialog_token:
687             raise Exception("Unexpected dialog token change")
688         resp = action_response(query)
689         resp['payload'] = anqp_comeback_resp(dialog_token, id=i, more=True) + struct.pack('<H', 0)
690         send_gas_resp(hapd, resp)
691     expect_gas_result(dev[0], "PEER_ERROR")
692
693 def test_gas_comeback_resp_additional_delay(dev, apdev):
694     """GAS comeback response requesting additional delay"""
695     hapd = start_ap(apdev[0])
696     bssid = apdev[0]['bssid']
697
698     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
699     hapd.set("ext_mgmt_frame_handling", "1")
700
701     query, dialog_token = init_gas(hapd, bssid, dev[0])
702     for i in range(0, 2):
703         resp = action_response(query)
704         resp['payload'] = anqp_comeback_resp(dialog_token, status_code=95, comeback_delay=50) + struct.pack('<H', 0)
705         send_gas_resp(hapd, resp)
706         query = gas_rx(hapd)
707         gas = parse_gas(query['payload'])
708         if gas['action'] != GAS_COMEBACK_REQUEST:
709             raise Exception("Unexpected request action")
710         if gas['dialog_token'] != dialog_token:
711             raise Exception("Unexpected dialog token change")
712     resp = action_response(query)
713     resp['payload'] = anqp_comeback_resp(dialog_token, status_code=0) + struct.pack('<H', 0)
714     send_gas_resp(hapd, resp)
715     expect_gas_result(dev[0], "SUCCESS")
716
717 def test_gas_unknown_adv_proto(dev, apdev):
718     """Unknown advertisement protocol id"""
719     bssid = apdev[0]['bssid']
720     params = hs20_ap_params()
721     params['hessid'] = bssid
722     hostapd.add_ap(apdev[0]['ifname'], params)
723
724     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
725     req = dev[0].request("GAS_REQUEST " + bssid + " 42 000102000101")
726     if "FAIL" in req:
727         raise Exception("GAS query request rejected")
728     expect_gas_result(dev[0], "FAILURE", "59")
729     ev = dev[0].wait_event(["GAS-RESPONSE-INFO"], timeout=10)
730     if ev is None:
731         raise Exception("GAS query timed out")
732     exp = r'<.>(GAS-RESPONSE-INFO) addr=([0-9a-f:]*) dialog_token=([0-9]*) status_code=([0-9]*) resp_len=([\-0-9]*)'
733     res = re.split(exp, ev)
734     if len(res) < 6:
735         raise Exception("Could not parse GAS-RESPONSE-INFO")
736     if res[2] != bssid:
737         raise Exception("Unexpected BSSID in response")
738     status = res[4]
739     if status != "59":
740         raise Exception("Unexpected GAS-RESPONSE-INFO status")
741
742 def test_gas_max_pending(dev, apdev):
743     """GAS and maximum pending query limit"""
744     hapd = start_ap(apdev[0])
745     hapd.set("gas_frag_limit", "50")
746     bssid = apdev[0]['bssid']
747
748     wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
749     wpas.interface_add("wlan5")
750     if "OK" not in wpas.request("P2P_SET listen_channel 1"):
751         raise Exception("Failed to set listen channel")
752     if "OK" not in wpas.p2p_listen():
753         raise Exception("Failed to start listen state")
754     if "FAIL" in wpas.request("SET ext_mgmt_frame_handling 1"):
755         raise Exception("Failed to enable external management frame handling")
756
757     anqp_query = struct.pack('<HHHHHHHHHH', 256, 16, 257, 258, 260, 261, 262, 263, 264, 268)
758     gas = struct.pack('<H', len(anqp_query)) + anqp_query
759
760     for dialog_token in range(1, 10):
761         msg = struct.pack('<BBB', ACTION_CATEG_PUBLIC, GAS_INITIAL_REQUEST,
762                           dialog_token) + anqp_adv_proto() + gas
763         req = "MGMT_TX {} {} freq=2412 wait_time=10 action={}".format(bssid, bssid, binascii.hexlify(msg))
764         if "OK" not in wpas.request(req):
765             raise Exception("Could not send management frame")
766         resp = wpas.mgmt_rx()
767         if resp is None:
768             raise Exception("MGMT-RX timeout")
769         if 'payload' not in resp:
770             raise Exception("Missing payload")
771         gresp = parse_gas(resp['payload'])
772         if gresp['dialog_token'] != dialog_token:
773             raise Exception("Dialog token mismatch")
774         status_code = gresp['status_code']
775         if dialog_token < 9 and status_code != 0:
776             raise Exception("Unexpected failure status code {} for dialog token {}".format(status_code, dialog_token))
777         if dialog_token > 8 and status_code == 0:
778             raise Exception("Unexpected success status code {} for dialog token {}".format(status_code, dialog_token))
779
780 def test_gas_no_pending(dev, apdev):
781     """GAS and no pending query for comeback request"""
782     hapd = start_ap(apdev[0])
783     bssid = apdev[0]['bssid']
784
785     wpas = WpaSupplicant(global_iface='/tmp/wpas-wlan5')
786     wpas.interface_add("wlan5")
787     if "OK" not in wpas.request("P2P_SET listen_channel 1"):
788         raise Exception("Failed to set listen channel")
789     if "OK" not in wpas.p2p_listen():
790         raise Exception("Failed to start listen state")
791     if "FAIL" in wpas.request("SET ext_mgmt_frame_handling 1"):
792         raise Exception("Failed to enable external management frame handling")
793
794     msg = struct.pack('<BBB', ACTION_CATEG_PUBLIC, GAS_COMEBACK_REQUEST, 1)
795     req = "MGMT_TX {} {} freq=2412 wait_time=10 action={}".format(bssid, bssid, binascii.hexlify(msg))
796     if "OK" not in wpas.request(req):
797         raise Exception("Could not send management frame")
798     resp = wpas.mgmt_rx()
799     if resp is None:
800         raise Exception("MGMT-RX timeout")
801     if 'payload' not in resp:
802         raise Exception("Missing payload")
803     gresp = parse_gas(resp['payload'])
804     status_code = gresp['status_code']
805     if status_code != 60:
806         raise Exception("Unexpected status code {} (expected 60)".format(status_code))
807
808 def test_gas_missing_payload(dev, apdev):
809     """No action code in the query frame"""
810     bssid = apdev[0]['bssid']
811     params = hs20_ap_params()
812     params['hessid'] = bssid
813     hostapd.add_ap(apdev[0]['ifname'], params)
814
815     dev[0].scan_for_bss(bssid, freq="2412", force_scan=True)
816
817     cmd = "MGMT_TX {} {} freq=2412 action=040A".format(bssid, bssid)
818     if "FAIL" in dev[0].request(cmd):
819         raise Exception("Could not send test Action frame")
820     ev = dev[0].wait_event(["MGMT-TX-STATUS"], timeout=10)
821     if ev is None:
822         raise Exception("Timeout on MGMT-TX-STATUS")
823     if "result=SUCCESS" not in ev:
824         raise Exception("AP did not ack Action frame")
825
826     cmd = "MGMT_TX {} {} freq=2412 action=04".format(bssid, bssid)
827     if "FAIL" in dev[0].request(cmd):
828         raise Exception("Could not send test Action frame")
829     ev = dev[0].wait_event(["MGMT-TX-STATUS"], timeout=10)
830     if ev is None:
831         raise Exception("Timeout on MGMT-TX-STATUS")
832     if "result=SUCCESS" not in ev:
833         raise Exception("AP did not ack Action frame")