1/*
2 * hostapd - MBO
3 * Copyright (c) 2016, Qualcomm Atheros, Inc.
4 *
5 * This software may be distributed under the terms of the BSD license.
6 * See README for more details.
7 */
8
9#include "utils/includes.h"
10
11#include "utils/common.h"
12#include "common/ieee802_11_defs.h"
13#include "common/ieee802_11_common.h"
14#include "hostapd.h"
15#include "sta_info.h"
16#include "mbo_ap.h"
17
18
19void mbo_ap_sta_free(struct sta_info *sta)
20{
21	struct mbo_non_pref_chan_info *info, *prev;
22
23	info = sta->non_pref_chan;
24	sta->non_pref_chan = NULL;
25	while (info) {
26		prev = info;
27		info = info->next;
28		os_free(prev);
29	}
30}
31
32
33static void mbo_ap_parse_non_pref_chan(struct sta_info *sta,
34				       const u8 *buf, size_t len)
35{
36	struct mbo_non_pref_chan_info *info, *tmp;
37	char channels[200], *pos, *end;
38	size_t num_chan, i;
39	int ret;
40
41	if (len <= 3)
42		return; /* Not enough room for any channels */
43
44	num_chan = len - 3;
45	info = os_zalloc(sizeof(*info) + num_chan);
46	if (!info)
47		return;
48	info->op_class = buf[0];
49	info->pref = buf[len - 2];
50	info->reason_code = buf[len - 1];
51	info->num_channels = num_chan;
52	buf++;
53	os_memcpy(info->channels, buf, num_chan);
54	if (!sta->non_pref_chan) {
55		sta->non_pref_chan = info;
56	} else {
57		tmp = sta->non_pref_chan;
58		while (tmp->next)
59			tmp = tmp->next;
60		tmp->next = info;
61	}
62
63	pos = channels;
64	end = pos + sizeof(channels);
65	*pos = '\0';
66	for (i = 0; i < num_chan; i++) {
67		ret = os_snprintf(pos, end - pos, "%s%u",
68				  i == 0 ? "" : " ", buf[i]);
69		if (os_snprintf_error(end - pos, ret)) {
70			*pos = '\0';
71			break;
72		}
73		pos += ret;
74	}
75
76	wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
77		   " non-preferred channel list (op class %u, pref %u, reason code %u, channels %s)",
78		   MAC2STR(sta->addr), info->op_class, info->pref,
79		   info->reason_code, channels);
80}
81
82
83void mbo_ap_check_sta_assoc(struct hostapd_data *hapd, struct sta_info *sta,
84			    struct ieee802_11_elems *elems)
85{
86	const u8 *pos, *attr, *end;
87	size_t len;
88
89	if (!hapd->conf->mbo_enabled || !elems->mbo)
90		return;
91
92	pos = elems->mbo + 4;
93	len = elems->mbo_len - 4;
94	wpa_hexdump(MSG_DEBUG, "MBO: Association Request attributes", pos, len);
95
96	attr = get_ie(pos, len, MBO_ATTR_ID_CELL_DATA_CAPA);
97	if (attr && attr[1] >= 1)
98		sta->cell_capa = attr[2];
99
100	mbo_ap_sta_free(sta);
101	end = pos + len;
102	while (end - pos > 1) {
103		u8 ie_len = pos[1];
104
105		if (2 + ie_len > end - pos)
106			break;
107
108		if (pos[0] == MBO_ATTR_ID_NON_PREF_CHAN_REPORT)
109			mbo_ap_parse_non_pref_chan(sta, pos + 2, ie_len);
110		pos += 2 + pos[1];
111	}
112}
113
114
115int mbo_ap_get_info(struct sta_info *sta, char *buf, size_t buflen)
116{
117	char *pos = buf, *end = buf + buflen;
118	int ret;
119	struct mbo_non_pref_chan_info *info;
120	u8 i;
121	unsigned int count = 0;
122
123	if (!sta->cell_capa)
124		return 0;
125
126	ret = os_snprintf(pos, end - pos, "mbo_cell_capa=%u\n", sta->cell_capa);
127	if (os_snprintf_error(end - pos, ret))
128		return pos - buf;
129	pos += ret;
130
131	for (info = sta->non_pref_chan; info; info = info->next) {
132		char *pos2 = pos;
133
134		ret = os_snprintf(pos2, end - pos2,
135				  "non_pref_chan[%u]=%u:%u:%u:",
136				  count, info->op_class, info->pref,
137				  info->reason_code);
138		count++;
139		if (os_snprintf_error(end - pos2, ret))
140			break;
141		pos2 += ret;
142
143		for (i = 0; i < info->num_channels; i++) {
144			ret = os_snprintf(pos2, end - pos2, "%u%s",
145					  info->channels[i],
146					  i + 1 < info->num_channels ?
147					  "," : "");
148			if (os_snprintf_error(end - pos2, ret)) {
149				pos2 = NULL;
150				break;
151			}
152			pos2 += ret;
153		}
154
155		if (!pos2)
156			break;
157		ret = os_snprintf(pos2, end - pos2, "\n");
158		if (os_snprintf_error(end - pos2, ret))
159			break;
160		pos2 += ret;
161		pos = pos2;
162	}
163
164	return pos - buf;
165}
166
167
168static void mbo_ap_wnm_notif_req_cell_capa(struct sta_info *sta,
169					   const u8 *buf, size_t len)
170{
171	if (len < 1)
172		return;
173	wpa_printf(MSG_DEBUG, "MBO: STA " MACSTR
174		   " updated cellular data capability: %u",
175		   MAC2STR(sta->addr), buf[0]);
176	sta->cell_capa = buf[0];
177}
178
179
180static void mbo_ap_wnm_notif_req_elem(struct sta_info *sta, u8 type,
181				      const u8 *buf, size_t len,
182				      int *first_non_pref_chan)
183{
184	switch (type) {
185	case WFA_WNM_NOTIF_SUBELEM_NON_PREF_CHAN_REPORT:
186		if (*first_non_pref_chan) {
187			/*
188			 * Need to free the previously stored entries now to
189			 * allow the update to replace all entries.
190			 */
191			*first_non_pref_chan = 0;
192			mbo_ap_sta_free(sta);
193		}
194		mbo_ap_parse_non_pref_chan(sta, buf, len);
195		break;
196	case WFA_WNM_NOTIF_SUBELEM_CELL_DATA_CAPA:
197		mbo_ap_wnm_notif_req_cell_capa(sta, buf, len);
198		break;
199	default:
200		wpa_printf(MSG_DEBUG,
201			   "MBO: Ignore unknown WNM Notification WFA subelement %u",
202			   type);
203		break;
204	}
205}
206
207
208void mbo_ap_wnm_notification_req(struct hostapd_data *hapd, const u8 *addr,
209				 const u8 *buf, size_t len)
210{
211	const u8 *pos, *end;
212	u8 ie_len;
213	struct sta_info *sta;
214	int first_non_pref_chan = 1;
215
216	if (!hapd->conf->mbo_enabled)
217		return;
218
219	sta = ap_get_sta(hapd, addr);
220	if (!sta)
221		return;
222
223	pos = buf;
224	end = buf + len;
225
226	while (end - pos > 1) {
227		ie_len = pos[1];
228
229		if (2 + ie_len > end - pos)
230			break;
231
232		if (pos[0] == WLAN_EID_VENDOR_SPECIFIC &&
233		    ie_len >= 4 && WPA_GET_BE24(pos + 2) == OUI_WFA)
234			mbo_ap_wnm_notif_req_elem(sta, pos[5],
235						  pos + 6, ie_len - 4,
236						  &first_non_pref_chan);
237		else
238			wpa_printf(MSG_DEBUG,
239				   "MBO: Ignore unknown WNM Notification element %u (len=%u)",
240				   pos[0], pos[1]);
241
242		pos += 2 + pos[1];
243	}
244}
245