1252190Srpaulo/*
2252190Srpaulo * Generic advertisement service (GAS) (IEEE 802.11u)
3252190Srpaulo * Copyright (c) 2009, Atheros Communications
4252190Srpaulo * Copyright (c) 2011-2012, Qualcomm Atheros
5252190Srpaulo *
6252190Srpaulo * This software may be distributed under the terms of the BSD license.
7252190Srpaulo * See README for more details.
8252190Srpaulo */
9252190Srpaulo
10252190Srpaulo#include "includes.h"
11252190Srpaulo
12252190Srpaulo#include "common.h"
13252190Srpaulo#include "ieee802_11_defs.h"
14252190Srpaulo#include "gas.h"
15252190Srpaulo
16252190Srpaulo
17252190Srpaulostatic struct wpabuf *
18252190Srpaulogas_build_req(u8 action, u8 dialog_token, size_t size)
19252190Srpaulo{
20252190Srpaulo	struct wpabuf *buf;
21252190Srpaulo
22252190Srpaulo	buf = wpabuf_alloc(100 + size);
23252190Srpaulo	if (buf == NULL)
24252190Srpaulo		return NULL;
25252190Srpaulo
26252190Srpaulo	wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC);
27252190Srpaulo	wpabuf_put_u8(buf, action);
28252190Srpaulo	wpabuf_put_u8(buf, dialog_token);
29252190Srpaulo
30252190Srpaulo	return buf;
31252190Srpaulo}
32252190Srpaulo
33252190Srpaulo
34252190Srpaulostruct wpabuf * gas_build_initial_req(u8 dialog_token, size_t size)
35252190Srpaulo{
36252190Srpaulo	return gas_build_req(WLAN_PA_GAS_INITIAL_REQ, dialog_token,
37252190Srpaulo			     size);
38252190Srpaulo}
39252190Srpaulo
40252190Srpaulo
41252190Srpaulostruct wpabuf * gas_build_comeback_req(u8 dialog_token)
42252190Srpaulo{
43252190Srpaulo	return gas_build_req(WLAN_PA_GAS_COMEBACK_REQ, dialog_token, 0);
44252190Srpaulo}
45252190Srpaulo
46252190Srpaulo
47252190Srpaulostatic struct wpabuf *
48252190Srpaulogas_build_resp(u8 action, u8 dialog_token, u16 status_code, u8 frag_id,
49252190Srpaulo	       u8 more, u16 comeback_delay, size_t size)
50252190Srpaulo{
51252190Srpaulo	struct wpabuf *buf;
52252190Srpaulo
53252190Srpaulo	buf = wpabuf_alloc(100 + size);
54252190Srpaulo	if (buf == NULL)
55252190Srpaulo		return NULL;
56252190Srpaulo
57252190Srpaulo	wpabuf_put_u8(buf, WLAN_ACTION_PUBLIC);
58252190Srpaulo	wpabuf_put_u8(buf, action);
59252190Srpaulo	wpabuf_put_u8(buf, dialog_token);
60252190Srpaulo	wpabuf_put_le16(buf, status_code);
61252190Srpaulo	if (action == WLAN_PA_GAS_COMEBACK_RESP)
62252190Srpaulo		wpabuf_put_u8(buf, frag_id | (more ? 0x80 : 0));
63252190Srpaulo	wpabuf_put_le16(buf, comeback_delay);
64252190Srpaulo
65252190Srpaulo	return buf;
66252190Srpaulo}
67252190Srpaulo
68252190Srpaulo
69252190Srpaulostruct wpabuf *
70252190Srpaulogas_build_initial_resp(u8 dialog_token, u16 status_code, u16 comeback_delay,
71252190Srpaulo		       size_t size)
72252190Srpaulo{
73252190Srpaulo	return gas_build_resp(WLAN_PA_GAS_INITIAL_RESP, dialog_token,
74252190Srpaulo			      status_code, 0, 0, comeback_delay, size);
75252190Srpaulo}
76252190Srpaulo
77252190Srpaulo
78346981Scystruct wpabuf *
79252190Srpaulogas_build_comeback_resp(u8 dialog_token, u16 status_code, u8 frag_id, u8 more,
80252190Srpaulo			u16 comeback_delay, size_t size)
81252190Srpaulo{
82252190Srpaulo	return gas_build_resp(WLAN_PA_GAS_COMEBACK_RESP, dialog_token,
83252190Srpaulo			      status_code, frag_id, more, comeback_delay,
84252190Srpaulo			      size);
85252190Srpaulo}
86252190Srpaulo
87252190Srpaulo
88252190Srpaulo/**
89252190Srpaulo * gas_add_adv_proto_anqp - Add an Advertisement Protocol element
90252190Srpaulo * @buf: Buffer to which the element is added
91252190Srpaulo * @query_resp_len_limit: Query Response Length Limit in units of 256 octets
92252190Srpaulo * @pame_bi: Pre-Association Message Exchange BSSID Independent (0/1)
93252190Srpaulo *
94252190Srpaulo *
95252190Srpaulo * @query_resp_len_limit is 0 for request and 1-0x7f for response. 0x7f means
96252190Srpaulo * that the maximum limit is determined by the maximum allowable number of
97252190Srpaulo * fragments in the GAS Query Response Fragment ID.
98252190Srpaulo */
99252190Srpaulostatic void gas_add_adv_proto_anqp(struct wpabuf *buf, u8 query_resp_len_limit,
100252190Srpaulo				   u8 pame_bi)
101252190Srpaulo{
102252190Srpaulo	/* Advertisement Protocol IE */
103252190Srpaulo	wpabuf_put_u8(buf, WLAN_EID_ADV_PROTO);
104252190Srpaulo	wpabuf_put_u8(buf, 2); /* Length */
105252190Srpaulo	wpabuf_put_u8(buf, (query_resp_len_limit & 0x7f) |
106252190Srpaulo		      (pame_bi ? 0x80 : 0));
107252190Srpaulo	/* Advertisement Protocol */
108252190Srpaulo	wpabuf_put_u8(buf, ACCESS_NETWORK_QUERY_PROTOCOL);
109252190Srpaulo}
110252190Srpaulo
111252190Srpaulo
112252190Srpaulostruct wpabuf * gas_anqp_build_initial_req(u8 dialog_token, size_t size)
113252190Srpaulo{
114252190Srpaulo	struct wpabuf *buf;
115252190Srpaulo
116252190Srpaulo	buf = gas_build_initial_req(dialog_token, 4 + size);
117252190Srpaulo	if (buf == NULL)
118252190Srpaulo		return NULL;
119252190Srpaulo
120252190Srpaulo	gas_add_adv_proto_anqp(buf, 0, 0);
121252190Srpaulo
122252190Srpaulo	wpabuf_put(buf, 2); /* Query Request Length to be filled */
123252190Srpaulo
124252190Srpaulo	return buf;
125252190Srpaulo}
126252190Srpaulo
127252190Srpaulo
128252190Srpaulostruct wpabuf * gas_anqp_build_initial_resp(u8 dialog_token, u16 status_code,
129252190Srpaulo					    u16 comeback_delay, size_t size)
130252190Srpaulo{
131252190Srpaulo	struct wpabuf *buf;
132252190Srpaulo
133252190Srpaulo	buf = gas_build_initial_resp(dialog_token, status_code, comeback_delay,
134252190Srpaulo				     4 + size);
135252190Srpaulo	if (buf == NULL)
136252190Srpaulo		return NULL;
137252190Srpaulo
138252190Srpaulo	gas_add_adv_proto_anqp(buf, 0x7f, 0);
139252190Srpaulo
140252190Srpaulo	wpabuf_put(buf, 2); /* Query Response Length to be filled */
141252190Srpaulo
142252190Srpaulo	return buf;
143252190Srpaulo}
144252190Srpaulo
145252190Srpaulo
146252190Srpaulostruct wpabuf * gas_anqp_build_initial_resp_buf(u8 dialog_token,
147252190Srpaulo						u16 status_code,
148252190Srpaulo						u16 comeback_delay,
149252190Srpaulo						struct wpabuf *payload)
150252190Srpaulo{
151252190Srpaulo	struct wpabuf *buf;
152252190Srpaulo
153252190Srpaulo	buf = gas_anqp_build_initial_resp(dialog_token, status_code,
154252190Srpaulo					  comeback_delay,
155252190Srpaulo					  payload ? wpabuf_len(payload) : 0);
156252190Srpaulo	if (buf == NULL)
157252190Srpaulo		return NULL;
158252190Srpaulo
159252190Srpaulo	if (payload)
160252190Srpaulo		wpabuf_put_buf(buf, payload);
161252190Srpaulo
162252190Srpaulo	gas_anqp_set_len(buf);
163252190Srpaulo
164252190Srpaulo	return buf;
165252190Srpaulo}
166252190Srpaulo
167252190Srpaulo
168252190Srpaulostruct wpabuf * gas_anqp_build_comeback_resp(u8 dialog_token, u16 status_code,
169252190Srpaulo					     u8 frag_id, u8 more,
170252190Srpaulo					     u16 comeback_delay, size_t size)
171252190Srpaulo{
172252190Srpaulo	struct wpabuf *buf;
173252190Srpaulo
174252190Srpaulo	buf = gas_build_comeback_resp(dialog_token, status_code,
175252190Srpaulo				      frag_id, more, comeback_delay, 4 + size);
176252190Srpaulo	if (buf == NULL)
177252190Srpaulo		return NULL;
178252190Srpaulo
179252190Srpaulo	gas_add_adv_proto_anqp(buf, 0x7f, 0);
180252190Srpaulo
181252190Srpaulo	wpabuf_put(buf, 2); /* Query Response Length to be filled */
182252190Srpaulo
183252190Srpaulo	return buf;
184252190Srpaulo}
185252190Srpaulo
186252190Srpaulo
187252190Srpaulostruct wpabuf * gas_anqp_build_comeback_resp_buf(u8 dialog_token,
188252190Srpaulo						 u16 status_code,
189252190Srpaulo						 u8 frag_id, u8 more,
190252190Srpaulo						 u16 comeback_delay,
191252190Srpaulo						 struct wpabuf *payload)
192252190Srpaulo{
193252190Srpaulo	struct wpabuf *buf;
194252190Srpaulo
195252190Srpaulo	buf = gas_anqp_build_comeback_resp(dialog_token, status_code, frag_id,
196252190Srpaulo					   more, comeback_delay,
197252190Srpaulo					   payload ? wpabuf_len(payload) : 0);
198252190Srpaulo	if (buf == NULL)
199252190Srpaulo		return NULL;
200252190Srpaulo
201252190Srpaulo	if (payload)
202252190Srpaulo		wpabuf_put_buf(buf, payload);
203252190Srpaulo
204252190Srpaulo	gas_anqp_set_len(buf);
205252190Srpaulo
206252190Srpaulo	return buf;
207252190Srpaulo}
208252190Srpaulo
209252190Srpaulo
210252190Srpaulo/**
211252190Srpaulo * gas_anqp_set_len - Set Query Request/Response Length
212252190Srpaulo * @buf: GAS message
213252190Srpaulo *
214252190Srpaulo * This function is used to update the Query Request/Response Length field once
215252190Srpaulo * the payload has been filled.
216252190Srpaulo */
217252190Srpaulovoid gas_anqp_set_len(struct wpabuf *buf)
218252190Srpaulo{
219252190Srpaulo	u8 action;
220252190Srpaulo	size_t offset;
221252190Srpaulo	u8 *len;
222252190Srpaulo
223252190Srpaulo	if (buf == NULL || wpabuf_len(buf) < 2)
224252190Srpaulo		return;
225252190Srpaulo
226252190Srpaulo	action = *(wpabuf_head_u8(buf) + 1);
227252190Srpaulo	switch (action) {
228252190Srpaulo	case WLAN_PA_GAS_INITIAL_REQ:
229252190Srpaulo		offset = 3 + 4;
230252190Srpaulo		break;
231252190Srpaulo	case WLAN_PA_GAS_INITIAL_RESP:
232252190Srpaulo		offset = 7 + 4;
233252190Srpaulo		break;
234252190Srpaulo	case WLAN_PA_GAS_COMEBACK_RESP:
235252190Srpaulo		offset = 8 + 4;
236252190Srpaulo		break;
237252190Srpaulo	default:
238252190Srpaulo		return;
239252190Srpaulo	}
240252190Srpaulo
241252190Srpaulo	if (wpabuf_len(buf) < offset + 2)
242252190Srpaulo		return;
243252190Srpaulo
244252190Srpaulo	len = wpabuf_mhead_u8(buf) + offset;
245252190Srpaulo	WPA_PUT_LE16(len, (u8 *) wpabuf_put(buf, 0) - len - 2);
246252190Srpaulo}
247252190Srpaulo
248252190Srpaulo
249252190Srpaulo/**
250252190Srpaulo * gas_anqp_add_element - Add ANQP element header
251252190Srpaulo * @buf: GAS message
252252190Srpaulo * @info_id: ANQP Info ID
253252190Srpaulo * Returns: Pointer to the Length field for gas_anqp_set_element_len()
254252190Srpaulo */
255252190Srpaulou8 * gas_anqp_add_element(struct wpabuf *buf, u16 info_id)
256252190Srpaulo{
257252190Srpaulo	wpabuf_put_le16(buf, info_id);
258252190Srpaulo	return wpabuf_put(buf, 2); /* Length to be filled */
259252190Srpaulo}
260252190Srpaulo
261252190Srpaulo
262252190Srpaulo/**
263252190Srpaulo * gas_anqp_set_element_len - Update ANQP element Length field
264252190Srpaulo * @buf: GAS message
265252190Srpaulo * @len_pos: Length field position from gas_anqp_add_element()
266252190Srpaulo *
267252190Srpaulo * This function is called after the ANQP element payload has been added to the
268252190Srpaulo * buffer.
269252190Srpaulo */
270252190Srpaulovoid gas_anqp_set_element_len(struct wpabuf *buf, u8 *len_pos)
271252190Srpaulo{
272252190Srpaulo	WPA_PUT_LE16(len_pos, (u8 *) wpabuf_put(buf, 0) - len_pos - 2);
273252190Srpaulo}
274