1252190Srpaulo/*
2252190Srpaulo * Generic advertisement service (GAS) server
3281806Srpaulo * Copyright (c) 2011-2014, Qualcomm Atheros, Inc.
4252190Srpaulo *
5252190Srpaulo * This software may be distributed under the terms of the BSD license.
6252190Srpaulo * See README for more details.
7252190Srpaulo */
8252190Srpaulo
9252190Srpaulo#include "includes.h"
10252190Srpaulo
11252190Srpaulo#include "common.h"
12252190Srpaulo#include "common/ieee802_11_defs.h"
13252190Srpaulo#include "common/gas.h"
14346981Scy#include "common/wpa_ctrl.h"
15252190Srpaulo#include "utils/eloop.h"
16252190Srpaulo#include "hostapd.h"
17252190Srpaulo#include "ap_config.h"
18252190Srpaulo#include "ap_drv_ops.h"
19346981Scy#include "dpp_hostapd.h"
20252190Srpaulo#include "sta_info.h"
21252190Srpaulo#include "gas_serv.h"
22252190Srpaulo
23252190Srpaulo
24346981Scy#ifdef CONFIG_DPP
25346981Scystatic void gas_serv_write_dpp_adv_proto(struct wpabuf *buf)
26346981Scy{
27346981Scy	wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO);
28346981Scy	wpabuf_put_u8(buf, 8); /* Length */
29346981Scy	wpabuf_put_u8(buf, 0x7f);
30346981Scy	wpabuf_put_u8(buf, WLAN_EID_VENDOR_SPECIFIC);
31346981Scy	wpabuf_put_u8(buf, 5);
32346981Scy	wpabuf_put_be24(buf, OUI_WFA);
33346981Scy	wpabuf_put_u8(buf, DPP_OUI_TYPE);
34346981Scy	wpabuf_put_u8(buf, 0x01);
35346981Scy}
36346981Scy#endif /* CONFIG_DPP */
37346981Scy
38346981Scy
39281806Srpaulostatic void convert_to_protected_dual(struct wpabuf *msg)
40281806Srpaulo{
41281806Srpaulo	u8 *categ = wpabuf_mhead_u8(msg);
42281806Srpaulo	*categ = WLAN_ACTION_PROTECTED_DUAL;
43281806Srpaulo}
44281806Srpaulo
45281806Srpaulo
46252190Srpaulostatic struct gas_dialog_info *
47252190Srpaulogas_dialog_create(struct hostapd_data *hapd, const u8 *addr, u8 dialog_token)
48252190Srpaulo{
49252190Srpaulo	struct sta_info *sta;
50252190Srpaulo	struct gas_dialog_info *dia = NULL;
51252190Srpaulo	int i, j;
52252190Srpaulo
53252190Srpaulo	sta = ap_get_sta(hapd, addr);
54252190Srpaulo	if (!sta) {
55252190Srpaulo		/*
56252190Srpaulo		 * We need a STA entry to be able to maintain state for
57252190Srpaulo		 * the GAS query.
58252190Srpaulo		 */
59252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Add a temporary STA entry for "
60252190Srpaulo			   "GAS query");
61252190Srpaulo		sta = ap_sta_add(hapd, addr);
62252190Srpaulo		if (!sta) {
63252190Srpaulo			wpa_printf(MSG_DEBUG, "Failed to add STA " MACSTR
64252190Srpaulo				   " for GAS query", MAC2STR(addr));
65252190Srpaulo			return NULL;
66252190Srpaulo		}
67252190Srpaulo		sta->flags |= WLAN_STA_GAS;
68252190Srpaulo		/*
69252190Srpaulo		 * The default inactivity is 300 seconds. We don't need
70346981Scy		 * it to be that long. Use five second timeout and increase this
71346981Scy		 * with the comeback_delay for testing cases.
72252190Srpaulo		 */
73346981Scy		ap_sta_session_timeout(hapd, sta,
74346981Scy				       hapd->conf->gas_comeback_delay / 1024 +
75346981Scy				       5);
76281806Srpaulo	} else {
77281806Srpaulo		ap_sta_replenish_timeout(hapd, sta, 5);
78252190Srpaulo	}
79252190Srpaulo
80252190Srpaulo	if (sta->gas_dialog == NULL) {
81281806Srpaulo		sta->gas_dialog = os_calloc(GAS_DIALOG_MAX,
82252190Srpaulo					    sizeof(struct gas_dialog_info));
83252190Srpaulo		if (sta->gas_dialog == NULL)
84252190Srpaulo			return NULL;
85252190Srpaulo	}
86252190Srpaulo
87252190Srpaulo	for (i = sta->gas_dialog_next, j = 0; j < GAS_DIALOG_MAX; i++, j++) {
88252190Srpaulo		if (i == GAS_DIALOG_MAX)
89252190Srpaulo			i = 0;
90252190Srpaulo		if (sta->gas_dialog[i].valid)
91252190Srpaulo			continue;
92252190Srpaulo		dia = &sta->gas_dialog[i];
93252190Srpaulo		dia->valid = 1;
94252190Srpaulo		dia->dialog_token = dialog_token;
95252190Srpaulo		sta->gas_dialog_next = (++i == GAS_DIALOG_MAX) ? 0 : i;
96252190Srpaulo		return dia;
97252190Srpaulo	}
98252190Srpaulo
99252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_ERROR, "ANQP: Could not create dialog for "
100252190Srpaulo		MACSTR " dialog_token %u. Consider increasing "
101252190Srpaulo		"GAS_DIALOG_MAX.", MAC2STR(addr), dialog_token);
102252190Srpaulo
103252190Srpaulo	return NULL;
104252190Srpaulo}
105252190Srpaulo
106252190Srpaulo
107252190Srpaulostruct gas_dialog_info *
108252190Srpaulogas_serv_dialog_find(struct hostapd_data *hapd, const u8 *addr,
109252190Srpaulo		     u8 dialog_token)
110252190Srpaulo{
111252190Srpaulo	struct sta_info *sta;
112252190Srpaulo	int i;
113252190Srpaulo
114252190Srpaulo	sta = ap_get_sta(hapd, addr);
115252190Srpaulo	if (!sta) {
116252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: could not find STA " MACSTR,
117252190Srpaulo			   MAC2STR(addr));
118252190Srpaulo		return NULL;
119252190Srpaulo	}
120252190Srpaulo	for (i = 0; sta->gas_dialog && i < GAS_DIALOG_MAX; i++) {
121252190Srpaulo		if (sta->gas_dialog[i].dialog_token != dialog_token ||
122252190Srpaulo		    !sta->gas_dialog[i].valid)
123252190Srpaulo			continue;
124337817Scy		ap_sta_replenish_timeout(hapd, sta, 5);
125252190Srpaulo		return &sta->gas_dialog[i];
126252190Srpaulo	}
127252190Srpaulo	wpa_printf(MSG_DEBUG, "ANQP: Could not find dialog for "
128252190Srpaulo		   MACSTR " dialog_token %u", MAC2STR(addr), dialog_token);
129252190Srpaulo	return NULL;
130252190Srpaulo}
131252190Srpaulo
132252190Srpaulo
133252190Srpaulovoid gas_serv_dialog_clear(struct gas_dialog_info *dia)
134252190Srpaulo{
135252190Srpaulo	wpabuf_free(dia->sd_resp);
136252190Srpaulo	os_memset(dia, 0, sizeof(*dia));
137252190Srpaulo}
138252190Srpaulo
139252190Srpaulo
140252190Srpaulostatic void gas_serv_free_dialogs(struct hostapd_data *hapd,
141252190Srpaulo				  const u8 *sta_addr)
142252190Srpaulo{
143252190Srpaulo	struct sta_info *sta;
144252190Srpaulo	int i;
145252190Srpaulo
146252190Srpaulo	sta = ap_get_sta(hapd, sta_addr);
147252190Srpaulo	if (sta == NULL || sta->gas_dialog == NULL)
148252190Srpaulo		return;
149252190Srpaulo
150252190Srpaulo	for (i = 0; i < GAS_DIALOG_MAX; i++) {
151252190Srpaulo		if (sta->gas_dialog[i].valid)
152252190Srpaulo			return;
153252190Srpaulo	}
154252190Srpaulo
155252190Srpaulo	os_free(sta->gas_dialog);
156252190Srpaulo	sta->gas_dialog = NULL;
157252190Srpaulo}
158252190Srpaulo
159252190Srpaulo
160252190Srpaulo#ifdef CONFIG_HS20
161252190Srpaulostatic void anqp_add_hs_capab_list(struct hostapd_data *hapd,
162252190Srpaulo				   struct wpabuf *buf)
163252190Srpaulo{
164252190Srpaulo	u8 *len;
165252190Srpaulo
166252190Srpaulo	len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
167252190Srpaulo	wpabuf_put_be24(buf, OUI_WFA);
168252190Srpaulo	wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
169252190Srpaulo	wpabuf_put_u8(buf, HS20_STYPE_CAPABILITY_LIST);
170252190Srpaulo	wpabuf_put_u8(buf, 0); /* Reserved */
171252190Srpaulo	wpabuf_put_u8(buf, HS20_STYPE_CAPABILITY_LIST);
172252190Srpaulo	if (hapd->conf->hs20_oper_friendly_name)
173252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_FRIENDLY_NAME);
174252190Srpaulo	if (hapd->conf->hs20_wan_metrics)
175252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_WAN_METRICS);
176252190Srpaulo	if (hapd->conf->hs20_connection_capability)
177252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_CONNECTION_CAPABILITY);
178252190Srpaulo	if (hapd->conf->nai_realm_data)
179252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_NAI_HOME_REALM_QUERY);
180252190Srpaulo	if (hapd->conf->hs20_operating_class)
181252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS);
182281806Srpaulo	if (hapd->conf->hs20_osu_providers_count)
183281806Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_LIST);
184346981Scy	if (hapd->conf->hs20_osu_providers_nai_count)
185346981Scy		wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_NAI_LIST);
186281806Srpaulo	if (hapd->conf->hs20_icons_count)
187281806Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_ICON_REQUEST);
188346981Scy	if (hapd->conf->hs20_operator_icon_count)
189346981Scy		wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_ICON_METADATA);
190252190Srpaulo	gas_anqp_set_element_len(buf, len);
191252190Srpaulo}
192252190Srpaulo#endif /* CONFIG_HS20 */
193252190Srpaulo
194252190Srpaulo
195337817Scystatic struct anqp_element * get_anqp_elem(struct hostapd_data *hapd,
196337817Scy					   u16 infoid)
197337817Scy{
198337817Scy	struct anqp_element *elem;
199337817Scy
200337817Scy	dl_list_for_each(elem, &hapd->conf->anqp_elem, struct anqp_element,
201337817Scy			 list) {
202337817Scy		if (elem->infoid == infoid)
203337817Scy			return elem;
204337817Scy	}
205337817Scy
206337817Scy	return NULL;
207337817Scy}
208337817Scy
209337817Scy
210337817Scystatic void anqp_add_elem(struct hostapd_data *hapd, struct wpabuf *buf,
211337817Scy			  u16 infoid)
212337817Scy{
213337817Scy	struct anqp_element *elem;
214337817Scy
215337817Scy	elem = get_anqp_elem(hapd, infoid);
216337817Scy	if (!elem)
217337817Scy		return;
218337817Scy	if (wpabuf_tailroom(buf) < 2 + 2 + wpabuf_len(elem->payload)) {
219337817Scy		wpa_printf(MSG_DEBUG, "ANQP: No room for InfoID %u payload",
220337817Scy			   infoid);
221337817Scy		return;
222337817Scy	}
223337817Scy
224337817Scy	wpabuf_put_le16(buf, infoid);
225337817Scy	wpabuf_put_le16(buf, wpabuf_len(elem->payload));
226337817Scy	wpabuf_put_buf(buf, elem->payload);
227337817Scy}
228337817Scy
229337817Scy
230337817Scystatic int anqp_add_override(struct hostapd_data *hapd, struct wpabuf *buf,
231337817Scy			     u16 infoid)
232337817Scy{
233337817Scy	if (get_anqp_elem(hapd, infoid)) {
234337817Scy		anqp_add_elem(hapd, buf, infoid);
235337817Scy		return 1;
236337817Scy	}
237337817Scy
238337817Scy	return 0;
239337817Scy}
240337817Scy
241337817Scy
242252190Srpaulostatic void anqp_add_capab_list(struct hostapd_data *hapd,
243252190Srpaulo				struct wpabuf *buf)
244252190Srpaulo{
245252190Srpaulo	u8 *len;
246337817Scy	u16 id;
247252190Srpaulo
248337817Scy	if (anqp_add_override(hapd, buf, ANQP_CAPABILITY_LIST))
249337817Scy		return;
250337817Scy
251252190Srpaulo	len = gas_anqp_add_element(buf, ANQP_CAPABILITY_LIST);
252252190Srpaulo	wpabuf_put_le16(buf, ANQP_CAPABILITY_LIST);
253337817Scy	if (hapd->conf->venue_name || get_anqp_elem(hapd, ANQP_VENUE_NAME))
254252190Srpaulo		wpabuf_put_le16(buf, ANQP_VENUE_NAME);
255337817Scy	if (get_anqp_elem(hapd, ANQP_EMERGENCY_CALL_NUMBER))
256337817Scy		wpabuf_put_le16(buf, ANQP_EMERGENCY_CALL_NUMBER);
257337817Scy	if (hapd->conf->network_auth_type ||
258337817Scy	    get_anqp_elem(hapd, ANQP_NETWORK_AUTH_TYPE))
259252190Srpaulo		wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE);
260337817Scy	if (hapd->conf->roaming_consortium ||
261337817Scy	    get_anqp_elem(hapd, ANQP_ROAMING_CONSORTIUM))
262252190Srpaulo		wpabuf_put_le16(buf, ANQP_ROAMING_CONSORTIUM);
263337817Scy	if (hapd->conf->ipaddr_type_configured ||
264337817Scy	    get_anqp_elem(hapd, ANQP_IP_ADDR_TYPE_AVAILABILITY))
265252190Srpaulo		wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY);
266337817Scy	if (hapd->conf->nai_realm_data ||
267337817Scy	    get_anqp_elem(hapd, ANQP_NAI_REALM))
268252190Srpaulo		wpabuf_put_le16(buf, ANQP_NAI_REALM);
269337817Scy	if (hapd->conf->anqp_3gpp_cell_net ||
270337817Scy	    get_anqp_elem(hapd, ANQP_3GPP_CELLULAR_NETWORK))
271252190Srpaulo		wpabuf_put_le16(buf, ANQP_3GPP_CELLULAR_NETWORK);
272337817Scy	if (get_anqp_elem(hapd, ANQP_AP_GEOSPATIAL_LOCATION))
273337817Scy		wpabuf_put_le16(buf, ANQP_AP_GEOSPATIAL_LOCATION);
274337817Scy	if (get_anqp_elem(hapd, ANQP_AP_CIVIC_LOCATION))
275337817Scy		wpabuf_put_le16(buf, ANQP_AP_CIVIC_LOCATION);
276337817Scy	if (get_anqp_elem(hapd, ANQP_AP_LOCATION_PUBLIC_URI))
277337817Scy		wpabuf_put_le16(buf, ANQP_AP_LOCATION_PUBLIC_URI);
278337817Scy	if (hapd->conf->domain_name || get_anqp_elem(hapd, ANQP_DOMAIN_NAME))
279252190Srpaulo		wpabuf_put_le16(buf, ANQP_DOMAIN_NAME);
280337817Scy	if (get_anqp_elem(hapd, ANQP_EMERGENCY_ALERT_URI))
281337817Scy		wpabuf_put_le16(buf, ANQP_EMERGENCY_ALERT_URI);
282346981Scy	if (get_anqp_elem(hapd, ANQP_TDLS_CAPABILITY))
283346981Scy		wpabuf_put_le16(buf, ANQP_TDLS_CAPABILITY);
284337817Scy	if (get_anqp_elem(hapd, ANQP_EMERGENCY_NAI))
285337817Scy		wpabuf_put_le16(buf, ANQP_EMERGENCY_NAI);
286337817Scy	if (get_anqp_elem(hapd, ANQP_NEIGHBOR_REPORT))
287337817Scy		wpabuf_put_le16(buf, ANQP_NEIGHBOR_REPORT);
288346981Scy#ifdef CONFIG_FILS
289346981Scy	if (!dl_list_empty(&hapd->conf->fils_realms) ||
290346981Scy	    get_anqp_elem(hapd, ANQP_FILS_REALM_INFO))
291346981Scy		wpabuf_put_le16(buf, ANQP_FILS_REALM_INFO);
292346981Scy#endif /* CONFIG_FILS */
293346981Scy	if (get_anqp_elem(hapd, ANQP_CAG))
294346981Scy		wpabuf_put_le16(buf, ANQP_CAG);
295346981Scy	if (hapd->conf->venue_url || get_anqp_elem(hapd, ANQP_VENUE_URL))
296337817Scy		wpabuf_put_le16(buf, ANQP_VENUE_URL);
297337817Scy	if (get_anqp_elem(hapd, ANQP_ADVICE_OF_CHARGE))
298337817Scy		wpabuf_put_le16(buf, ANQP_ADVICE_OF_CHARGE);
299337817Scy	if (get_anqp_elem(hapd, ANQP_LOCAL_CONTENT))
300337817Scy		wpabuf_put_le16(buf, ANQP_LOCAL_CONTENT);
301346981Scy	for (id = 280; id < 300; id++) {
302346981Scy		if (get_anqp_elem(hapd, id))
303346981Scy			wpabuf_put_le16(buf, id);
304346981Scy	}
305252190Srpaulo#ifdef CONFIG_HS20
306252190Srpaulo	anqp_add_hs_capab_list(hapd, buf);
307252190Srpaulo#endif /* CONFIG_HS20 */
308252190Srpaulo	gas_anqp_set_element_len(buf, len);
309252190Srpaulo}
310252190Srpaulo
311252190Srpaulo
312252190Srpaulostatic void anqp_add_venue_name(struct hostapd_data *hapd, struct wpabuf *buf)
313252190Srpaulo{
314337817Scy	if (anqp_add_override(hapd, buf, ANQP_VENUE_NAME))
315337817Scy		return;
316337817Scy
317252190Srpaulo	if (hapd->conf->venue_name) {
318252190Srpaulo		u8 *len;
319252190Srpaulo		unsigned int i;
320252190Srpaulo		len = gas_anqp_add_element(buf, ANQP_VENUE_NAME);
321252190Srpaulo		wpabuf_put_u8(buf, hapd->conf->venue_group);
322252190Srpaulo		wpabuf_put_u8(buf, hapd->conf->venue_type);
323252190Srpaulo		for (i = 0; i < hapd->conf->venue_name_count; i++) {
324252190Srpaulo			struct hostapd_lang_string *vn;
325252190Srpaulo			vn = &hapd->conf->venue_name[i];
326252190Srpaulo			wpabuf_put_u8(buf, 3 + vn->name_len);
327252190Srpaulo			wpabuf_put_data(buf, vn->lang, 3);
328252190Srpaulo			wpabuf_put_data(buf, vn->name, vn->name_len);
329252190Srpaulo		}
330252190Srpaulo		gas_anqp_set_element_len(buf, len);
331252190Srpaulo	}
332252190Srpaulo}
333252190Srpaulo
334252190Srpaulo
335346981Scystatic void anqp_add_venue_url(struct hostapd_data *hapd, struct wpabuf *buf)
336346981Scy{
337346981Scy	if (anqp_add_override(hapd, buf, ANQP_VENUE_URL))
338346981Scy		return;
339346981Scy
340346981Scy	if (hapd->conf->venue_url) {
341346981Scy		u8 *len;
342346981Scy		unsigned int i;
343346981Scy
344346981Scy		len = gas_anqp_add_element(buf, ANQP_VENUE_URL);
345346981Scy		for (i = 0; i < hapd->conf->venue_url_count; i++) {
346346981Scy			struct hostapd_venue_url *url;
347346981Scy
348346981Scy			url = &hapd->conf->venue_url[i];
349346981Scy			wpabuf_put_u8(buf, 1 + url->url_len);
350346981Scy			wpabuf_put_u8(buf, url->venue_number);
351346981Scy			wpabuf_put_data(buf, url->url, url->url_len);
352346981Scy		}
353346981Scy		gas_anqp_set_element_len(buf, len);
354346981Scy	}
355346981Scy}
356346981Scy
357346981Scy
358252190Srpaulostatic void anqp_add_network_auth_type(struct hostapd_data *hapd,
359252190Srpaulo				       struct wpabuf *buf)
360252190Srpaulo{
361337817Scy	if (anqp_add_override(hapd, buf, ANQP_NETWORK_AUTH_TYPE))
362337817Scy		return;
363337817Scy
364252190Srpaulo	if (hapd->conf->network_auth_type) {
365252190Srpaulo		wpabuf_put_le16(buf, ANQP_NETWORK_AUTH_TYPE);
366252190Srpaulo		wpabuf_put_le16(buf, hapd->conf->network_auth_type_len);
367252190Srpaulo		wpabuf_put_data(buf, hapd->conf->network_auth_type,
368252190Srpaulo				hapd->conf->network_auth_type_len);
369252190Srpaulo	}
370252190Srpaulo}
371252190Srpaulo
372252190Srpaulo
373252190Srpaulostatic void anqp_add_roaming_consortium(struct hostapd_data *hapd,
374252190Srpaulo					struct wpabuf *buf)
375252190Srpaulo{
376252190Srpaulo	unsigned int i;
377252190Srpaulo	u8 *len;
378252190Srpaulo
379337817Scy	if (anqp_add_override(hapd, buf, ANQP_ROAMING_CONSORTIUM))
380337817Scy		return;
381337817Scy
382252190Srpaulo	len = gas_anqp_add_element(buf, ANQP_ROAMING_CONSORTIUM);
383252190Srpaulo	for (i = 0; i < hapd->conf->roaming_consortium_count; i++) {
384252190Srpaulo		struct hostapd_roaming_consortium *rc;
385252190Srpaulo		rc = &hapd->conf->roaming_consortium[i];
386252190Srpaulo		wpabuf_put_u8(buf, rc->len);
387252190Srpaulo		wpabuf_put_data(buf, rc->oi, rc->len);
388252190Srpaulo	}
389252190Srpaulo	gas_anqp_set_element_len(buf, len);
390252190Srpaulo}
391252190Srpaulo
392252190Srpaulo
393252190Srpaulostatic void anqp_add_ip_addr_type_availability(struct hostapd_data *hapd,
394252190Srpaulo					       struct wpabuf *buf)
395252190Srpaulo{
396337817Scy	if (anqp_add_override(hapd, buf, ANQP_IP_ADDR_TYPE_AVAILABILITY))
397337817Scy		return;
398337817Scy
399252190Srpaulo	if (hapd->conf->ipaddr_type_configured) {
400252190Srpaulo		wpabuf_put_le16(buf, ANQP_IP_ADDR_TYPE_AVAILABILITY);
401252190Srpaulo		wpabuf_put_le16(buf, 1);
402252190Srpaulo		wpabuf_put_u8(buf, hapd->conf->ipaddr_type_availability);
403252190Srpaulo	}
404252190Srpaulo}
405252190Srpaulo
406252190Srpaulo
407252190Srpaulostatic void anqp_add_nai_realm_eap(struct wpabuf *buf,
408252190Srpaulo				   struct hostapd_nai_realm_data *realm)
409252190Srpaulo{
410252190Srpaulo	unsigned int i, j;
411252190Srpaulo
412252190Srpaulo	wpabuf_put_u8(buf, realm->eap_method_count);
413252190Srpaulo
414252190Srpaulo	for (i = 0; i < realm->eap_method_count; i++) {
415252190Srpaulo		struct hostapd_nai_realm_eap *eap = &realm->eap_method[i];
416252190Srpaulo		wpabuf_put_u8(buf, 2 + (3 * eap->num_auths));
417252190Srpaulo		wpabuf_put_u8(buf, eap->eap_method);
418252190Srpaulo		wpabuf_put_u8(buf, eap->num_auths);
419252190Srpaulo		for (j = 0; j < eap->num_auths; j++) {
420252190Srpaulo			wpabuf_put_u8(buf, eap->auth_id[j]);
421252190Srpaulo			wpabuf_put_u8(buf, 1);
422252190Srpaulo			wpabuf_put_u8(buf, eap->auth_val[j]);
423252190Srpaulo		}
424252190Srpaulo	}
425252190Srpaulo}
426252190Srpaulo
427252190Srpaulo
428252190Srpaulostatic void anqp_add_nai_realm_data(struct wpabuf *buf,
429252190Srpaulo				    struct hostapd_nai_realm_data *realm,
430252190Srpaulo				    unsigned int realm_idx)
431252190Srpaulo{
432252190Srpaulo	u8 *realm_data_len;
433252190Srpaulo
434252190Srpaulo	wpa_printf(MSG_DEBUG, "realm=%s, len=%d", realm->realm[realm_idx],
435252190Srpaulo		   (int) os_strlen(realm->realm[realm_idx]));
436252190Srpaulo	realm_data_len = wpabuf_put(buf, 2);
437252190Srpaulo	wpabuf_put_u8(buf, realm->encoding);
438252190Srpaulo	wpabuf_put_u8(buf, os_strlen(realm->realm[realm_idx]));
439252190Srpaulo	wpabuf_put_str(buf, realm->realm[realm_idx]);
440252190Srpaulo	anqp_add_nai_realm_eap(buf, realm);
441252190Srpaulo	gas_anqp_set_element_len(buf, realm_data_len);
442252190Srpaulo}
443252190Srpaulo
444252190Srpaulo
445252190Srpaulostatic int hs20_add_nai_home_realm_matches(struct hostapd_data *hapd,
446252190Srpaulo					   struct wpabuf *buf,
447252190Srpaulo					   const u8 *home_realm,
448252190Srpaulo					   size_t home_realm_len)
449252190Srpaulo{
450252190Srpaulo	unsigned int i, j, k;
451252190Srpaulo	u8 num_realms, num_matching = 0, encoding, realm_len, *realm_list_len;
452252190Srpaulo	struct hostapd_nai_realm_data *realm;
453252190Srpaulo	const u8 *pos, *realm_name, *end;
454252190Srpaulo	struct {
455252190Srpaulo		unsigned int realm_data_idx;
456252190Srpaulo		unsigned int realm_idx;
457252190Srpaulo	} matches[10];
458252190Srpaulo
459252190Srpaulo	pos = home_realm;
460252190Srpaulo	end = pos + home_realm_len;
461337817Scy	if (end - pos < 1) {
462252190Srpaulo		wpa_hexdump(MSG_DEBUG, "Too short NAI Home Realm Query",
463252190Srpaulo			    home_realm, home_realm_len);
464252190Srpaulo		return -1;
465252190Srpaulo	}
466252190Srpaulo	num_realms = *pos++;
467252190Srpaulo
468252190Srpaulo	for (i = 0; i < num_realms && num_matching < 10; i++) {
469337817Scy		if (end - pos < 2) {
470252190Srpaulo			wpa_hexdump(MSG_DEBUG,
471252190Srpaulo				    "Truncated NAI Home Realm Query",
472252190Srpaulo				    home_realm, home_realm_len);
473252190Srpaulo			return -1;
474252190Srpaulo		}
475252190Srpaulo		encoding = *pos++;
476252190Srpaulo		realm_len = *pos++;
477337817Scy		if (realm_len > end - pos) {
478252190Srpaulo			wpa_hexdump(MSG_DEBUG,
479252190Srpaulo				    "Truncated NAI Home Realm Query",
480252190Srpaulo				    home_realm, home_realm_len);
481252190Srpaulo			return -1;
482252190Srpaulo		}
483252190Srpaulo		realm_name = pos;
484252190Srpaulo		for (j = 0; j < hapd->conf->nai_realm_count &&
485252190Srpaulo			     num_matching < 10; j++) {
486252190Srpaulo			const u8 *rpos, *rend;
487252190Srpaulo			realm = &hapd->conf->nai_realm_data[j];
488252190Srpaulo			if (encoding != realm->encoding)
489252190Srpaulo				continue;
490252190Srpaulo
491252190Srpaulo			rpos = realm_name;
492252190Srpaulo			while (rpos < realm_name + realm_len &&
493252190Srpaulo			       num_matching < 10) {
494252190Srpaulo				for (rend = rpos;
495252190Srpaulo				     rend < realm_name + realm_len; rend++) {
496252190Srpaulo					if (*rend == ';')
497252190Srpaulo						break;
498252190Srpaulo				}
499252190Srpaulo				for (k = 0; k < MAX_NAI_REALMS &&
500252190Srpaulo					     realm->realm[k] &&
501252190Srpaulo					     num_matching < 10; k++) {
502252190Srpaulo					if ((int) os_strlen(realm->realm[k]) !=
503252190Srpaulo					    rend - rpos ||
504252190Srpaulo					    os_strncmp((char *) rpos,
505252190Srpaulo						       realm->realm[k],
506252190Srpaulo						       rend - rpos) != 0)
507252190Srpaulo						continue;
508252190Srpaulo					matches[num_matching].realm_data_idx =
509252190Srpaulo						j;
510252190Srpaulo					matches[num_matching].realm_idx = k;
511252190Srpaulo					num_matching++;
512252190Srpaulo				}
513252190Srpaulo				rpos = rend + 1;
514252190Srpaulo			}
515252190Srpaulo		}
516252190Srpaulo		pos += realm_len;
517252190Srpaulo	}
518252190Srpaulo
519252190Srpaulo	realm_list_len = gas_anqp_add_element(buf, ANQP_NAI_REALM);
520252190Srpaulo	wpabuf_put_le16(buf, num_matching);
521252190Srpaulo
522252190Srpaulo	/*
523252190Srpaulo	 * There are two ways to format. 1. each realm in a NAI Realm Data unit
524252190Srpaulo	 * 2. all realms that share the same EAP methods in a NAI Realm Data
525252190Srpaulo	 * unit. The first format is likely to be bigger in size than the
526252190Srpaulo	 * second, but may be easier to parse and process by the receiver.
527252190Srpaulo	 */
528252190Srpaulo	for (i = 0; i < num_matching; i++) {
529252190Srpaulo		wpa_printf(MSG_DEBUG, "realm_idx %d, realm_data_idx %d",
530252190Srpaulo			   matches[i].realm_data_idx, matches[i].realm_idx);
531252190Srpaulo		realm = &hapd->conf->nai_realm_data[matches[i].realm_data_idx];
532252190Srpaulo		anqp_add_nai_realm_data(buf, realm, matches[i].realm_idx);
533252190Srpaulo	}
534252190Srpaulo	gas_anqp_set_element_len(buf, realm_list_len);
535252190Srpaulo	return 0;
536252190Srpaulo}
537252190Srpaulo
538252190Srpaulo
539252190Srpaulostatic void anqp_add_nai_realm(struct hostapd_data *hapd, struct wpabuf *buf,
540252190Srpaulo			       const u8 *home_realm, size_t home_realm_len,
541252190Srpaulo			       int nai_realm, int nai_home_realm)
542252190Srpaulo{
543337817Scy	if (nai_realm && !nai_home_realm &&
544337817Scy	    anqp_add_override(hapd, buf, ANQP_NAI_REALM))
545337817Scy		return;
546337817Scy
547252190Srpaulo	if (nai_realm && hapd->conf->nai_realm_data) {
548252190Srpaulo		u8 *len;
549252190Srpaulo		unsigned int i, j;
550252190Srpaulo		len = gas_anqp_add_element(buf, ANQP_NAI_REALM);
551252190Srpaulo		wpabuf_put_le16(buf, hapd->conf->nai_realm_count);
552252190Srpaulo		for (i = 0; i < hapd->conf->nai_realm_count; i++) {
553252190Srpaulo			u8 *realm_data_len, *realm_len;
554252190Srpaulo			struct hostapd_nai_realm_data *realm;
555252190Srpaulo
556252190Srpaulo			realm = &hapd->conf->nai_realm_data[i];
557252190Srpaulo			realm_data_len = wpabuf_put(buf, 2);
558252190Srpaulo			wpabuf_put_u8(buf, realm->encoding);
559252190Srpaulo			realm_len = wpabuf_put(buf, 1);
560252190Srpaulo			for (j = 0; realm->realm[j]; j++) {
561252190Srpaulo				if (j > 0)
562252190Srpaulo					wpabuf_put_u8(buf, ';');
563252190Srpaulo				wpabuf_put_str(buf, realm->realm[j]);
564252190Srpaulo			}
565252190Srpaulo			*realm_len = (u8 *) wpabuf_put(buf, 0) - realm_len - 1;
566252190Srpaulo			anqp_add_nai_realm_eap(buf, realm);
567252190Srpaulo			gas_anqp_set_element_len(buf, realm_data_len);
568252190Srpaulo		}
569252190Srpaulo		gas_anqp_set_element_len(buf, len);
570281806Srpaulo	} else if (nai_home_realm && hapd->conf->nai_realm_data && home_realm) {
571252190Srpaulo		hs20_add_nai_home_realm_matches(hapd, buf, home_realm,
572252190Srpaulo						home_realm_len);
573252190Srpaulo	}
574252190Srpaulo}
575252190Srpaulo
576252190Srpaulo
577252190Srpaulostatic void anqp_add_3gpp_cellular_network(struct hostapd_data *hapd,
578252190Srpaulo					   struct wpabuf *buf)
579252190Srpaulo{
580337817Scy	if (anqp_add_override(hapd, buf, ANQP_3GPP_CELLULAR_NETWORK))
581337817Scy		return;
582337817Scy
583252190Srpaulo	if (hapd->conf->anqp_3gpp_cell_net) {
584252190Srpaulo		wpabuf_put_le16(buf, ANQP_3GPP_CELLULAR_NETWORK);
585252190Srpaulo		wpabuf_put_le16(buf,
586252190Srpaulo				hapd->conf->anqp_3gpp_cell_net_len);
587252190Srpaulo		wpabuf_put_data(buf, hapd->conf->anqp_3gpp_cell_net,
588252190Srpaulo				hapd->conf->anqp_3gpp_cell_net_len);
589252190Srpaulo	}
590252190Srpaulo}
591252190Srpaulo
592252190Srpaulo
593252190Srpaulostatic void anqp_add_domain_name(struct hostapd_data *hapd, struct wpabuf *buf)
594252190Srpaulo{
595337817Scy	if (anqp_add_override(hapd, buf, ANQP_DOMAIN_NAME))
596337817Scy		return;
597337817Scy
598252190Srpaulo	if (hapd->conf->domain_name) {
599252190Srpaulo		wpabuf_put_le16(buf, ANQP_DOMAIN_NAME);
600252190Srpaulo		wpabuf_put_le16(buf, hapd->conf->domain_name_len);
601252190Srpaulo		wpabuf_put_data(buf, hapd->conf->domain_name,
602252190Srpaulo				hapd->conf->domain_name_len);
603252190Srpaulo	}
604252190Srpaulo}
605252190Srpaulo
606252190Srpaulo
607346981Scy#ifdef CONFIG_FILS
608346981Scystatic void anqp_add_fils_realm_info(struct hostapd_data *hapd,
609346981Scy				     struct wpabuf *buf)
610346981Scy{
611346981Scy	size_t count;
612346981Scy
613346981Scy	if (anqp_add_override(hapd, buf, ANQP_FILS_REALM_INFO))
614346981Scy		return;
615346981Scy
616346981Scy	count = dl_list_len(&hapd->conf->fils_realms);
617346981Scy	if (count > 10000)
618346981Scy		count = 10000;
619346981Scy	if (count) {
620346981Scy		struct fils_realm *realm;
621346981Scy
622346981Scy		wpabuf_put_le16(buf, ANQP_FILS_REALM_INFO);
623346981Scy		wpabuf_put_le16(buf, 2 * count);
624346981Scy
625346981Scy		dl_list_for_each(realm, &hapd->conf->fils_realms,
626346981Scy				 struct fils_realm, list) {
627346981Scy			if (count == 0)
628346981Scy				break;
629346981Scy			wpabuf_put_data(buf, realm->hash, 2);
630346981Scy			count--;
631346981Scy		}
632346981Scy	}
633346981Scy}
634346981Scy#endif /* CONFIG_FILS */
635346981Scy
636346981Scy
637252190Srpaulo#ifdef CONFIG_HS20
638252190Srpaulo
639252190Srpaulostatic void anqp_add_operator_friendly_name(struct hostapd_data *hapd,
640252190Srpaulo					    struct wpabuf *buf)
641252190Srpaulo{
642252190Srpaulo	if (hapd->conf->hs20_oper_friendly_name) {
643252190Srpaulo		u8 *len;
644252190Srpaulo		unsigned int i;
645252190Srpaulo		len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
646252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
647252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
648252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_FRIENDLY_NAME);
649252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
650252190Srpaulo		for (i = 0; i < hapd->conf->hs20_oper_friendly_name_count; i++)
651252190Srpaulo		{
652252190Srpaulo			struct hostapd_lang_string *vn;
653252190Srpaulo			vn = &hapd->conf->hs20_oper_friendly_name[i];
654252190Srpaulo			wpabuf_put_u8(buf, 3 + vn->name_len);
655252190Srpaulo			wpabuf_put_data(buf, vn->lang, 3);
656252190Srpaulo			wpabuf_put_data(buf, vn->name, vn->name_len);
657252190Srpaulo		}
658252190Srpaulo		gas_anqp_set_element_len(buf, len);
659252190Srpaulo	}
660252190Srpaulo}
661252190Srpaulo
662252190Srpaulo
663252190Srpaulostatic void anqp_add_wan_metrics(struct hostapd_data *hapd,
664252190Srpaulo				 struct wpabuf *buf)
665252190Srpaulo{
666252190Srpaulo	if (hapd->conf->hs20_wan_metrics) {
667252190Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
668252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
669252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
670252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_WAN_METRICS);
671252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
672252190Srpaulo		wpabuf_put_data(buf, hapd->conf->hs20_wan_metrics, 13);
673252190Srpaulo		gas_anqp_set_element_len(buf, len);
674252190Srpaulo	}
675252190Srpaulo}
676252190Srpaulo
677252190Srpaulo
678252190Srpaulostatic void anqp_add_connection_capability(struct hostapd_data *hapd,
679252190Srpaulo					   struct wpabuf *buf)
680252190Srpaulo{
681252190Srpaulo	if (hapd->conf->hs20_connection_capability) {
682252190Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
683252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
684252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
685252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_CONNECTION_CAPABILITY);
686252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
687252190Srpaulo		wpabuf_put_data(buf, hapd->conf->hs20_connection_capability,
688252190Srpaulo				hapd->conf->hs20_connection_capability_len);
689252190Srpaulo		gas_anqp_set_element_len(buf, len);
690252190Srpaulo	}
691252190Srpaulo}
692252190Srpaulo
693252190Srpaulo
694252190Srpaulostatic void anqp_add_operating_class(struct hostapd_data *hapd,
695252190Srpaulo				     struct wpabuf *buf)
696252190Srpaulo{
697252190Srpaulo	if (hapd->conf->hs20_operating_class) {
698252190Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
699252190Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
700252190Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
701252190Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OPERATING_CLASS);
702252190Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
703252190Srpaulo		wpabuf_put_data(buf, hapd->conf->hs20_operating_class,
704252190Srpaulo				hapd->conf->hs20_operating_class_len);
705252190Srpaulo		gas_anqp_set_element_len(buf, len);
706252190Srpaulo	}
707252190Srpaulo}
708252190Srpaulo
709281806Srpaulo
710346981Scystatic void anqp_add_icon(struct wpabuf *buf, struct hostapd_bss_config *bss,
711346981Scy			  const char *name)
712346981Scy{
713346981Scy	size_t j;
714346981Scy	struct hs20_icon *icon = NULL;
715346981Scy
716346981Scy	for (j = 0; j < bss->hs20_icons_count && !icon; j++) {
717346981Scy		if (os_strcmp(name, bss->hs20_icons[j].name) == 0)
718346981Scy			icon = &bss->hs20_icons[j];
719346981Scy	}
720346981Scy	if (!icon)
721346981Scy		return; /* icon info not found */
722346981Scy
723346981Scy	wpabuf_put_le16(buf, icon->width);
724346981Scy	wpabuf_put_le16(buf, icon->height);
725346981Scy	wpabuf_put_data(buf, icon->language, 3);
726346981Scy	wpabuf_put_u8(buf, os_strlen(icon->type));
727346981Scy	wpabuf_put_str(buf, icon->type);
728346981Scy	wpabuf_put_u8(buf, os_strlen(icon->name));
729346981Scy	wpabuf_put_str(buf, icon->name);
730346981Scy}
731346981Scy
732346981Scy
733281806Srpaulostatic void anqp_add_osu_provider(struct wpabuf *buf,
734281806Srpaulo				  struct hostapd_bss_config *bss,
735281806Srpaulo				  struct hs20_osu_provider *p)
736281806Srpaulo{
737281806Srpaulo	u8 *len, *len2, *count;
738281806Srpaulo	unsigned int i;
739281806Srpaulo
740281806Srpaulo	len = wpabuf_put(buf, 2); /* OSU Provider Length to be filled */
741281806Srpaulo
742281806Srpaulo	/* OSU Friendly Name Duples */
743281806Srpaulo	len2 = wpabuf_put(buf, 2);
744281806Srpaulo	for (i = 0; i < p->friendly_name_count; i++) {
745281806Srpaulo		struct hostapd_lang_string *s = &p->friendly_name[i];
746281806Srpaulo		wpabuf_put_u8(buf, 3 + s->name_len);
747281806Srpaulo		wpabuf_put_data(buf, s->lang, 3);
748281806Srpaulo		wpabuf_put_data(buf, s->name, s->name_len);
749281806Srpaulo	}
750281806Srpaulo	WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2);
751281806Srpaulo
752281806Srpaulo	/* OSU Server URI */
753281806Srpaulo	if (p->server_uri) {
754281806Srpaulo		wpabuf_put_u8(buf, os_strlen(p->server_uri));
755281806Srpaulo		wpabuf_put_str(buf, p->server_uri);
756281806Srpaulo	} else
757281806Srpaulo		wpabuf_put_u8(buf, 0);
758281806Srpaulo
759281806Srpaulo	/* OSU Method List */
760281806Srpaulo	count = wpabuf_put(buf, 1);
761346981Scy	for (i = 0; p->method_list && p->method_list[i] >= 0; i++)
762281806Srpaulo		wpabuf_put_u8(buf, p->method_list[i]);
763281806Srpaulo	*count = i;
764281806Srpaulo
765281806Srpaulo	/* Icons Available */
766281806Srpaulo	len2 = wpabuf_put(buf, 2);
767346981Scy	for (i = 0; i < p->icons_count; i++)
768346981Scy		anqp_add_icon(buf, bss, p->icons[i]);
769281806Srpaulo	WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2);
770281806Srpaulo
771281806Srpaulo	/* OSU_NAI */
772281806Srpaulo	if (p->osu_nai) {
773281806Srpaulo		wpabuf_put_u8(buf, os_strlen(p->osu_nai));
774281806Srpaulo		wpabuf_put_str(buf, p->osu_nai);
775281806Srpaulo	} else
776281806Srpaulo		wpabuf_put_u8(buf, 0);
777281806Srpaulo
778281806Srpaulo	/* OSU Service Description Duples */
779281806Srpaulo	len2 = wpabuf_put(buf, 2);
780281806Srpaulo	for (i = 0; i < p->service_desc_count; i++) {
781281806Srpaulo		struct hostapd_lang_string *s = &p->service_desc[i];
782281806Srpaulo		wpabuf_put_u8(buf, 3 + s->name_len);
783281806Srpaulo		wpabuf_put_data(buf, s->lang, 3);
784281806Srpaulo		wpabuf_put_data(buf, s->name, s->name_len);
785281806Srpaulo	}
786281806Srpaulo	WPA_PUT_LE16(len2, (u8 *) wpabuf_put(buf, 0) - len2 - 2);
787281806Srpaulo
788281806Srpaulo	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
789281806Srpaulo}
790281806Srpaulo
791281806Srpaulo
792281806Srpaulostatic void anqp_add_osu_providers_list(struct hostapd_data *hapd,
793281806Srpaulo					struct wpabuf *buf)
794281806Srpaulo{
795281806Srpaulo	if (hapd->conf->hs20_osu_providers_count) {
796281806Srpaulo		size_t i;
797281806Srpaulo		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
798281806Srpaulo		wpabuf_put_be24(buf, OUI_WFA);
799281806Srpaulo		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
800281806Srpaulo		wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_LIST);
801281806Srpaulo		wpabuf_put_u8(buf, 0); /* Reserved */
802281806Srpaulo
803281806Srpaulo		/* OSU SSID */
804281806Srpaulo		wpabuf_put_u8(buf, hapd->conf->osu_ssid_len);
805281806Srpaulo		wpabuf_put_data(buf, hapd->conf->osu_ssid,
806281806Srpaulo				hapd->conf->osu_ssid_len);
807281806Srpaulo
808281806Srpaulo		/* Number of OSU Providers */
809281806Srpaulo		wpabuf_put_u8(buf, hapd->conf->hs20_osu_providers_count);
810281806Srpaulo
811281806Srpaulo		for (i = 0; i < hapd->conf->hs20_osu_providers_count; i++) {
812281806Srpaulo			anqp_add_osu_provider(
813281806Srpaulo				buf, hapd->conf,
814281806Srpaulo				&hapd->conf->hs20_osu_providers[i]);
815281806Srpaulo		}
816281806Srpaulo
817281806Srpaulo		gas_anqp_set_element_len(buf, len);
818281806Srpaulo	}
819281806Srpaulo}
820281806Srpaulo
821281806Srpaulo
822346981Scystatic void anqp_add_osu_provider_nai(struct wpabuf *buf,
823346981Scy				      struct hs20_osu_provider *p)
824346981Scy{
825346981Scy	/* OSU_NAI for shared BSS (Single SSID) */
826346981Scy	if (p->osu_nai2) {
827346981Scy		wpabuf_put_u8(buf, os_strlen(p->osu_nai2));
828346981Scy		wpabuf_put_str(buf, p->osu_nai2);
829346981Scy	} else {
830346981Scy		wpabuf_put_u8(buf, 0);
831346981Scy	}
832346981Scy}
833346981Scy
834346981Scy
835346981Scystatic void anqp_add_osu_providers_nai_list(struct hostapd_data *hapd,
836346981Scy					    struct wpabuf *buf)
837346981Scy{
838346981Scy	if (hapd->conf->hs20_osu_providers_nai_count) {
839346981Scy		size_t i;
840346981Scy		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
841346981Scy		wpabuf_put_be24(buf, OUI_WFA);
842346981Scy		wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
843346981Scy		wpabuf_put_u8(buf, HS20_STYPE_OSU_PROVIDERS_NAI_LIST);
844346981Scy		wpabuf_put_u8(buf, 0); /* Reserved */
845346981Scy
846346981Scy		for (i = 0; i < hapd->conf->hs20_osu_providers_count; i++) {
847346981Scy			anqp_add_osu_provider_nai(
848346981Scy				buf, &hapd->conf->hs20_osu_providers[i]);
849346981Scy		}
850346981Scy
851346981Scy		gas_anqp_set_element_len(buf, len);
852346981Scy	}
853346981Scy}
854346981Scy
855346981Scy
856281806Srpaulostatic void anqp_add_icon_binary_file(struct hostapd_data *hapd,
857281806Srpaulo				      struct wpabuf *buf,
858281806Srpaulo				      const u8 *name, size_t name_len)
859281806Srpaulo{
860281806Srpaulo	struct hs20_icon *icon;
861281806Srpaulo	size_t i;
862281806Srpaulo	u8 *len;
863281806Srpaulo
864281806Srpaulo	wpa_hexdump_ascii(MSG_DEBUG, "HS 2.0: Requested Icon Filename",
865281806Srpaulo			  name, name_len);
866281806Srpaulo	for (i = 0; i < hapd->conf->hs20_icons_count; i++) {
867281806Srpaulo		icon = &hapd->conf->hs20_icons[i];
868281806Srpaulo		if (name_len == os_strlen(icon->name) &&
869281806Srpaulo		    os_memcmp(name, icon->name, name_len) == 0)
870281806Srpaulo			break;
871281806Srpaulo	}
872281806Srpaulo
873281806Srpaulo	if (i < hapd->conf->hs20_icons_count)
874281806Srpaulo		icon = &hapd->conf->hs20_icons[i];
875281806Srpaulo	else
876281806Srpaulo		icon = NULL;
877281806Srpaulo
878281806Srpaulo	len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
879281806Srpaulo	wpabuf_put_be24(buf, OUI_WFA);
880281806Srpaulo	wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
881281806Srpaulo	wpabuf_put_u8(buf, HS20_STYPE_ICON_BINARY_FILE);
882281806Srpaulo	wpabuf_put_u8(buf, 0); /* Reserved */
883281806Srpaulo
884281806Srpaulo	if (icon) {
885281806Srpaulo		char *data;
886281806Srpaulo		size_t data_len;
887281806Srpaulo
888281806Srpaulo		data = os_readfile(icon->file, &data_len);
889281806Srpaulo		if (data == NULL || data_len > 65535) {
890281806Srpaulo			wpabuf_put_u8(buf, 2); /* Download Status:
891281806Srpaulo						* Unspecified file error */
892281806Srpaulo			wpabuf_put_u8(buf, 0);
893281806Srpaulo			wpabuf_put_le16(buf, 0);
894281806Srpaulo		} else {
895281806Srpaulo			wpabuf_put_u8(buf, 0); /* Download Status: Success */
896281806Srpaulo			wpabuf_put_u8(buf, os_strlen(icon->type));
897281806Srpaulo			wpabuf_put_str(buf, icon->type);
898281806Srpaulo			wpabuf_put_le16(buf, data_len);
899281806Srpaulo			wpabuf_put_data(buf, data, data_len);
900281806Srpaulo		}
901281806Srpaulo		os_free(data);
902281806Srpaulo	} else {
903281806Srpaulo		wpabuf_put_u8(buf, 1); /* Download Status: File not found */
904281806Srpaulo		wpabuf_put_u8(buf, 0);
905281806Srpaulo		wpabuf_put_le16(buf, 0);
906281806Srpaulo	}
907281806Srpaulo
908281806Srpaulo	gas_anqp_set_element_len(buf, len);
909281806Srpaulo}
910281806Srpaulo
911346981Scy
912346981Scystatic void anqp_add_operator_icon_metadata(struct hostapd_data *hapd,
913346981Scy					    struct wpabuf *buf)
914346981Scy{
915346981Scy	struct hostapd_bss_config *bss = hapd->conf;
916346981Scy	size_t i;
917346981Scy	u8 *len;
918346981Scy
919346981Scy	if (!bss->hs20_operator_icon_count)
920346981Scy		return;
921346981Scy
922346981Scy	len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
923346981Scy
924346981Scy	wpabuf_put_be24(buf, OUI_WFA);
925346981Scy	wpabuf_put_u8(buf, HS20_ANQP_OUI_TYPE);
926346981Scy	wpabuf_put_u8(buf, HS20_STYPE_OPERATOR_ICON_METADATA);
927346981Scy	wpabuf_put_u8(buf, 0); /* Reserved */
928346981Scy
929346981Scy	for (i = 0; i < bss->hs20_operator_icon_count; i++)
930346981Scy		anqp_add_icon(buf, bss, bss->hs20_operator_icon[i]);
931346981Scy
932346981Scy	gas_anqp_set_element_len(buf, len);
933346981Scy}
934346981Scy
935252190Srpaulo#endif /* CONFIG_HS20 */
936252190Srpaulo
937252190Srpaulo
938346981Scy#ifdef CONFIG_MBO
939346981Scystatic void anqp_add_mbo_cell_data_conn_pref(struct hostapd_data *hapd,
940346981Scy					     struct wpabuf *buf)
941346981Scy{
942346981Scy	if (hapd->conf->mbo_cell_data_conn_pref >= 0) {
943346981Scy		u8 *len = gas_anqp_add_element(buf, ANQP_VENDOR_SPECIFIC);
944346981Scy		wpabuf_put_be24(buf, OUI_WFA);
945346981Scy		wpabuf_put_u8(buf, MBO_ANQP_OUI_TYPE);
946346981Scy		wpabuf_put_u8(buf, MBO_ANQP_SUBTYPE_CELL_CONN_PREF);
947346981Scy		wpabuf_put_u8(buf, hapd->conf->mbo_cell_data_conn_pref);
948346981Scy		gas_anqp_set_element_len(buf, len);
949346981Scy	}
950346981Scy}
951346981Scy#endif /* CONFIG_MBO */
952346981Scy
953346981Scy
954337817Scystatic size_t anqp_get_required_len(struct hostapd_data *hapd,
955337817Scy				    const u16 *infoid,
956337817Scy				    unsigned int num_infoid)
957337817Scy{
958337817Scy	size_t len = 0;
959337817Scy	unsigned int i;
960337817Scy
961337817Scy	for (i = 0; i < num_infoid; i++) {
962337817Scy		struct anqp_element *elem = get_anqp_elem(hapd, infoid[i]);
963337817Scy
964337817Scy		if (elem)
965337817Scy			len += 2 + 2 + wpabuf_len(elem->payload);
966337817Scy	}
967337817Scy
968337817Scy	return len;
969337817Scy}
970337817Scy
971337817Scy
972252190Srpaulostatic struct wpabuf *
973252190Srpaulogas_serv_build_gas_resp_payload(struct hostapd_data *hapd,
974252190Srpaulo				unsigned int request,
975281806Srpaulo				const u8 *home_realm, size_t home_realm_len,
976337817Scy				const u8 *icon_name, size_t icon_name_len,
977337817Scy				const u16 *extra_req,
978337817Scy				unsigned int num_extra_req)
979252190Srpaulo{
980252190Srpaulo	struct wpabuf *buf;
981281806Srpaulo	size_t len;
982337817Scy	unsigned int i;
983252190Srpaulo
984281806Srpaulo	len = 1400;
985281806Srpaulo	if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM))
986281806Srpaulo		len += 1000;
987281806Srpaulo	if (request & ANQP_REQ_ICON_REQUEST)
988281806Srpaulo		len += 65536;
989346981Scy#ifdef CONFIG_FILS
990346981Scy	if (request & ANQP_FILS_REALM_INFO)
991346981Scy		len += 2 * dl_list_len(&hapd->conf->fils_realms);
992346981Scy#endif /* CONFIG_FILS */
993337817Scy	len += anqp_get_required_len(hapd, extra_req, num_extra_req);
994281806Srpaulo
995281806Srpaulo	buf = wpabuf_alloc(len);
996252190Srpaulo	if (buf == NULL)
997252190Srpaulo		return NULL;
998252190Srpaulo
999252190Srpaulo	if (request & ANQP_REQ_CAPABILITY_LIST)
1000252190Srpaulo		anqp_add_capab_list(hapd, buf);
1001252190Srpaulo	if (request & ANQP_REQ_VENUE_NAME)
1002252190Srpaulo		anqp_add_venue_name(hapd, buf);
1003337817Scy	if (request & ANQP_REQ_EMERGENCY_CALL_NUMBER)
1004337817Scy		anqp_add_elem(hapd, buf, ANQP_EMERGENCY_CALL_NUMBER);
1005252190Srpaulo	if (request & ANQP_REQ_NETWORK_AUTH_TYPE)
1006252190Srpaulo		anqp_add_network_auth_type(hapd, buf);
1007252190Srpaulo	if (request & ANQP_REQ_ROAMING_CONSORTIUM)
1008252190Srpaulo		anqp_add_roaming_consortium(hapd, buf);
1009252190Srpaulo	if (request & ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY)
1010252190Srpaulo		anqp_add_ip_addr_type_availability(hapd, buf);
1011252190Srpaulo	if (request & (ANQP_REQ_NAI_REALM | ANQP_REQ_NAI_HOME_REALM))
1012252190Srpaulo		anqp_add_nai_realm(hapd, buf, home_realm, home_realm_len,
1013252190Srpaulo				   request & ANQP_REQ_NAI_REALM,
1014252190Srpaulo				   request & ANQP_REQ_NAI_HOME_REALM);
1015252190Srpaulo	if (request & ANQP_REQ_3GPP_CELLULAR_NETWORK)
1016252190Srpaulo		anqp_add_3gpp_cellular_network(hapd, buf);
1017337817Scy	if (request & ANQP_REQ_AP_GEOSPATIAL_LOCATION)
1018337817Scy		anqp_add_elem(hapd, buf, ANQP_AP_GEOSPATIAL_LOCATION);
1019337817Scy	if (request & ANQP_REQ_AP_CIVIC_LOCATION)
1020337817Scy		anqp_add_elem(hapd, buf, ANQP_AP_CIVIC_LOCATION);
1021337817Scy	if (request & ANQP_REQ_AP_LOCATION_PUBLIC_URI)
1022337817Scy		anqp_add_elem(hapd, buf, ANQP_AP_LOCATION_PUBLIC_URI);
1023252190Srpaulo	if (request & ANQP_REQ_DOMAIN_NAME)
1024252190Srpaulo		anqp_add_domain_name(hapd, buf);
1025337817Scy	if (request & ANQP_REQ_EMERGENCY_ALERT_URI)
1026337817Scy		anqp_add_elem(hapd, buf, ANQP_EMERGENCY_ALERT_URI);
1027337817Scy	if (request & ANQP_REQ_TDLS_CAPABILITY)
1028337817Scy		anqp_add_elem(hapd, buf, ANQP_TDLS_CAPABILITY);
1029337817Scy	if (request & ANQP_REQ_EMERGENCY_NAI)
1030337817Scy		anqp_add_elem(hapd, buf, ANQP_EMERGENCY_NAI);
1031252190Srpaulo
1032346981Scy	for (i = 0; i < num_extra_req; i++) {
1033346981Scy#ifdef CONFIG_FILS
1034346981Scy		if (extra_req[i] == ANQP_FILS_REALM_INFO) {
1035346981Scy			anqp_add_fils_realm_info(hapd, buf);
1036346981Scy			continue;
1037346981Scy		}
1038346981Scy#endif /* CONFIG_FILS */
1039346981Scy		if (extra_req[i] == ANQP_VENUE_URL) {
1040346981Scy			anqp_add_venue_url(hapd, buf);
1041346981Scy			continue;
1042346981Scy		}
1043337817Scy		anqp_add_elem(hapd, buf, extra_req[i]);
1044346981Scy	}
1045337817Scy
1046252190Srpaulo#ifdef CONFIG_HS20
1047252190Srpaulo	if (request & ANQP_REQ_HS_CAPABILITY_LIST)
1048252190Srpaulo		anqp_add_hs_capab_list(hapd, buf);
1049252190Srpaulo	if (request & ANQP_REQ_OPERATOR_FRIENDLY_NAME)
1050252190Srpaulo		anqp_add_operator_friendly_name(hapd, buf);
1051252190Srpaulo	if (request & ANQP_REQ_WAN_METRICS)
1052252190Srpaulo		anqp_add_wan_metrics(hapd, buf);
1053252190Srpaulo	if (request & ANQP_REQ_CONNECTION_CAPABILITY)
1054252190Srpaulo		anqp_add_connection_capability(hapd, buf);
1055252190Srpaulo	if (request & ANQP_REQ_OPERATING_CLASS)
1056252190Srpaulo		anqp_add_operating_class(hapd, buf);
1057281806Srpaulo	if (request & ANQP_REQ_OSU_PROVIDERS_LIST)
1058281806Srpaulo		anqp_add_osu_providers_list(hapd, buf);
1059281806Srpaulo	if (request & ANQP_REQ_ICON_REQUEST)
1060281806Srpaulo		anqp_add_icon_binary_file(hapd, buf, icon_name, icon_name_len);
1061346981Scy	if (request & ANQP_REQ_OPERATOR_ICON_METADATA)
1062346981Scy		anqp_add_operator_icon_metadata(hapd, buf);
1063346981Scy	if (request & ANQP_REQ_OSU_PROVIDERS_NAI_LIST)
1064346981Scy		anqp_add_osu_providers_nai_list(hapd, buf);
1065252190Srpaulo#endif /* CONFIG_HS20 */
1066252190Srpaulo
1067346981Scy#ifdef CONFIG_MBO
1068346981Scy	if (request & ANQP_REQ_MBO_CELL_DATA_CONN_PREF)
1069346981Scy		anqp_add_mbo_cell_data_conn_pref(hapd, buf);
1070346981Scy#endif /* CONFIG_MBO */
1071346981Scy
1072252190Srpaulo	return buf;
1073252190Srpaulo}
1074252190Srpaulo
1075252190Srpaulo
1076337817Scy#define ANQP_MAX_EXTRA_REQ 20
1077337817Scy
1078252190Srpaulostruct anqp_query_info {
1079252190Srpaulo	unsigned int request;
1080252190Srpaulo	const u8 *home_realm_query;
1081252190Srpaulo	size_t home_realm_query_len;
1082281806Srpaulo	const u8 *icon_name;
1083281806Srpaulo	size_t icon_name_len;
1084281806Srpaulo	int p2p_sd;
1085337817Scy	u16 extra_req[ANQP_MAX_EXTRA_REQ];
1086337817Scy	unsigned int num_extra_req;
1087252190Srpaulo};
1088252190Srpaulo
1089252190Srpaulo
1090252190Srpaulostatic void set_anqp_req(unsigned int bit, const char *name, int local,
1091252190Srpaulo			 struct anqp_query_info *qi)
1092252190Srpaulo{
1093252190Srpaulo	qi->request |= bit;
1094252190Srpaulo	if (local) {
1095252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: %s (local)", name);
1096252190Srpaulo	} else {
1097252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: %s not available", name);
1098252190Srpaulo	}
1099252190Srpaulo}
1100252190Srpaulo
1101252190Srpaulo
1102252190Srpaulostatic void rx_anqp_query_list_id(struct hostapd_data *hapd, u16 info_id,
1103252190Srpaulo				  struct anqp_query_info *qi)
1104252190Srpaulo{
1105252190Srpaulo	switch (info_id) {
1106252190Srpaulo	case ANQP_CAPABILITY_LIST:
1107281806Srpaulo		set_anqp_req(ANQP_REQ_CAPABILITY_LIST, "Capability List", 1,
1108281806Srpaulo			     qi);
1109252190Srpaulo		break;
1110252190Srpaulo	case ANQP_VENUE_NAME:
1111252190Srpaulo		set_anqp_req(ANQP_REQ_VENUE_NAME, "Venue Name",
1112281806Srpaulo			     hapd->conf->venue_name != NULL, qi);
1113252190Srpaulo		break;
1114337817Scy	case ANQP_EMERGENCY_CALL_NUMBER:
1115337817Scy		set_anqp_req(ANQP_REQ_EMERGENCY_CALL_NUMBER,
1116337817Scy			     "Emergency Call Number",
1117337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1118337817Scy		break;
1119252190Srpaulo	case ANQP_NETWORK_AUTH_TYPE:
1120252190Srpaulo		set_anqp_req(ANQP_REQ_NETWORK_AUTH_TYPE, "Network Auth Type",
1121281806Srpaulo			     hapd->conf->network_auth_type != NULL, qi);
1122252190Srpaulo		break;
1123252190Srpaulo	case ANQP_ROAMING_CONSORTIUM:
1124252190Srpaulo		set_anqp_req(ANQP_REQ_ROAMING_CONSORTIUM, "Roaming Consortium",
1125281806Srpaulo			     hapd->conf->roaming_consortium != NULL, qi);
1126252190Srpaulo		break;
1127252190Srpaulo	case ANQP_IP_ADDR_TYPE_AVAILABILITY:
1128252190Srpaulo		set_anqp_req(ANQP_REQ_IP_ADDR_TYPE_AVAILABILITY,
1129252190Srpaulo			     "IP Addr Type Availability",
1130281806Srpaulo			     hapd->conf->ipaddr_type_configured, qi);
1131252190Srpaulo		break;
1132252190Srpaulo	case ANQP_NAI_REALM:
1133252190Srpaulo		set_anqp_req(ANQP_REQ_NAI_REALM, "NAI Realm",
1134281806Srpaulo			     hapd->conf->nai_realm_data != NULL, qi);
1135252190Srpaulo		break;
1136252190Srpaulo	case ANQP_3GPP_CELLULAR_NETWORK:
1137252190Srpaulo		set_anqp_req(ANQP_REQ_3GPP_CELLULAR_NETWORK,
1138252190Srpaulo			     "3GPP Cellular Network",
1139281806Srpaulo			     hapd->conf->anqp_3gpp_cell_net != NULL, qi);
1140252190Srpaulo		break;
1141337817Scy	case ANQP_AP_GEOSPATIAL_LOCATION:
1142337817Scy		set_anqp_req(ANQP_REQ_AP_GEOSPATIAL_LOCATION,
1143337817Scy			     "AP Geospatial Location",
1144337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1145337817Scy		break;
1146337817Scy	case ANQP_AP_CIVIC_LOCATION:
1147337817Scy		set_anqp_req(ANQP_REQ_AP_CIVIC_LOCATION,
1148337817Scy			     "AP Civic Location",
1149337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1150337817Scy		break;
1151337817Scy	case ANQP_AP_LOCATION_PUBLIC_URI:
1152337817Scy		set_anqp_req(ANQP_REQ_AP_LOCATION_PUBLIC_URI,
1153337817Scy			     "AP Location Public URI",
1154337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1155337817Scy		break;
1156252190Srpaulo	case ANQP_DOMAIN_NAME:
1157252190Srpaulo		set_anqp_req(ANQP_REQ_DOMAIN_NAME, "Domain Name",
1158281806Srpaulo			     hapd->conf->domain_name != NULL, qi);
1159252190Srpaulo		break;
1160337817Scy	case ANQP_EMERGENCY_ALERT_URI:
1161337817Scy		set_anqp_req(ANQP_REQ_EMERGENCY_ALERT_URI,
1162337817Scy			     "Emergency Alert URI",
1163337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1164337817Scy		break;
1165337817Scy	case ANQP_TDLS_CAPABILITY:
1166337817Scy		set_anqp_req(ANQP_REQ_TDLS_CAPABILITY,
1167337817Scy			     "TDLS Capability",
1168337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1169337817Scy		break;
1170337817Scy	case ANQP_EMERGENCY_NAI:
1171337817Scy		set_anqp_req(ANQP_REQ_EMERGENCY_NAI,
1172337817Scy			     "Emergency NAI",
1173337817Scy			     get_anqp_elem(hapd, info_id) != NULL, qi);
1174337817Scy		break;
1175252190Srpaulo	default:
1176346981Scy#ifdef CONFIG_FILS
1177346981Scy		if (info_id == ANQP_FILS_REALM_INFO &&
1178346981Scy		    !dl_list_empty(&hapd->conf->fils_realms)) {
1179346981Scy			wpa_printf(MSG_DEBUG,
1180346981Scy				   "ANQP: FILS Realm Information (local)");
1181346981Scy		} else
1182346981Scy#endif /* CONFIG_FILS */
1183346981Scy		if (info_id == ANQP_VENUE_URL && hapd->conf->venue_url) {
1184346981Scy			wpa_printf(MSG_DEBUG,
1185346981Scy				   "ANQP: Venue URL (local)");
1186346981Scy		} else if (!get_anqp_elem(hapd, info_id)) {
1187337817Scy			wpa_printf(MSG_DEBUG, "ANQP: Unsupported Info Id %u",
1188337817Scy				   info_id);
1189337817Scy			break;
1190337817Scy		}
1191337817Scy		if (qi->num_extra_req == ANQP_MAX_EXTRA_REQ) {
1192337817Scy			wpa_printf(MSG_DEBUG,
1193337817Scy				   "ANQP: No more room for extra requests - ignore Info Id %u",
1194337817Scy				   info_id);
1195337817Scy			break;
1196337817Scy		}
1197337817Scy		wpa_printf(MSG_DEBUG, "ANQP: Info Id %u (local)", info_id);
1198337817Scy		qi->extra_req[qi->num_extra_req] = info_id;
1199337817Scy		qi->num_extra_req++;
1200252190Srpaulo		break;
1201252190Srpaulo	}
1202252190Srpaulo}
1203252190Srpaulo
1204252190Srpaulo
1205252190Srpaulostatic void rx_anqp_query_list(struct hostapd_data *hapd,
1206252190Srpaulo			       const u8 *pos, const u8 *end,
1207252190Srpaulo			       struct anqp_query_info *qi)
1208252190Srpaulo{
1209252190Srpaulo	wpa_printf(MSG_DEBUG, "ANQP: %u Info IDs requested in Query list",
1210252190Srpaulo		   (unsigned int) (end - pos) / 2);
1211252190Srpaulo
1212337817Scy	while (end - pos >= 2) {
1213252190Srpaulo		rx_anqp_query_list_id(hapd, WPA_GET_LE16(pos), qi);
1214252190Srpaulo		pos += 2;
1215252190Srpaulo	}
1216252190Srpaulo}
1217252190Srpaulo
1218252190Srpaulo
1219252190Srpaulo#ifdef CONFIG_HS20
1220252190Srpaulo
1221252190Srpaulostatic void rx_anqp_hs_query_list(struct hostapd_data *hapd, u8 subtype,
1222252190Srpaulo				  struct anqp_query_info *qi)
1223252190Srpaulo{
1224252190Srpaulo	switch (subtype) {
1225252190Srpaulo	case HS20_STYPE_CAPABILITY_LIST:
1226252190Srpaulo		set_anqp_req(ANQP_REQ_HS_CAPABILITY_LIST, "HS Capability List",
1227281806Srpaulo			     1, qi);
1228252190Srpaulo		break;
1229252190Srpaulo	case HS20_STYPE_OPERATOR_FRIENDLY_NAME:
1230252190Srpaulo		set_anqp_req(ANQP_REQ_OPERATOR_FRIENDLY_NAME,
1231252190Srpaulo			     "Operator Friendly Name",
1232281806Srpaulo			     hapd->conf->hs20_oper_friendly_name != NULL, qi);
1233252190Srpaulo		break;
1234252190Srpaulo	case HS20_STYPE_WAN_METRICS:
1235252190Srpaulo		set_anqp_req(ANQP_REQ_WAN_METRICS, "WAN Metrics",
1236281806Srpaulo			     hapd->conf->hs20_wan_metrics != NULL, qi);
1237252190Srpaulo		break;
1238252190Srpaulo	case HS20_STYPE_CONNECTION_CAPABILITY:
1239252190Srpaulo		set_anqp_req(ANQP_REQ_CONNECTION_CAPABILITY,
1240252190Srpaulo			     "Connection Capability",
1241252190Srpaulo			     hapd->conf->hs20_connection_capability != NULL,
1242281806Srpaulo			     qi);
1243252190Srpaulo		break;
1244252190Srpaulo	case HS20_STYPE_OPERATING_CLASS:
1245252190Srpaulo		set_anqp_req(ANQP_REQ_OPERATING_CLASS, "Operating Class",
1246281806Srpaulo			     hapd->conf->hs20_operating_class != NULL, qi);
1247252190Srpaulo		break;
1248281806Srpaulo	case HS20_STYPE_OSU_PROVIDERS_LIST:
1249281806Srpaulo		set_anqp_req(ANQP_REQ_OSU_PROVIDERS_LIST, "OSU Providers list",
1250281806Srpaulo			     hapd->conf->hs20_osu_providers_count, qi);
1251281806Srpaulo		break;
1252346981Scy	case HS20_STYPE_OPERATOR_ICON_METADATA:
1253346981Scy		set_anqp_req(ANQP_REQ_OPERATOR_ICON_METADATA,
1254346981Scy			     "Operator Icon Metadata",
1255346981Scy			     hapd->conf->hs20_operator_icon_count, qi);
1256346981Scy		break;
1257346981Scy	case HS20_STYPE_OSU_PROVIDERS_NAI_LIST:
1258346981Scy		set_anqp_req(ANQP_REQ_OSU_PROVIDERS_NAI_LIST,
1259346981Scy			     "OSU Providers NAI List",
1260346981Scy			     hapd->conf->hs20_osu_providers_nai_count, qi);
1261346981Scy		break;
1262252190Srpaulo	default:
1263252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 subtype %u",
1264252190Srpaulo			   subtype);
1265252190Srpaulo		break;
1266252190Srpaulo	}
1267252190Srpaulo}
1268252190Srpaulo
1269252190Srpaulo
1270252190Srpaulostatic void rx_anqp_hs_nai_home_realm(struct hostapd_data *hapd,
1271252190Srpaulo				      const u8 *pos, const u8 *end,
1272252190Srpaulo				      struct anqp_query_info *qi)
1273252190Srpaulo{
1274252190Srpaulo	qi->request |= ANQP_REQ_NAI_HOME_REALM;
1275252190Srpaulo	qi->home_realm_query = pos;
1276252190Srpaulo	qi->home_realm_query_len = end - pos;
1277252190Srpaulo	if (hapd->conf->nai_realm_data != NULL) {
1278252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 NAI Home Realm Query "
1279252190Srpaulo			   "(local)");
1280252190Srpaulo	} else {
1281252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 NAI Home Realm Query not "
1282252190Srpaulo			   "available");
1283252190Srpaulo	}
1284252190Srpaulo}
1285252190Srpaulo
1286252190Srpaulo
1287281806Srpaulostatic void rx_anqp_hs_icon_request(struct hostapd_data *hapd,
1288281806Srpaulo				    const u8 *pos, const u8 *end,
1289281806Srpaulo				    struct anqp_query_info *qi)
1290281806Srpaulo{
1291281806Srpaulo	qi->request |= ANQP_REQ_ICON_REQUEST;
1292281806Srpaulo	qi->icon_name = pos;
1293281806Srpaulo	qi->icon_name_len = end - pos;
1294281806Srpaulo	if (hapd->conf->hs20_icons_count) {
1295281806Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query "
1296281806Srpaulo			   "(local)");
1297281806Srpaulo	} else {
1298281806Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Icon Request Query not "
1299281806Srpaulo			   "available");
1300281806Srpaulo	}
1301281806Srpaulo}
1302281806Srpaulo
1303281806Srpaulo
1304346981Scystatic void rx_anqp_vendor_specific_hs20(struct hostapd_data *hapd,
1305346981Scy					 const u8 *pos, const u8 *end,
1306346981Scy					 struct anqp_query_info *qi)
1307252190Srpaulo{
1308252190Srpaulo	u8 subtype;
1309252190Srpaulo
1310337817Scy	if (end - pos <= 1)
1311252190Srpaulo		return;
1312252190Srpaulo
1313252190Srpaulo	subtype = *pos++;
1314252190Srpaulo	pos++; /* Reserved */
1315252190Srpaulo	switch (subtype) {
1316252190Srpaulo	case HS20_STYPE_QUERY_LIST:
1317252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: HS 2.0 Query List");
1318252190Srpaulo		while (pos < end) {
1319252190Srpaulo			rx_anqp_hs_query_list(hapd, *pos, qi);
1320252190Srpaulo			pos++;
1321252190Srpaulo		}
1322252190Srpaulo		break;
1323252190Srpaulo	case HS20_STYPE_NAI_HOME_REALM_QUERY:
1324252190Srpaulo		rx_anqp_hs_nai_home_realm(hapd, pos, end, qi);
1325252190Srpaulo		break;
1326281806Srpaulo	case HS20_STYPE_ICON_REQUEST:
1327281806Srpaulo		rx_anqp_hs_icon_request(hapd, pos, end, qi);
1328281806Srpaulo		break;
1329252190Srpaulo	default:
1330252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Unsupported HS 2.0 query subtype "
1331252190Srpaulo			   "%u", subtype);
1332252190Srpaulo		break;
1333252190Srpaulo	}
1334252190Srpaulo}
1335252190Srpaulo
1336252190Srpaulo#endif /* CONFIG_HS20 */
1337252190Srpaulo
1338252190Srpaulo
1339346981Scy#ifdef CONFIG_P2P
1340346981Scystatic void rx_anqp_vendor_specific_p2p(struct hostapd_data *hapd,
1341346981Scy					struct anqp_query_info *qi)
1342346981Scy{
1343346981Scy	/*
1344346981Scy	 * This is for P2P SD and will be taken care of by the P2P
1345346981Scy	 * implementation. This query needs to be ignored in the generic
1346346981Scy	 * GAS server to avoid duplicated response.
1347346981Scy	 */
1348346981Scy	wpa_printf(MSG_DEBUG,
1349346981Scy		   "ANQP: Ignore WFA vendor type %u (P2P SD) in generic GAS server",
1350346981Scy		   P2P_OUI_TYPE);
1351346981Scy	qi->p2p_sd = 1;
1352346981Scy	return;
1353346981Scy}
1354346981Scy#endif /* CONFIG_P2P */
1355346981Scy
1356346981Scy
1357346981Scy#ifdef CONFIG_MBO
1358346981Scy
1359346981Scystatic void rx_anqp_mbo_query_list(struct hostapd_data *hapd, u8 subtype,
1360346981Scy				  struct anqp_query_info *qi)
1361346981Scy{
1362346981Scy	switch (subtype) {
1363346981Scy	case MBO_ANQP_SUBTYPE_CELL_CONN_PREF:
1364346981Scy		set_anqp_req(ANQP_REQ_MBO_CELL_DATA_CONN_PREF,
1365346981Scy			     "Cellular Data Connection Preference",
1366346981Scy			     hapd->conf->mbo_cell_data_conn_pref >= 0, qi);
1367346981Scy		break;
1368346981Scy	default:
1369346981Scy		wpa_printf(MSG_DEBUG, "ANQP: Unsupported MBO subtype %u",
1370346981Scy			   subtype);
1371346981Scy		break;
1372346981Scy	}
1373346981Scy}
1374346981Scy
1375346981Scy
1376346981Scystatic void rx_anqp_vendor_specific_mbo(struct hostapd_data *hapd,
1377346981Scy					const u8 *pos, const u8 *end,
1378346981Scy					struct anqp_query_info *qi)
1379346981Scy{
1380346981Scy	u8 subtype;
1381346981Scy
1382346981Scy	if (end - pos < 1)
1383346981Scy		return;
1384346981Scy
1385346981Scy	subtype = *pos++;
1386346981Scy	switch (subtype) {
1387346981Scy	case MBO_ANQP_SUBTYPE_QUERY_LIST:
1388346981Scy		wpa_printf(MSG_DEBUG, "ANQP: MBO Query List");
1389346981Scy		while (pos < end) {
1390346981Scy			rx_anqp_mbo_query_list(hapd, *pos, qi);
1391346981Scy			pos++;
1392346981Scy		}
1393346981Scy		break;
1394346981Scy	default:
1395346981Scy		wpa_printf(MSG_DEBUG, "ANQP: Unsupported MBO query subtype %u",
1396346981Scy			   subtype);
1397346981Scy		break;
1398346981Scy	}
1399346981Scy}
1400346981Scy
1401346981Scy#endif /* CONFIG_MBO */
1402346981Scy
1403346981Scy
1404346981Scystatic void rx_anqp_vendor_specific(struct hostapd_data *hapd,
1405346981Scy				    const u8 *pos, const u8 *end,
1406346981Scy				    struct anqp_query_info *qi)
1407346981Scy{
1408346981Scy	u32 oui;
1409346981Scy
1410346981Scy	if (end - pos < 4) {
1411346981Scy		wpa_printf(MSG_DEBUG, "ANQP: Too short vendor specific ANQP "
1412346981Scy			   "Query element");
1413346981Scy		return;
1414346981Scy	}
1415346981Scy
1416346981Scy	oui = WPA_GET_BE24(pos);
1417346981Scy	pos += 3;
1418346981Scy	if (oui != OUI_WFA) {
1419346981Scy		wpa_printf(MSG_DEBUG, "ANQP: Unsupported vendor OUI %06x",
1420346981Scy			   oui);
1421346981Scy		return;
1422346981Scy	}
1423346981Scy
1424346981Scy	switch (*pos) {
1425346981Scy#ifdef CONFIG_P2P
1426346981Scy	case P2P_OUI_TYPE:
1427346981Scy		rx_anqp_vendor_specific_p2p(hapd, qi);
1428346981Scy		break;
1429346981Scy#endif /* CONFIG_P2P */
1430346981Scy#ifdef CONFIG_HS20
1431346981Scy	case HS20_ANQP_OUI_TYPE:
1432346981Scy		rx_anqp_vendor_specific_hs20(hapd, pos + 1, end, qi);
1433346981Scy		break;
1434346981Scy#endif /* CONFIG_HS20 */
1435346981Scy#ifdef CONFIG_MBO
1436346981Scy	case MBO_ANQP_OUI_TYPE:
1437346981Scy		rx_anqp_vendor_specific_mbo(hapd, pos + 1, end, qi);
1438346981Scy		break;
1439346981Scy#endif /* CONFIG_MBO */
1440346981Scy	default:
1441346981Scy		wpa_printf(MSG_DEBUG, "ANQP: Unsupported WFA vendor type %u",
1442346981Scy			   *pos);
1443346981Scy		break;
1444346981Scy	}
1445346981Scy}
1446346981Scy
1447346981Scy
1448252190Srpaulostatic void gas_serv_req_local_processing(struct hostapd_data *hapd,
1449252190Srpaulo					  const u8 *sa, u8 dialog_token,
1450337817Scy					  struct anqp_query_info *qi, int prot,
1451337817Scy					  int std_addr3)
1452252190Srpaulo{
1453252190Srpaulo	struct wpabuf *buf, *tx_buf;
1454252190Srpaulo
1455281806Srpaulo	buf = gas_serv_build_gas_resp_payload(hapd, qi->request,
1456252190Srpaulo					      qi->home_realm_query,
1457281806Srpaulo					      qi->home_realm_query_len,
1458337817Scy					      qi->icon_name, qi->icon_name_len,
1459337817Scy					      qi->extra_req, qi->num_extra_req);
1460252190Srpaulo	wpa_hexdump_buf(MSG_MSGDUMP, "ANQP: Locally generated ANQP responses",
1461252190Srpaulo			buf);
1462252190Srpaulo	if (!buf)
1463252190Srpaulo		return;
1464281806Srpaulo#ifdef CONFIG_P2P
1465281806Srpaulo	if (wpabuf_len(buf) == 0 && qi->p2p_sd) {
1466281806Srpaulo		wpa_printf(MSG_DEBUG,
1467281806Srpaulo			   "ANQP: Do not send response to P2P SD from generic GAS service (P2P SD implementation will process this)");
1468281806Srpaulo		wpabuf_free(buf);
1469281806Srpaulo		return;
1470281806Srpaulo	}
1471281806Srpaulo#endif /* CONFIG_P2P */
1472252190Srpaulo
1473346981Scy	if (wpabuf_len(buf) > hapd->conf->gas_frag_limit ||
1474252190Srpaulo	    hapd->conf->gas_comeback_delay) {
1475252190Srpaulo		struct gas_dialog_info *di;
1476252190Srpaulo		u16 comeback_delay = 1;
1477252190Srpaulo
1478252190Srpaulo		if (hapd->conf->gas_comeback_delay) {
1479252190Srpaulo			/* Testing - allow overriding of the delay value */
1480252190Srpaulo			comeback_delay = hapd->conf->gas_comeback_delay;
1481252190Srpaulo		}
1482252190Srpaulo
1483252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Too long response to fit in "
1484252190Srpaulo			   "initial response - use GAS comeback");
1485252190Srpaulo		di = gas_dialog_create(hapd, sa, dialog_token);
1486252190Srpaulo		if (!di) {
1487252190Srpaulo			wpa_printf(MSG_INFO, "ANQP: Could not create dialog "
1488252190Srpaulo				   "for " MACSTR " (dialog token %u)",
1489252190Srpaulo				   MAC2STR(sa), dialog_token);
1490252190Srpaulo			wpabuf_free(buf);
1491281806Srpaulo			tx_buf = gas_anqp_build_initial_resp_buf(
1492281806Srpaulo				dialog_token, WLAN_STATUS_UNSPECIFIED_FAILURE,
1493281806Srpaulo				0, NULL);
1494281806Srpaulo		} else {
1495281806Srpaulo			di->prot = prot;
1496281806Srpaulo			di->sd_resp = buf;
1497281806Srpaulo			di->sd_resp_pos = 0;
1498281806Srpaulo			tx_buf = gas_anqp_build_initial_resp_buf(
1499281806Srpaulo				dialog_token, WLAN_STATUS_SUCCESS,
1500281806Srpaulo				comeback_delay, NULL);
1501252190Srpaulo		}
1502252190Srpaulo	} else {
1503252190Srpaulo		wpa_printf(MSG_DEBUG, "ANQP: Initial response (no comeback)");
1504252190Srpaulo		tx_buf = gas_anqp_build_initial_resp_buf(
1505252190Srpaulo			dialog_token, WLAN_STATUS_SUCCESS, 0, buf);
1506252190Srpaulo		wpabuf_free(buf);
1507252190Srpaulo	}
1508252190Srpaulo	if (!tx_buf)
1509252190Srpaulo		return;
1510281806Srpaulo	if (prot)
1511281806Srpaulo		convert_to_protected_dual(tx_buf);
1512337817Scy	if (std_addr3)
1513337817Scy		hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
1514337817Scy					wpabuf_head(tx_buf),
1515337817Scy					wpabuf_len(tx_buf));
1516337817Scy	else
1517337817Scy		hostapd_drv_send_action_addr3_ap(hapd, hapd->iface->freq, 0, sa,
1518337817Scy						 wpabuf_head(tx_buf),
1519337817Scy						 wpabuf_len(tx_buf));
1520252190Srpaulo	wpabuf_free(tx_buf);
1521252190Srpaulo}
1522252190Srpaulo
1523252190Srpaulo
1524346981Scy#ifdef CONFIG_DPP
1525351611Scyvoid gas_serv_req_dpp_processing(struct hostapd_data *hapd,
1526351611Scy				 const u8 *sa, u8 dialog_token,
1527351611Scy				 int prot, struct wpabuf *buf)
1528346981Scy{
1529346981Scy	struct wpabuf *tx_buf;
1530346981Scy
1531346981Scy	if (wpabuf_len(buf) > hapd->conf->gas_frag_limit ||
1532346981Scy	    hapd->conf->gas_comeback_delay) {
1533346981Scy		struct gas_dialog_info *di;
1534346981Scy		u16 comeback_delay = 1;
1535346981Scy
1536346981Scy		if (hapd->conf->gas_comeback_delay) {
1537346981Scy			/* Testing - allow overriding of the delay value */
1538346981Scy			comeback_delay = hapd->conf->gas_comeback_delay;
1539346981Scy		}
1540346981Scy
1541346981Scy		wpa_printf(MSG_DEBUG,
1542346981Scy			   "DPP: Too long response to fit in initial response - use GAS comeback");
1543346981Scy		di = gas_dialog_create(hapd, sa, dialog_token);
1544346981Scy		if (!di) {
1545346981Scy			wpa_printf(MSG_INFO, "DPP: Could not create dialog for "
1546346981Scy				   MACSTR " (dialog token %u)",
1547346981Scy				   MAC2STR(sa), dialog_token);
1548346981Scy			wpabuf_free(buf);
1549346981Scy			tx_buf = gas_build_initial_resp(
1550346981Scy				dialog_token, WLAN_STATUS_UNSPECIFIED_FAILURE,
1551346981Scy				0, 10);
1552346981Scy			if (tx_buf)
1553346981Scy				gas_serv_write_dpp_adv_proto(tx_buf);
1554346981Scy		} else {
1555346981Scy			di->prot = prot;
1556346981Scy			di->sd_resp = buf;
1557346981Scy			di->sd_resp_pos = 0;
1558346981Scy			tx_buf = gas_build_initial_resp(
1559346981Scy				dialog_token, WLAN_STATUS_SUCCESS,
1560346981Scy				comeback_delay, 10);
1561346981Scy			if (tx_buf)
1562346981Scy				gas_serv_write_dpp_adv_proto(tx_buf);
1563346981Scy		}
1564346981Scy	} else {
1565346981Scy		wpa_printf(MSG_DEBUG,
1566346981Scy			   "DPP: GAS Initial response (no comeback)");
1567346981Scy		tx_buf = gas_build_initial_resp(
1568346981Scy			dialog_token, WLAN_STATUS_SUCCESS, 0,
1569346981Scy			10 + 2 + wpabuf_len(buf));
1570346981Scy		if (tx_buf) {
1571346981Scy			gas_serv_write_dpp_adv_proto(tx_buf);
1572346981Scy			wpabuf_put_le16(tx_buf, wpabuf_len(buf));
1573346981Scy			wpabuf_put_buf(tx_buf, buf);
1574346981Scy			hostapd_dpp_gas_status_handler(hapd, 1);
1575346981Scy		}
1576346981Scy		wpabuf_free(buf);
1577346981Scy	}
1578346981Scy	if (!tx_buf)
1579346981Scy		return;
1580346981Scy	if (prot)
1581346981Scy		convert_to_protected_dual(tx_buf);
1582346981Scy	hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
1583346981Scy				wpabuf_head(tx_buf),
1584346981Scy				wpabuf_len(tx_buf));
1585346981Scy	wpabuf_free(tx_buf);
1586346981Scy}
1587346981Scy#endif /* CONFIG_DPP */
1588346981Scy
1589346981Scy
1590252190Srpaulostatic void gas_serv_rx_gas_initial_req(struct hostapd_data *hapd,
1591252190Srpaulo					const u8 *sa,
1592337817Scy					const u8 *data, size_t len, int prot,
1593337817Scy					int std_addr3)
1594252190Srpaulo{
1595252190Srpaulo	const u8 *pos = data;
1596252190Srpaulo	const u8 *end = data + len;
1597252190Srpaulo	const u8 *next;
1598252190Srpaulo	u8 dialog_token;
1599252190Srpaulo	u16 slen;
1600252190Srpaulo	struct anqp_query_info qi;
1601252190Srpaulo	const u8 *adv_proto;
1602346981Scy#ifdef CONFIG_DPP
1603346981Scy	int dpp = 0;
1604346981Scy#endif /* CONFIG_DPP */
1605252190Srpaulo
1606252190Srpaulo	if (len < 1 + 2)
1607252190Srpaulo		return;
1608252190Srpaulo
1609252190Srpaulo	os_memset(&qi, 0, sizeof(qi));
1610252190Srpaulo
1611252190Srpaulo	dialog_token = *pos++;
1612252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG,
1613252190Srpaulo		"GAS: GAS Initial Request from " MACSTR " (dialog token %u) ",
1614252190Srpaulo		MAC2STR(sa), dialog_token);
1615252190Srpaulo
1616252190Srpaulo	if (*pos != WLAN_EID_ADV_PROTO) {
1617252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
1618252190Srpaulo			"GAS: Unexpected IE in GAS Initial Request: %u", *pos);
1619252190Srpaulo		return;
1620252190Srpaulo	}
1621252190Srpaulo	adv_proto = pos++;
1622252190Srpaulo
1623252190Srpaulo	slen = *pos++;
1624337817Scy	if (slen > end - pos || slen < 2) {
1625252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
1626252190Srpaulo			"GAS: Invalid IE in GAS Initial Request");
1627252190Srpaulo		return;
1628252190Srpaulo	}
1629337817Scy	next = pos + slen;
1630252190Srpaulo	pos++; /* skip QueryRespLenLimit and PAME-BI */
1631252190Srpaulo
1632346981Scy#ifdef CONFIG_DPP
1633346981Scy	if (slen == 8 && *pos == WLAN_EID_VENDOR_SPECIFIC &&
1634346981Scy	    pos[1] == 5 && WPA_GET_BE24(&pos[2]) == OUI_WFA &&
1635346981Scy	    pos[5] == DPP_OUI_TYPE && pos[6] == 0x01) {
1636346981Scy		wpa_printf(MSG_DEBUG, "DPP: Configuration Request");
1637346981Scy		dpp = 1;
1638346981Scy	} else
1639346981Scy#endif /* CONFIG_DPP */
1640346981Scy
1641252190Srpaulo	if (*pos != ACCESS_NETWORK_QUERY_PROTOCOL) {
1642252190Srpaulo		struct wpabuf *buf;
1643252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG,
1644252190Srpaulo			"GAS: Unsupported GAS advertisement protocol id %u",
1645252190Srpaulo			*pos);
1646252190Srpaulo		if (sa[0] & 0x01)
1647252190Srpaulo			return; /* Invalid source address - drop silently */
1648252190Srpaulo		buf = gas_build_initial_resp(
1649252190Srpaulo			dialog_token, WLAN_STATUS_GAS_ADV_PROTO_NOT_SUPPORTED,
1650252190Srpaulo			0, 2 + slen + 2);
1651252190Srpaulo		if (buf == NULL)
1652252190Srpaulo			return;
1653252190Srpaulo		wpabuf_put_data(buf, adv_proto, 2 + slen);
1654252190Srpaulo		wpabuf_put_le16(buf, 0); /* Query Response Length */
1655281806Srpaulo		if (prot)
1656281806Srpaulo			convert_to_protected_dual(buf);
1657337817Scy		if (std_addr3)
1658337817Scy			hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
1659337817Scy						wpabuf_head(buf),
1660337817Scy						wpabuf_len(buf));
1661337817Scy		else
1662337817Scy			hostapd_drv_send_action_addr3_ap(hapd,
1663337817Scy							 hapd->iface->freq, 0,
1664337817Scy							 sa, wpabuf_head(buf),
1665337817Scy							 wpabuf_len(buf));
1666252190Srpaulo		wpabuf_free(buf);
1667252190Srpaulo		return;
1668252190Srpaulo	}
1669252190Srpaulo
1670252190Srpaulo	pos = next;
1671252190Srpaulo	/* Query Request */
1672337817Scy	if (end - pos < 2)
1673252190Srpaulo		return;
1674252190Srpaulo	slen = WPA_GET_LE16(pos);
1675252190Srpaulo	pos += 2;
1676337817Scy	if (slen > end - pos)
1677252190Srpaulo		return;
1678252190Srpaulo	end = pos + slen;
1679252190Srpaulo
1680346981Scy#ifdef CONFIG_DPP
1681346981Scy	if (dpp) {
1682346981Scy		struct wpabuf *msg;
1683346981Scy
1684351611Scy		msg = hostapd_dpp_gas_req_handler(hapd, sa, pos, slen,
1685351611Scy						  data, len);
1686346981Scy		if (!msg)
1687346981Scy			return;
1688346981Scy		gas_serv_req_dpp_processing(hapd, sa, dialog_token, prot, msg);
1689346981Scy		return;
1690346981Scy	}
1691346981Scy#endif /* CONFIG_DPP */
1692346981Scy
1693252190Srpaulo	/* ANQP Query Request */
1694252190Srpaulo	while (pos < end) {
1695252190Srpaulo		u16 info_id, elen;
1696252190Srpaulo
1697337817Scy		if (end - pos < 4)
1698252190Srpaulo			return;
1699252190Srpaulo
1700252190Srpaulo		info_id = WPA_GET_LE16(pos);
1701252190Srpaulo		pos += 2;
1702252190Srpaulo		elen = WPA_GET_LE16(pos);
1703252190Srpaulo		pos += 2;
1704252190Srpaulo
1705337817Scy		if (elen > end - pos) {
1706252190Srpaulo			wpa_printf(MSG_DEBUG, "ANQP: Invalid Query Request");
1707252190Srpaulo			return;
1708252190Srpaulo		}
1709252190Srpaulo
1710252190Srpaulo		switch (info_id) {
1711252190Srpaulo		case ANQP_QUERY_LIST:
1712252190Srpaulo			rx_anqp_query_list(hapd, pos, pos + elen, &qi);
1713252190Srpaulo			break;
1714252190Srpaulo		case ANQP_VENDOR_SPECIFIC:
1715252190Srpaulo			rx_anqp_vendor_specific(hapd, pos, pos + elen, &qi);
1716252190Srpaulo			break;
1717252190Srpaulo		default:
1718252190Srpaulo			wpa_printf(MSG_DEBUG, "ANQP: Unsupported Query "
1719252190Srpaulo				   "Request element %u", info_id);
1720252190Srpaulo			break;
1721252190Srpaulo		}
1722252190Srpaulo
1723252190Srpaulo		pos += elen;
1724252190Srpaulo	}
1725252190Srpaulo
1726337817Scy	gas_serv_req_local_processing(hapd, sa, dialog_token, &qi, prot,
1727337817Scy				      std_addr3);
1728252190Srpaulo}
1729252190Srpaulo
1730252190Srpaulo
1731252190Srpaulostatic void gas_serv_rx_gas_comeback_req(struct hostapd_data *hapd,
1732252190Srpaulo					 const u8 *sa,
1733337817Scy					 const u8 *data, size_t len, int prot,
1734337817Scy					 int std_addr3)
1735252190Srpaulo{
1736252190Srpaulo	struct gas_dialog_info *dialog;
1737252190Srpaulo	struct wpabuf *buf, *tx_buf;
1738252190Srpaulo	u8 dialog_token;
1739252190Srpaulo	size_t frag_len;
1740252190Srpaulo	int more = 0;
1741252190Srpaulo
1742252190Srpaulo	wpa_hexdump(MSG_DEBUG, "GAS: RX GAS Comeback Request", data, len);
1743252190Srpaulo	if (len < 1)
1744252190Srpaulo		return;
1745252190Srpaulo	dialog_token = *data;
1746252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Dialog Token: %u",
1747252190Srpaulo		dialog_token);
1748252190Srpaulo
1749252190Srpaulo	dialog = gas_serv_dialog_find(hapd, sa, dialog_token);
1750252190Srpaulo	if (!dialog) {
1751252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: No pending SD "
1752252190Srpaulo			"response fragment for " MACSTR " dialog token %u",
1753252190Srpaulo			MAC2STR(sa), dialog_token);
1754252190Srpaulo
1755252190Srpaulo		if (sa[0] & 0x01)
1756252190Srpaulo			return; /* Invalid source address - drop silently */
1757252190Srpaulo		tx_buf = gas_anqp_build_comeback_resp_buf(
1758252190Srpaulo			dialog_token, WLAN_STATUS_NO_OUTSTANDING_GAS_REQ, 0, 0,
1759252190Srpaulo			0, NULL);
1760252190Srpaulo		if (tx_buf == NULL)
1761252190Srpaulo			return;
1762252190Srpaulo		goto send_resp;
1763252190Srpaulo	}
1764252190Srpaulo
1765252190Srpaulo	frag_len = wpabuf_len(dialog->sd_resp) - dialog->sd_resp_pos;
1766346981Scy	if (frag_len > hapd->conf->gas_frag_limit) {
1767346981Scy		frag_len = hapd->conf->gas_frag_limit;
1768252190Srpaulo		more = 1;
1769252190Srpaulo	}
1770252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: resp frag_len %u",
1771252190Srpaulo		(unsigned int) frag_len);
1772252190Srpaulo	buf = wpabuf_alloc_copy(wpabuf_head_u8(dialog->sd_resp) +
1773252190Srpaulo				dialog->sd_resp_pos, frag_len);
1774252190Srpaulo	if (buf == NULL) {
1775252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Failed to allocate "
1776252190Srpaulo			"buffer");
1777281806Srpaulo		gas_serv_dialog_clear(dialog);
1778281806Srpaulo		return;
1779252190Srpaulo	}
1780346981Scy#ifdef CONFIG_DPP
1781346981Scy	if (dialog->dpp) {
1782346981Scy		tx_buf = gas_build_comeback_resp(dialog_token,
1783346981Scy						 WLAN_STATUS_SUCCESS,
1784346981Scy						 dialog->sd_frag_id, more, 0,
1785346981Scy						 10 + frag_len);
1786346981Scy		if (tx_buf) {
1787346981Scy			gas_serv_write_dpp_adv_proto(tx_buf);
1788346981Scy			wpabuf_put_buf(tx_buf, buf);
1789346981Scy		}
1790346981Scy	} else
1791346981Scy#endif /* CONFIG_DPP */
1792252190Srpaulo	tx_buf = gas_anqp_build_comeback_resp_buf(dialog_token,
1793252190Srpaulo						  WLAN_STATUS_SUCCESS,
1794252190Srpaulo						  dialog->sd_frag_id,
1795252190Srpaulo						  more, 0, buf);
1796252190Srpaulo	wpabuf_free(buf);
1797281806Srpaulo	if (tx_buf == NULL) {
1798281806Srpaulo		gas_serv_dialog_clear(dialog);
1799281806Srpaulo		return;
1800281806Srpaulo	}
1801252190Srpaulo	wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: Tx GAS Comeback Response "
1802252190Srpaulo		"(frag_id %d more=%d frag_len=%d)",
1803252190Srpaulo		dialog->sd_frag_id, more, (int) frag_len);
1804252190Srpaulo	dialog->sd_frag_id++;
1805252190Srpaulo	dialog->sd_resp_pos += frag_len;
1806252190Srpaulo
1807252190Srpaulo	if (more) {
1808252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: %d more bytes remain "
1809252190Srpaulo			"to be sent",
1810252190Srpaulo			(int) (wpabuf_len(dialog->sd_resp) -
1811252190Srpaulo			       dialog->sd_resp_pos));
1812252190Srpaulo	} else {
1813252190Srpaulo		wpa_msg(hapd->msg_ctx, MSG_DEBUG, "GAS: All fragments of "
1814252190Srpaulo			"SD response sent");
1815346981Scy#ifdef CONFIG_DPP
1816346981Scy		if (dialog->dpp)
1817346981Scy			hostapd_dpp_gas_status_handler(hapd, 1);
1818346981Scy#endif /* CONFIG_DPP */
1819252190Srpaulo		gas_serv_dialog_clear(dialog);
1820252190Srpaulo		gas_serv_free_dialogs(hapd, sa);
1821252190Srpaulo	}
1822252190Srpaulo
1823252190Srpaulosend_resp:
1824281806Srpaulo	if (prot)
1825281806Srpaulo		convert_to_protected_dual(tx_buf);
1826337817Scy	if (std_addr3)
1827337817Scy		hostapd_drv_send_action(hapd, hapd->iface->freq, 0, sa,
1828337817Scy					wpabuf_head(tx_buf),
1829337817Scy					wpabuf_len(tx_buf));
1830337817Scy	else
1831337817Scy		hostapd_drv_send_action_addr3_ap(hapd, hapd->iface->freq, 0, sa,
1832337817Scy						 wpabuf_head(tx_buf),
1833337817Scy						 wpabuf_len(tx_buf));
1834252190Srpaulo	wpabuf_free(tx_buf);
1835252190Srpaulo}
1836252190Srpaulo
1837252190Srpaulo
1838252190Srpaulostatic void gas_serv_rx_public_action(void *ctx, const u8 *buf, size_t len,
1839252190Srpaulo				      int freq)
1840252190Srpaulo{
1841252190Srpaulo	struct hostapd_data *hapd = ctx;
1842252190Srpaulo	const struct ieee80211_mgmt *mgmt;
1843252190Srpaulo	const u8 *sa, *data;
1844337817Scy	int prot, std_addr3;
1845252190Srpaulo
1846252190Srpaulo	mgmt = (const struct ieee80211_mgmt *) buf;
1847281806Srpaulo	if (len < IEEE80211_HDRLEN + 2)
1848252190Srpaulo		return;
1849281806Srpaulo	if (mgmt->u.action.category != WLAN_ACTION_PUBLIC &&
1850281806Srpaulo	    mgmt->u.action.category != WLAN_ACTION_PROTECTED_DUAL)
1851252190Srpaulo		return;
1852281806Srpaulo	/*
1853281806Srpaulo	 * Note: Public Action and Protected Dual of Public Action frames share
1854281806Srpaulo	 * the same payload structure, so it is fine to use definitions of
1855281806Srpaulo	 * Public Action frames to process both.
1856281806Srpaulo	 */
1857281806Srpaulo	prot = mgmt->u.action.category == WLAN_ACTION_PROTECTED_DUAL;
1858252190Srpaulo	sa = mgmt->sa;
1859337817Scy	if (hapd->conf->gas_address3 == 1)
1860337817Scy		std_addr3 = 1;
1861337817Scy	else if (hapd->conf->gas_address3 == 2)
1862337817Scy		std_addr3 = 0;
1863337817Scy	else
1864337817Scy		std_addr3 = is_broadcast_ether_addr(mgmt->bssid);
1865281806Srpaulo	len -= IEEE80211_HDRLEN + 1;
1866281806Srpaulo	data = buf + IEEE80211_HDRLEN + 1;
1867252190Srpaulo	switch (data[0]) {
1868252190Srpaulo	case WLAN_PA_GAS_INITIAL_REQ:
1869337817Scy		gas_serv_rx_gas_initial_req(hapd, sa, data + 1, len - 1, prot,
1870337817Scy					    std_addr3);
1871252190Srpaulo		break;
1872252190Srpaulo	case WLAN_PA_GAS_COMEBACK_REQ:
1873337817Scy		gas_serv_rx_gas_comeback_req(hapd, sa, data + 1, len - 1, prot,
1874337817Scy					     std_addr3);
1875252190Srpaulo		break;
1876252190Srpaulo	}
1877252190Srpaulo}
1878252190Srpaulo
1879252190Srpaulo
1880252190Srpauloint gas_serv_init(struct hostapd_data *hapd)
1881252190Srpaulo{
1882281806Srpaulo	hapd->public_action_cb2 = gas_serv_rx_public_action;
1883281806Srpaulo	hapd->public_action_cb2_ctx = hapd;
1884252190Srpaulo	return 0;
1885252190Srpaulo}
1886252190Srpaulo
1887252190Srpaulo
1888252190Srpaulovoid gas_serv_deinit(struct hostapd_data *hapd)
1889252190Srpaulo{
1890252190Srpaulo}
1891