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