1289284Srpaulo/*
2289284Srpaulo * FST module - FST Session implementation
3289284Srpaulo * Copyright (c) 2014, Qualcomm Atheros, Inc.
4289284Srpaulo *
5289284Srpaulo * This software may be distributed under the terms of the BSD license.
6289284Srpaulo * See README for more details.
7289284Srpaulo */
8289284Srpaulo
9289284Srpaulo#include "utils/includes.h"
10289284Srpaulo
11289284Srpaulo#include "utils/common.h"
12289284Srpaulo#include "utils/eloop.h"
13289284Srpaulo#include "common/defs.h"
14289284Srpaulo#include "fst/fst_internal.h"
15289284Srpaulo#include "fst/fst_defs.h"
16289284Srpaulo#include "fst/fst_ctrl_iface.h"
17289284Srpaulo#ifdef CONFIG_FST_TEST
18289284Srpaulo#include "fst/fst_ctrl_defs.h"
19289284Srpaulo#endif /* CONFIG_FST_TEST */
20289284Srpaulo
21289284Srpaulo#define US_80211_TU 1024
22289284Srpaulo
23289284Srpaulo#define US_TO_TU(m) ((m) * / US_80211_TU)
24289284Srpaulo#define TU_TO_US(m) ((m) * US_80211_TU)
25289284Srpaulo
26289284Srpaulo#define FST_LLT_SWITCH_IMMEDIATELY 0
27289284Srpaulo
28289284Srpaulo#define fst_printf_session(s, level, format, ...) \
29289284Srpaulo	fst_printf((level), "%u (0x%08x): [" MACSTR "," MACSTR "] :" format, \
30289284Srpaulo		   (s)->id, (s)->data.fsts_id, \
31289284Srpaulo		   MAC2STR((s)->data.old_peer_addr), \
32289284Srpaulo		   MAC2STR((s)->data.new_peer_addr), \
33289284Srpaulo		   ##__VA_ARGS__)
34289284Srpaulo
35289284Srpaulo#define fst_printf_siface(s, iface, level, format, ...) \
36289284Srpaulo	fst_printf_session((s), (level), "%s: " format, \
37289284Srpaulo			   fst_iface_get_name(iface), ##__VA_ARGS__)
38289284Srpaulo
39289284Srpaulo#define fst_printf_sframe(s, is_old, level, format, ...) \
40289284Srpaulo	fst_printf_siface((s), \
41289284Srpaulo		(is_old) ? (s)->data.old_iface : (s)->data.new_iface, \
42289284Srpaulo		(level), format, ##__VA_ARGS__)
43289284Srpaulo
44289284Srpaulo#define FST_LLT_MS_DEFAULT 50
45289284Srpaulo#define FST_ACTION_MAX_SUPPORTED   FST_ACTION_ON_CHANNEL_TUNNEL
46289284Srpaulo
47337817Scystatic const char * const fst_action_names[] = {
48289284Srpaulo	[FST_ACTION_SETUP_REQUEST]     = "Setup Request",
49289284Srpaulo	[FST_ACTION_SETUP_RESPONSE]    = "Setup Response",
50289284Srpaulo	[FST_ACTION_TEAR_DOWN]         = "Tear Down",
51289284Srpaulo	[FST_ACTION_ACK_REQUEST]       = "Ack Request",
52289284Srpaulo	[FST_ACTION_ACK_RESPONSE]      = "Ack Response",
53289284Srpaulo	[FST_ACTION_ON_CHANNEL_TUNNEL] = "On Channel Tunnel",
54289284Srpaulo};
55289284Srpaulo
56289284Srpaulostruct fst_session {
57289284Srpaulo	struct {
58289284Srpaulo		/* Session configuration that can be zeroed on reset */
59289284Srpaulo		u8 old_peer_addr[ETH_ALEN];
60289284Srpaulo		u8 new_peer_addr[ETH_ALEN];
61289284Srpaulo		struct fst_iface *new_iface;
62289284Srpaulo		struct fst_iface *old_iface;
63289284Srpaulo		u32 llt_ms;
64289284Srpaulo		u8 pending_setup_req_dlgt;
65289284Srpaulo		u32 fsts_id; /* FSTS ID, see spec, 8.4.2.147
66289284Srpaulo			      * Session Transition element */
67289284Srpaulo	} data;
68289284Srpaulo	/* Session object internal fields which won't be zeroed on reset */
69289284Srpaulo	struct dl_list global_sessions_lentry;
70289284Srpaulo	u32 id; /* Session object ID used to identify
71289284Srpaulo		 * specific session object */
72289284Srpaulo	struct fst_group *group;
73289284Srpaulo	enum fst_session_state state;
74289284Srpaulo	Boolean stt_armed;
75289284Srpaulo};
76289284Srpaulo
77289284Srpaulostatic struct dl_list global_sessions_list;
78289284Srpaulostatic u32 global_session_id = 0;
79289284Srpaulo
80289284Srpaulo#define foreach_fst_session(s) \
81289284Srpaulo	dl_list_for_each((s), &global_sessions_list, \
82289284Srpaulo			 struct fst_session, global_sessions_lentry)
83289284Srpaulo
84289284Srpaulo#define foreach_fst_session_safe(s, temp) \
85289284Srpaulo	dl_list_for_each_safe((s), (temp), &global_sessions_list, \
86289284Srpaulo			      struct fst_session, global_sessions_lentry)
87289284Srpaulo
88289284Srpaulo
89289284Srpaulostatic void fst_session_global_inc_id(void)
90289284Srpaulo{
91289284Srpaulo	global_session_id++;
92289284Srpaulo	if (global_session_id == FST_INVALID_SESSION_ID)
93289284Srpaulo		global_session_id++;
94289284Srpaulo}
95289284Srpaulo
96289284Srpaulo
97289284Srpauloint fst_session_global_init(void)
98289284Srpaulo{
99289284Srpaulo	dl_list_init(&global_sessions_list);
100289284Srpaulo	return 0;
101289284Srpaulo}
102289284Srpaulo
103289284Srpaulo
104289284Srpaulovoid fst_session_global_deinit(void)
105289284Srpaulo{
106289284Srpaulo	WPA_ASSERT(dl_list_empty(&global_sessions_list));
107289284Srpaulo}
108289284Srpaulo
109289284Srpaulo
110289284Srpaulostatic inline void fst_session_notify_ctrl(struct fst_session *s,
111289284Srpaulo					   enum fst_event_type event_type,
112289284Srpaulo					   union fst_event_extra *extra)
113289284Srpaulo{
114289284Srpaulo	foreach_fst_ctrl_call(on_event, event_type, NULL, s, extra);
115289284Srpaulo}
116289284Srpaulo
117289284Srpaulo
118289284Srpaulostatic void fst_session_set_state(struct fst_session *s,
119289284Srpaulo				  enum fst_session_state state,
120289284Srpaulo				  union fst_session_state_switch_extra *extra)
121289284Srpaulo{
122289284Srpaulo	if (s->state != state) {
123289284Srpaulo		union fst_event_extra evext = {
124289284Srpaulo			.session_state = {
125289284Srpaulo				.old_state = s->state,
126289284Srpaulo				.new_state = state,
127289284Srpaulo			},
128289284Srpaulo		};
129289284Srpaulo
130289284Srpaulo		if (extra)
131289284Srpaulo			evext.session_state.extra = *extra;
132289284Srpaulo		fst_session_notify_ctrl(s, EVENT_FST_SESSION_STATE_CHANGED,
133289284Srpaulo					&evext);
134289284Srpaulo		fst_printf_session(s, MSG_INFO, "State: %s => %s",
135289284Srpaulo				   fst_session_state_name(s->state),
136289284Srpaulo				   fst_session_state_name(state));
137289284Srpaulo		s->state = state;
138289284Srpaulo	}
139289284Srpaulo}
140289284Srpaulo
141289284Srpaulo
142289284Srpaulostatic u32 fst_find_free_session_id(void)
143289284Srpaulo{
144289284Srpaulo	u32 i, id = FST_INVALID_SESSION_ID;
145289284Srpaulo	struct fst_session *s;
146289284Srpaulo
147289284Srpaulo	for (i = 0; i < (u32) -1; i++) {
148289284Srpaulo		Boolean in_use = FALSE;
149289284Srpaulo
150289284Srpaulo		foreach_fst_session(s) {
151289284Srpaulo			if (s->id == global_session_id) {
152289284Srpaulo				fst_session_global_inc_id();
153289284Srpaulo				in_use = TRUE;
154289284Srpaulo				break;
155289284Srpaulo			}
156289284Srpaulo		}
157289284Srpaulo		if (!in_use) {
158289284Srpaulo			id = global_session_id;
159289284Srpaulo			fst_session_global_inc_id();
160289284Srpaulo			break;
161289284Srpaulo		}
162289284Srpaulo	}
163289284Srpaulo
164289284Srpaulo	return id;
165289284Srpaulo}
166289284Srpaulo
167289284Srpaulo
168289284Srpaulostatic void fst_session_timeout_handler(void *eloop_data, void *user_ctx)
169289284Srpaulo{
170289284Srpaulo	struct fst_session *s = user_ctx;
171289284Srpaulo	union fst_session_state_switch_extra extra = {
172289284Srpaulo		.to_initial = {
173289284Srpaulo			.reason = REASON_STT,
174289284Srpaulo		},
175289284Srpaulo	};
176289284Srpaulo
177289284Srpaulo	fst_printf_session(s, MSG_WARNING, "Session State Timeout");
178289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &extra);
179289284Srpaulo}
180289284Srpaulo
181289284Srpaulo
182289284Srpaulostatic void fst_session_stt_arm(struct fst_session *s)
183289284Srpaulo{
184337817Scy	/* Action frames sometimes get delayed. Use relaxed timeout (2*) */
185337817Scy	eloop_register_timeout(0, 2 * TU_TO_US(FST_DEFAULT_SESSION_TIMEOUT_TU),
186289284Srpaulo			       fst_session_timeout_handler, NULL, s);
187289284Srpaulo	s->stt_armed = TRUE;
188289284Srpaulo}
189289284Srpaulo
190289284Srpaulo
191289284Srpaulostatic void fst_session_stt_disarm(struct fst_session *s)
192289284Srpaulo{
193289284Srpaulo	if (s->stt_armed) {
194289284Srpaulo		eloop_cancel_timeout(fst_session_timeout_handler, NULL, s);
195289284Srpaulo		s->stt_armed = FALSE;
196289284Srpaulo	}
197289284Srpaulo}
198289284Srpaulo
199289284Srpaulo
200289284Srpaulostatic Boolean fst_session_is_in_transition(struct fst_session *s)
201289284Srpaulo{
202289284Srpaulo	/* See spec, 10.32.2.2  Transitioning between states */
203289284Srpaulo	return s->stt_armed;
204289284Srpaulo}
205289284Srpaulo
206289284Srpaulo
207289284Srpaulostatic int fst_session_is_in_progress(struct fst_session *s)
208289284Srpaulo{
209289284Srpaulo	return s->state != FST_SESSION_STATE_INITIAL;
210289284Srpaulo}
211289284Srpaulo
212289284Srpaulo
213289284Srpaulostatic int fst_session_is_ready_pending(struct fst_session *s)
214289284Srpaulo{
215289284Srpaulo	return s->state == FST_SESSION_STATE_SETUP_COMPLETION &&
216289284Srpaulo		fst_session_is_in_transition(s);
217289284Srpaulo}
218289284Srpaulo
219289284Srpaulo
220289284Srpaulostatic int fst_session_is_ready(struct fst_session *s)
221289284Srpaulo{
222289284Srpaulo	return s->state == FST_SESSION_STATE_SETUP_COMPLETION &&
223289284Srpaulo		!fst_session_is_in_transition(s);
224289284Srpaulo}
225289284Srpaulo
226289284Srpaulo
227289284Srpaulostatic int fst_session_is_switch_requested(struct fst_session *s)
228289284Srpaulo{
229289284Srpaulo	return s->state == FST_SESSION_STATE_TRANSITION_DONE &&
230289284Srpaulo		fst_session_is_in_transition(s);
231289284Srpaulo}
232289284Srpaulo
233289284Srpaulo
234289284Srpaulostatic struct fst_session *
235289284Srpaulofst_find_session_in_progress(const u8 *peer_addr, struct fst_group *g)
236289284Srpaulo{
237289284Srpaulo	struct fst_session *s;
238289284Srpaulo
239289284Srpaulo	foreach_fst_session(s) {
240289284Srpaulo		if (s->group == g &&
241289284Srpaulo		    (os_memcmp(s->data.old_peer_addr, peer_addr,
242289284Srpaulo			       ETH_ALEN) == 0 ||
243289284Srpaulo		     os_memcmp(s->data.new_peer_addr, peer_addr,
244289284Srpaulo			       ETH_ALEN) == 0) &&
245289284Srpaulo		    fst_session_is_in_progress(s))
246289284Srpaulo			return s;
247289284Srpaulo	}
248289284Srpaulo
249289284Srpaulo	return NULL;
250289284Srpaulo}
251289284Srpaulo
252289284Srpaulo
253289284Srpaulostatic void fst_session_reset_ex(struct fst_session *s, enum fst_reason reason)
254289284Srpaulo{
255289284Srpaulo	union fst_session_state_switch_extra evext = {
256289284Srpaulo		.to_initial = {
257289284Srpaulo			.reason = reason,
258289284Srpaulo		},
259289284Srpaulo	};
260289284Srpaulo
261289284Srpaulo	if (s->state == FST_SESSION_STATE_SETUP_COMPLETION ||
262289284Srpaulo	    s->state == FST_SESSION_STATE_TRANSITION_DONE)
263289284Srpaulo		fst_session_tear_down_setup(s);
264289284Srpaulo	fst_session_stt_disarm(s);
265289284Srpaulo	os_memset(&s->data, 0, sizeof(s->data));
266289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
267289284Srpaulo}
268289284Srpaulo
269289284Srpaulo
270289284Srpaulostatic int fst_session_send_action(struct fst_session *s, Boolean old_iface,
271289284Srpaulo				   const void *payload, size_t size,
272289284Srpaulo				   const struct wpabuf *extra_buf)
273289284Srpaulo{
274289284Srpaulo	size_t len;
275289284Srpaulo	int res;
276289284Srpaulo	struct wpabuf *buf;
277289284Srpaulo	u8 action;
278289284Srpaulo	struct fst_iface *iface =
279289284Srpaulo		old_iface ? s->data.old_iface : s->data.new_iface;
280289284Srpaulo
281289284Srpaulo	WPA_ASSERT(payload != NULL);
282289284Srpaulo	WPA_ASSERT(size != 0);
283289284Srpaulo
284289284Srpaulo	action = *(const u8 *) payload;
285289284Srpaulo
286289284Srpaulo	WPA_ASSERT(action <= FST_ACTION_MAX_SUPPORTED);
287289284Srpaulo
288289284Srpaulo	if (!iface) {
289289284Srpaulo		fst_printf_session(s, MSG_ERROR,
290289284Srpaulo				   "no %s interface for FST Action '%s' sending",
291289284Srpaulo				   old_iface ? "old" : "new",
292289284Srpaulo				   fst_action_names[action]);
293289284Srpaulo		return -1;
294289284Srpaulo	}
295289284Srpaulo
296289284Srpaulo	len = sizeof(u8) /* category */ + size;
297289284Srpaulo	if (extra_buf)
298289284Srpaulo		len += wpabuf_size(extra_buf);
299289284Srpaulo
300289284Srpaulo	buf = wpabuf_alloc(len);
301289284Srpaulo	if (!buf) {
302289284Srpaulo		fst_printf_session(s, MSG_ERROR,
303289284Srpaulo				   "cannot allocate buffer of %zu bytes for FST Action '%s' sending",
304289284Srpaulo				   len, fst_action_names[action]);
305289284Srpaulo		return -1;
306289284Srpaulo	}
307289284Srpaulo
308289284Srpaulo	wpabuf_put_u8(buf, WLAN_ACTION_FST);
309289284Srpaulo	wpabuf_put_data(buf, payload, size);
310289284Srpaulo	if (extra_buf)
311289284Srpaulo		wpabuf_put_buf(buf, extra_buf);
312289284Srpaulo
313289284Srpaulo	res = fst_iface_send_action(iface,
314289284Srpaulo				    old_iface ? s->data.old_peer_addr :
315289284Srpaulo				    s->data.new_peer_addr, buf);
316289284Srpaulo	if (res < 0)
317289284Srpaulo		fst_printf_siface(s, iface, MSG_ERROR,
318289284Srpaulo				  "failed to send FST Action '%s'",
319289284Srpaulo				  fst_action_names[action]);
320289284Srpaulo	else
321289284Srpaulo		fst_printf_siface(s, iface, MSG_DEBUG, "FST Action '%s' sent",
322289284Srpaulo				  fst_action_names[action]);
323289284Srpaulo	wpabuf_free(buf);
324289284Srpaulo
325289284Srpaulo	return res;
326289284Srpaulo}
327289284Srpaulo
328289284Srpaulo
329289284Srpaulostatic int fst_session_send_tear_down(struct fst_session *s)
330289284Srpaulo{
331289284Srpaulo	struct fst_tear_down td;
332289284Srpaulo	int res;
333289284Srpaulo
334289284Srpaulo	if (!fst_session_is_in_progress(s)) {
335289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No FST setup to tear down");
336289284Srpaulo		return -1;
337289284Srpaulo	}
338289284Srpaulo
339289284Srpaulo	WPA_ASSERT(s->data.old_iface != NULL);
340289284Srpaulo	WPA_ASSERT(s->data.new_iface != NULL);
341289284Srpaulo
342289284Srpaulo	os_memset(&td, 0, sizeof(td));
343289284Srpaulo
344289284Srpaulo	td.action = FST_ACTION_TEAR_DOWN;
345289284Srpaulo	td.fsts_id = host_to_le32(s->data.fsts_id);
346289284Srpaulo
347289284Srpaulo	res = fst_session_send_action(s, TRUE, &td, sizeof(td), NULL);
348289284Srpaulo	if (!res)
349289284Srpaulo		fst_printf_sframe(s, TRUE, MSG_INFO, "FST TearDown sent");
350289284Srpaulo	else
351289284Srpaulo		fst_printf_sframe(s, TRUE, MSG_ERROR,
352289284Srpaulo				  "failed to send FST TearDown");
353289284Srpaulo
354289284Srpaulo	return res;
355289284Srpaulo}
356289284Srpaulo
357289284Srpaulo
358289284Srpaulostatic void fst_session_handle_setup_request(struct fst_iface *iface,
359289284Srpaulo					     const struct ieee80211_mgmt *mgmt,
360289284Srpaulo					     size_t frame_len)
361289284Srpaulo{
362289284Srpaulo	struct fst_session *s;
363289284Srpaulo	const struct fst_setup_req *req;
364289284Srpaulo	struct fst_iface *new_iface = NULL;
365289284Srpaulo	struct fst_group *g;
366289284Srpaulo	u8 new_iface_peer_addr[ETH_ALEN];
367289284Srpaulo	size_t plen;
368289284Srpaulo
369289284Srpaulo	if (frame_len < IEEE80211_HDRLEN + 1 + sizeof(*req))  {
370289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
371289284Srpaulo				 "FST Request dropped: too short (%zu < %zu)",
372289284Srpaulo				 frame_len,
373289284Srpaulo				 IEEE80211_HDRLEN + 1 + sizeof(*req));
374289284Srpaulo		return;
375289284Srpaulo	}
376289284Srpaulo	plen = frame_len - IEEE80211_HDRLEN - 1;
377289284Srpaulo	req = (const struct fst_setup_req *)
378289284Srpaulo		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
379289284Srpaulo	if (req->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
380289284Srpaulo	    req->stie.length < 11) {
381289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
382289284Srpaulo				 "FST Request dropped: invalid STIE");
383289284Srpaulo		return;
384289284Srpaulo	}
385289284Srpaulo
386289284Srpaulo	if (req->stie.new_band_id == req->stie.old_band_id) {
387289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
388289284Srpaulo				 "FST Request dropped: new and old band IDs are the same");
389289284Srpaulo		return;
390289284Srpaulo	}
391289284Srpaulo
392289284Srpaulo	g = fst_iface_get_group(iface);
393289284Srpaulo
394289284Srpaulo	if (plen > sizeof(*req)) {
395289284Srpaulo		fst_iface_update_mb_ie(iface, mgmt->sa, (const u8 *) (req + 1),
396289284Srpaulo				       plen - sizeof(*req));
397289284Srpaulo		fst_printf_iface(iface, MSG_INFO,
398289284Srpaulo				 "FST Request: MB IEs updated for " MACSTR,
399289284Srpaulo				 MAC2STR(mgmt->sa));
400289284Srpaulo	}
401289284Srpaulo
402337817Scy	new_iface = fst_group_get_peer_other_connection(iface, mgmt->sa,
403337817Scy							req->stie.new_band_id,
404337817Scy							new_iface_peer_addr);
405289284Srpaulo	if (!new_iface) {
406289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
407289284Srpaulo				 "FST Request dropped: new iface not found");
408289284Srpaulo		return;
409289284Srpaulo	}
410337817Scy	fst_printf_iface(iface, MSG_INFO,
411337817Scy			 "FST Request: new iface (%s:" MACSTR ") found",
412337817Scy			 fst_iface_get_name(new_iface),
413337817Scy			 MAC2STR(new_iface_peer_addr));
414289284Srpaulo
415289284Srpaulo	s = fst_find_session_in_progress(mgmt->sa, g);
416289284Srpaulo	if (s) {
417289284Srpaulo		union fst_session_state_switch_extra evext = {
418289284Srpaulo			.to_initial = {
419289284Srpaulo				.reason = REASON_SETUP,
420289284Srpaulo			},
421289284Srpaulo		};
422289284Srpaulo
423289284Srpaulo		/*
424289284Srpaulo		 * 10.32.2.2  Transitioning between states:
425289284Srpaulo		 * Upon receipt of an FST Setup Request frame, the responder
426289284Srpaulo		 * shall respond with an FST Setup Response frame unless it has
427289284Srpaulo		 * a pending FST Setup Request frame addressed to the initiator
428289284Srpaulo		 * and the responder has a numerically larger MAC address than
429289284Srpaulo		 * the initiator���s MAC address, in which case, the responder
430289284Srpaulo		 * shall delete the received FST Setup Request.
431289284Srpaulo		 */
432337817Scy		if (fst_session_is_ready_pending(s) &&
433337817Scy		    /* waiting for Setup Response */
434337817Scy		    os_memcmp(mgmt->da, mgmt->sa, ETH_ALEN) > 0) {
435289284Srpaulo			fst_printf_session(s, MSG_WARNING,
436289284Srpaulo					   "FST Request dropped due to MAC comparison (our MAC is "
437289284Srpaulo					   MACSTR ")",
438289284Srpaulo					   MAC2STR(mgmt->da));
439289284Srpaulo			return;
440289284Srpaulo		}
441289284Srpaulo
442337817Scy		/*
443337817Scy		 * State is SETUP_COMPLETION (either in transition or not) or
444337817Scy		 * TRANSITION_DONE (in transition).
445337817Scy		 * Setup Request arriving in this state could mean:
446337817Scy		 * 1. peer sent it before receiving our Setup Request (race
447337817Scy		 *    condition)
448337817Scy		 * 2. peer didn't receive our Setup Response. Peer is retrying
449337817Scy		 *    after STT timeout
450337817Scy		 * 3. peer's FST state machines are out of sync due to some
451337817Scy		 *    other reason
452337817Scy		 *
453337817Scy		 * We will reset our session and create a new one instead.
454337817Scy		 */
455289284Srpaulo
456337817Scy		fst_printf_session(s, MSG_WARNING,
457337817Scy			"resetting due to FST request");
458289284Srpaulo
459289284Srpaulo		/*
460289284Srpaulo		 * If FST Setup Request arrived with the same FSTS ID as one we
461337817Scy		 * initialized before, there's no need to tear down the session.
462289284Srpaulo		 * Moreover, as FSTS ID is the same, the other side will
463289284Srpaulo		 * associate this tear down with the session it initiated that
464289284Srpaulo		 * will break the sync.
465289284Srpaulo		 */
466289284Srpaulo		if (le_to_host32(req->stie.fsts_id) != s->data.fsts_id)
467289284Srpaulo			fst_session_send_tear_down(s);
468289284Srpaulo		else
469289284Srpaulo			fst_printf_session(s, MSG_WARNING,
470289284Srpaulo					   "Skipping TearDown as the FST request has the same FSTS ID as initiated");
471289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
472289284Srpaulo		fst_session_stt_disarm(s);
473289284Srpaulo	}
474289284Srpaulo
475289284Srpaulo	s = fst_session_create(g);
476289284Srpaulo	if (!s) {
477289284Srpaulo		fst_printf(MSG_WARNING,
478289284Srpaulo			   "FST Request dropped: cannot create session for %s and %s",
479289284Srpaulo			   fst_iface_get_name(iface),
480289284Srpaulo			   fst_iface_get_name(new_iface));
481289284Srpaulo		return;
482289284Srpaulo	}
483289284Srpaulo
484289284Srpaulo	fst_session_set_iface(s, iface, TRUE);
485289284Srpaulo	fst_session_set_peer_addr(s, mgmt->sa, TRUE);
486289284Srpaulo	fst_session_set_iface(s, new_iface, FALSE);
487289284Srpaulo	fst_session_set_peer_addr(s, new_iface_peer_addr, FALSE);
488289284Srpaulo	fst_session_set_llt(s, FST_LLT_VAL_TO_MS(le_to_host32(req->llt)));
489289284Srpaulo	s->data.pending_setup_req_dlgt = req->dialog_token;
490289284Srpaulo	s->data.fsts_id = le_to_host32(req->stie.fsts_id);
491289284Srpaulo
492289284Srpaulo	fst_session_stt_arm(s);
493289284Srpaulo
494289284Srpaulo	fst_session_notify_ctrl(s, EVENT_FST_SETUP, NULL);
495289284Srpaulo
496289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION, NULL);
497289284Srpaulo}
498289284Srpaulo
499289284Srpaulo
500289284Srpaulostatic void fst_session_handle_setup_response(struct fst_session *s,
501289284Srpaulo					      struct fst_iface *iface,
502289284Srpaulo					      const struct ieee80211_mgmt *mgmt,
503289284Srpaulo					      size_t frame_len)
504289284Srpaulo{
505289284Srpaulo	const struct fst_setup_res *res;
506289284Srpaulo	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
507289284Srpaulo	enum hostapd_hw_mode hw_mode;
508289284Srpaulo	u8 channel;
509289284Srpaulo	union fst_session_state_switch_extra evext = {
510337817Scy		.to_initial = {
511337817Scy			.reject_code = 0,
512337817Scy		},
513289284Srpaulo	};
514289284Srpaulo
515289284Srpaulo	if (iface != s->data.old_iface) {
516289284Srpaulo		fst_printf_session(s, MSG_WARNING,
517289284Srpaulo				   "FST Response dropped: %s is not the old iface",
518289284Srpaulo				   fst_iface_get_name(iface));
519289284Srpaulo		return;
520289284Srpaulo	}
521289284Srpaulo
522289284Srpaulo	if (!fst_session_is_ready_pending(s)) {
523289284Srpaulo		fst_printf_session(s, MSG_WARNING,
524289284Srpaulo				   "FST Response dropped due to wrong state: %s",
525289284Srpaulo				   fst_session_state_name(s->state));
526289284Srpaulo		return;
527289284Srpaulo	}
528289284Srpaulo
529289284Srpaulo	if (plen < sizeof(*res)) {
530289284Srpaulo		fst_printf_session(s, MSG_WARNING,
531289284Srpaulo				   "Too short FST Response dropped");
532289284Srpaulo		return;
533289284Srpaulo	}
534289284Srpaulo	res = (const struct fst_setup_res *)
535289284Srpaulo		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
536289284Srpaulo	if (res->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
537289284Srpaulo	    res->stie.length < 11) {
538289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
539289284Srpaulo				 "FST Response dropped: invalid STIE");
540289284Srpaulo		return;
541289284Srpaulo	}
542289284Srpaulo
543289284Srpaulo	if (res->dialog_token != s->data.pending_setup_req_dlgt)  {
544289284Srpaulo		fst_printf_session(s, MSG_WARNING,
545289284Srpaulo				   "FST Response dropped due to wrong dialog token (%u != %u)",
546289284Srpaulo				   s->data.pending_setup_req_dlgt,
547289284Srpaulo				   res->dialog_token);
548289284Srpaulo		return;
549289284Srpaulo	}
550289284Srpaulo
551289284Srpaulo	if (res->status_code == WLAN_STATUS_SUCCESS &&
552289284Srpaulo	    le_to_host32(res->stie.fsts_id) != s->data.fsts_id) {
553289284Srpaulo		fst_printf_session(s, MSG_WARNING,
554289284Srpaulo				   "FST Response dropped due to wrong FST Session ID (%u)",
555289284Srpaulo				   le_to_host32(res->stie.fsts_id));
556289284Srpaulo		return;
557289284Srpaulo	}
558289284Srpaulo
559289284Srpaulo	fst_session_stt_disarm(s);
560289284Srpaulo
561289284Srpaulo	if (res->status_code != WLAN_STATUS_SUCCESS) {
562289284Srpaulo		/*
563289284Srpaulo		 * 10.32.2.2  Transitioning between states
564289284Srpaulo		 * The initiator shall set the STT to the value of the
565289284Srpaulo		 * FSTSessionTimeOut field at ... and at each ACK frame sent in
566289284Srpaulo		 * response to a received FST Setup Response with the Status
567289284Srpaulo		 * Code field equal to PENDING_ADMITTING_FST_SESSION or
568289284Srpaulo		 * PENDING_GAP_IN_BA_WINDOW.
569289284Srpaulo		 */
570289284Srpaulo		evext.to_initial.reason = REASON_REJECT;
571289284Srpaulo		evext.to_initial.reject_code = res->status_code;
572289284Srpaulo		evext.to_initial.initiator = FST_INITIATOR_REMOTE;
573289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
574289284Srpaulo		fst_printf_session(s, MSG_WARNING,
575289284Srpaulo				   "FST Setup rejected by remote side with status %u",
576289284Srpaulo				   res->status_code);
577289284Srpaulo		return;
578289284Srpaulo	}
579289284Srpaulo
580289284Srpaulo	fst_iface_get_channel_info(s->data.new_iface, &hw_mode, &channel);
581289284Srpaulo
582289284Srpaulo	if (fst_hw_mode_to_band(hw_mode) != res->stie.new_band_id) {
583289284Srpaulo		evext.to_initial.reason = REASON_ERROR_PARAMS;
584289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
585289284Srpaulo		fst_printf_session(s, MSG_WARNING,
586289284Srpaulo				   "invalid FST Setup parameters");
587289284Srpaulo		fst_session_tear_down_setup(s);
588289284Srpaulo		return;
589289284Srpaulo	}
590289284Srpaulo
591289284Srpaulo	fst_printf_session(s, MSG_INFO,
592289284Srpaulo			   "%s: FST Setup established for %s (llt=%u)",
593289284Srpaulo			   fst_iface_get_name(s->data.old_iface),
594289284Srpaulo			   fst_iface_get_name(s->data.new_iface),
595289284Srpaulo			   s->data.llt_ms);
596289284Srpaulo
597289284Srpaulo	fst_session_notify_ctrl(s, EVENT_FST_ESTABLISHED, NULL);
598289284Srpaulo
599289284Srpaulo	if (s->data.llt_ms == FST_LLT_SWITCH_IMMEDIATELY)
600289284Srpaulo		fst_session_initiate_switch(s);
601289284Srpaulo}
602289284Srpaulo
603289284Srpaulo
604289284Srpaulostatic void fst_session_handle_tear_down(struct fst_session *s,
605289284Srpaulo					 struct fst_iface *iface,
606289284Srpaulo					 const struct ieee80211_mgmt *mgmt,
607289284Srpaulo					 size_t frame_len)
608289284Srpaulo{
609289284Srpaulo	const struct fst_tear_down *td;
610289284Srpaulo	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
611289284Srpaulo	union fst_session_state_switch_extra evext = {
612289284Srpaulo		.to_initial = {
613289284Srpaulo			.reason = REASON_TEARDOWN,
614289284Srpaulo			.initiator = FST_INITIATOR_REMOTE,
615289284Srpaulo		},
616289284Srpaulo	};
617289284Srpaulo
618289284Srpaulo	if (plen < sizeof(*td)) {
619289284Srpaulo		fst_printf_session(s, MSG_WARNING,
620289284Srpaulo				   "Too short FST Tear Down dropped");
621289284Srpaulo		return;
622289284Srpaulo	}
623289284Srpaulo	td = (const struct fst_tear_down *)
624289284Srpaulo		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
625289284Srpaulo
626289284Srpaulo	if (le_to_host32(td->fsts_id) != s->data.fsts_id) {
627289284Srpaulo		fst_printf_siface(s, iface, MSG_WARNING,
628289284Srpaulo				  "tear down for wrong FST Setup ID (%u)",
629289284Srpaulo				  le_to_host32(td->fsts_id));
630289284Srpaulo		return;
631289284Srpaulo	}
632289284Srpaulo
633289284Srpaulo	fst_session_stt_disarm(s);
634289284Srpaulo
635289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
636289284Srpaulo}
637289284Srpaulo
638289284Srpaulo
639289284Srpaulostatic void fst_session_handle_ack_request(struct fst_session *s,
640289284Srpaulo					   struct fst_iface *iface,
641289284Srpaulo					   const struct ieee80211_mgmt *mgmt,
642289284Srpaulo					   size_t frame_len)
643289284Srpaulo{
644289284Srpaulo	const struct fst_ack_req *req;
645289284Srpaulo	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
646289284Srpaulo	struct fst_ack_res res;
647289284Srpaulo	union fst_session_state_switch_extra evext = {
648289284Srpaulo		.to_initial = {
649289284Srpaulo			.reason = REASON_SWITCH,
650289284Srpaulo			.initiator = FST_INITIATOR_REMOTE,
651289284Srpaulo		},
652289284Srpaulo	};
653289284Srpaulo
654289284Srpaulo	if (!fst_session_is_ready(s) && !fst_session_is_switch_requested(s)) {
655289284Srpaulo		fst_printf_siface(s, iface, MSG_ERROR,
656289284Srpaulo				  "cannot initiate switch due to wrong session state (%s)",
657289284Srpaulo				  fst_session_state_name(s->state));
658289284Srpaulo		return;
659289284Srpaulo	}
660289284Srpaulo
661289284Srpaulo	WPA_ASSERT(s->data.new_iface != NULL);
662289284Srpaulo
663289284Srpaulo	if (iface != s->data.new_iface) {
664289284Srpaulo		fst_printf_siface(s, iface, MSG_ERROR,
665289284Srpaulo				  "Ack received on wrong interface");
666289284Srpaulo		return;
667289284Srpaulo	}
668289284Srpaulo
669289284Srpaulo	if (plen < sizeof(*req)) {
670289284Srpaulo		fst_printf_session(s, MSG_WARNING,
671289284Srpaulo				   "Too short FST Ack Request dropped");
672289284Srpaulo		return;
673289284Srpaulo	}
674289284Srpaulo	req = (const struct fst_ack_req *)
675289284Srpaulo		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
676289284Srpaulo
677289284Srpaulo	if (le_to_host32(req->fsts_id) != s->data.fsts_id) {
678289284Srpaulo		fst_printf_siface(s, iface, MSG_WARNING,
679289284Srpaulo				  "Ack for wrong FST Setup ID (%u)",
680289284Srpaulo				  le_to_host32(req->fsts_id));
681289284Srpaulo		return;
682289284Srpaulo	}
683289284Srpaulo
684289284Srpaulo	os_memset(&res, 0, sizeof(res));
685289284Srpaulo
686289284Srpaulo	res.action = FST_ACTION_ACK_RESPONSE;
687289284Srpaulo	res.dialog_token = req->dialog_token;
688289284Srpaulo	res.fsts_id = req->fsts_id;
689289284Srpaulo
690289284Srpaulo	if (!fst_session_send_action(s, FALSE, &res, sizeof(res), NULL)) {
691289284Srpaulo		fst_printf_sframe(s, FALSE, MSG_INFO, "FST Ack Response sent");
692289284Srpaulo		fst_session_stt_disarm(s);
693289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE,
694289284Srpaulo				      NULL);
695289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED,
696289284Srpaulo				      NULL);
697289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
698289284Srpaulo	}
699289284Srpaulo}
700289284Srpaulo
701289284Srpaulo
702289284Srpaulostatic void
703289284Srpaulofst_session_handle_ack_response(struct fst_session *s,
704289284Srpaulo				struct fst_iface *iface,
705289284Srpaulo				const struct ieee80211_mgmt *mgmt,
706289284Srpaulo				size_t frame_len)
707289284Srpaulo{
708289284Srpaulo	const struct fst_ack_res *res;
709289284Srpaulo	size_t plen = frame_len - IEEE80211_HDRLEN - 1;
710289284Srpaulo	union fst_session_state_switch_extra evext = {
711289284Srpaulo		.to_initial = {
712289284Srpaulo			.reason = REASON_SWITCH,
713289284Srpaulo			.initiator = FST_INITIATOR_LOCAL,
714289284Srpaulo		},
715289284Srpaulo	};
716289284Srpaulo
717289284Srpaulo	if (!fst_session_is_switch_requested(s)) {
718289284Srpaulo		fst_printf_siface(s, iface, MSG_ERROR,
719289284Srpaulo				  "Ack Response in inappropriate session state (%s)",
720289284Srpaulo				  fst_session_state_name(s->state));
721289284Srpaulo		return;
722289284Srpaulo	}
723289284Srpaulo
724289284Srpaulo	WPA_ASSERT(s->data.new_iface != NULL);
725289284Srpaulo
726289284Srpaulo	if (iface != s->data.new_iface) {
727289284Srpaulo		fst_printf_siface(s, iface, MSG_ERROR,
728289284Srpaulo				  "Ack response received on wrong interface");
729289284Srpaulo		return;
730289284Srpaulo	}
731289284Srpaulo
732289284Srpaulo	if (plen < sizeof(*res)) {
733289284Srpaulo		fst_printf_session(s, MSG_WARNING,
734289284Srpaulo				   "Too short FST Ack Response dropped");
735289284Srpaulo		return;
736289284Srpaulo	}
737289284Srpaulo	res = (const struct fst_ack_res *)
738289284Srpaulo		(((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
739289284Srpaulo
740289284Srpaulo	if (le_to_host32(res->fsts_id) != s->data.fsts_id) {
741289284Srpaulo		fst_printf_siface(s, iface, MSG_ERROR,
742289284Srpaulo				  "Ack response for wrong FST Setup ID (%u)",
743289284Srpaulo				  le_to_host32(res->fsts_id));
744289284Srpaulo		return;
745289284Srpaulo	}
746289284Srpaulo
747289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED, NULL);
748289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
749289284Srpaulo
750289284Srpaulo	fst_session_stt_disarm(s);
751289284Srpaulo}
752289284Srpaulo
753289284Srpaulo
754289284Srpaulostruct fst_session * fst_session_create(struct fst_group *g)
755289284Srpaulo{
756289284Srpaulo	struct fst_session *s;
757289284Srpaulo	u32 id;
758289284Srpaulo
759289284Srpaulo	id = fst_find_free_session_id();
760289284Srpaulo	if (id == FST_INVALID_SESSION_ID) {
761289284Srpaulo		fst_printf(MSG_ERROR, "Cannot assign new session ID");
762289284Srpaulo		return NULL;
763289284Srpaulo	}
764289284Srpaulo
765289284Srpaulo	s = os_zalloc(sizeof(*s));
766289284Srpaulo	if (!s) {
767289284Srpaulo		fst_printf(MSG_ERROR, "Cannot allocate new session object");
768289284Srpaulo		return NULL;
769289284Srpaulo	}
770289284Srpaulo
771289284Srpaulo	s->id = id;
772289284Srpaulo	s->group = g;
773289284Srpaulo	s->state = FST_SESSION_STATE_INITIAL;
774289284Srpaulo
775289284Srpaulo	s->data.llt_ms = FST_LLT_MS_DEFAULT;
776289284Srpaulo
777289284Srpaulo	fst_printf(MSG_INFO, "Session %u created", s->id);
778289284Srpaulo
779289284Srpaulo	dl_list_add_tail(&global_sessions_list, &s->global_sessions_lentry);
780289284Srpaulo
781289284Srpaulo	foreach_fst_ctrl_call(on_session_added, s);
782289284Srpaulo
783289284Srpaulo	return s;
784289284Srpaulo}
785289284Srpaulo
786289284Srpaulo
787289284Srpaulovoid fst_session_set_iface(struct fst_session *s, struct fst_iface *iface,
788289284Srpaulo			   Boolean is_old)
789289284Srpaulo{
790289284Srpaulo	if (is_old)
791289284Srpaulo		s->data.old_iface = iface;
792289284Srpaulo	else
793289284Srpaulo		s->data.new_iface = iface;
794289284Srpaulo
795289284Srpaulo}
796289284Srpaulo
797289284Srpaulo
798289284Srpaulovoid fst_session_set_llt(struct fst_session *s, u32 llt)
799289284Srpaulo{
800289284Srpaulo	s->data.llt_ms = llt;
801289284Srpaulo}
802289284Srpaulo
803289284Srpaulo
804289284Srpaulovoid fst_session_set_peer_addr(struct fst_session *s, const u8 *addr,
805289284Srpaulo			       Boolean is_old)
806289284Srpaulo{
807289284Srpaulo	u8 *a = is_old ? s->data.old_peer_addr : s->data.new_peer_addr;
808289284Srpaulo
809289284Srpaulo	os_memcpy(a, addr, ETH_ALEN);
810289284Srpaulo}
811289284Srpaulo
812289284Srpaulo
813289284Srpauloint fst_session_initiate_setup(struct fst_session *s)
814289284Srpaulo{
815289284Srpaulo	struct fst_setup_req req;
816289284Srpaulo	int res;
817289284Srpaulo	u32 fsts_id;
818289284Srpaulo	u8 dialog_token;
819289284Srpaulo	struct fst_session *_s;
820289284Srpaulo
821289284Srpaulo	if (fst_session_is_in_progress(s)) {
822289284Srpaulo		fst_printf_session(s, MSG_ERROR, "Session in progress");
823289284Srpaulo		return -EINVAL;
824289284Srpaulo	}
825289284Srpaulo
826289284Srpaulo	if (is_zero_ether_addr(s->data.old_peer_addr)) {
827289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No old peer MAC address");
828289284Srpaulo		return -EINVAL;
829289284Srpaulo	}
830289284Srpaulo
831289284Srpaulo	if (is_zero_ether_addr(s->data.new_peer_addr)) {
832289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No new peer MAC address");
833289284Srpaulo		return -EINVAL;
834289284Srpaulo	}
835289284Srpaulo
836289284Srpaulo	if (!s->data.old_iface) {
837289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No old interface defined");
838289284Srpaulo		return -EINVAL;
839289284Srpaulo	}
840289284Srpaulo
841289284Srpaulo	if (!s->data.new_iface) {
842289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No new interface defined");
843289284Srpaulo		return -EINVAL;
844289284Srpaulo	}
845289284Srpaulo
846289284Srpaulo	if (s->data.new_iface == s->data.old_iface) {
847289284Srpaulo		fst_printf_session(s, MSG_ERROR,
848289284Srpaulo				   "Same interface set as old and new");
849289284Srpaulo		return -EINVAL;
850289284Srpaulo	}
851289284Srpaulo
852337817Scy	if (!fst_iface_is_connected(s->data.old_iface, s->data.old_peer_addr,
853337817Scy				    FALSE)) {
854289284Srpaulo		fst_printf_session(s, MSG_ERROR,
855289284Srpaulo				   "The preset old peer address is not connected");
856289284Srpaulo		return -EINVAL;
857289284Srpaulo	}
858289284Srpaulo
859337817Scy	if (!fst_iface_is_connected(s->data.new_iface, s->data.new_peer_addr,
860337817Scy				    FALSE)) {
861289284Srpaulo		fst_printf_session(s, MSG_ERROR,
862289284Srpaulo				   "The preset new peer address is not connected");
863289284Srpaulo		return -EINVAL;
864289284Srpaulo	}
865289284Srpaulo
866289284Srpaulo	_s = fst_find_session_in_progress(s->data.old_peer_addr, s->group);
867289284Srpaulo	if (_s) {
868289284Srpaulo		fst_printf_session(s, MSG_ERROR,
869289284Srpaulo				   "There is another session in progress (old): %u",
870289284Srpaulo				   _s->id);
871289284Srpaulo		return -EINVAL;
872289284Srpaulo	}
873289284Srpaulo
874289284Srpaulo	_s = fst_find_session_in_progress(s->data.new_peer_addr, s->group);
875289284Srpaulo	if (_s) {
876289284Srpaulo		fst_printf_session(s, MSG_ERROR,
877289284Srpaulo				   "There is another session in progress (new): %u",
878289284Srpaulo				   _s->id);
879289284Srpaulo		return -EINVAL;
880289284Srpaulo	}
881289284Srpaulo
882289284Srpaulo	dialog_token = fst_group_assign_dialog_token(s->group);
883289284Srpaulo	fsts_id = fst_group_assign_fsts_id(s->group);
884289284Srpaulo
885289284Srpaulo	os_memset(&req, 0, sizeof(req));
886289284Srpaulo
887289284Srpaulo	fst_printf_siface(s, s->data.old_iface, MSG_INFO,
888289284Srpaulo		"initiating FST setup for %s (llt=%u ms)",
889289284Srpaulo		fst_iface_get_name(s->data.new_iface), s->data.llt_ms);
890289284Srpaulo
891289284Srpaulo	req.action = FST_ACTION_SETUP_REQUEST;
892289284Srpaulo	req.dialog_token = dialog_token;
893289284Srpaulo	req.llt = host_to_le32(FST_LLT_MS_TO_VAL(s->data.llt_ms));
894289284Srpaulo	/* 8.4.2.147 Session Transition element */
895289284Srpaulo	req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
896289284Srpaulo	req.stie.length = sizeof(req.stie) - 2;
897289284Srpaulo	req.stie.fsts_id = host_to_le32(fsts_id);
898289284Srpaulo	req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
899289284Srpaulo
900289284Srpaulo	req.stie.new_band_id = fst_iface_get_band_id(s->data.new_iface);
901289284Srpaulo	req.stie.new_band_op = 1;
902289284Srpaulo	req.stie.new_band_setup = 0;
903289284Srpaulo
904289284Srpaulo	req.stie.old_band_id = fst_iface_get_band_id(s->data.old_iface);
905289284Srpaulo	req.stie.old_band_op = 1;
906289284Srpaulo	req.stie.old_band_setup = 0;
907289284Srpaulo
908289284Srpaulo	res = fst_session_send_action(s, TRUE, &req, sizeof(req),
909289284Srpaulo				      fst_iface_get_mbie(s->data.old_iface));
910289284Srpaulo	if (!res) {
911289284Srpaulo		s->data.fsts_id = fsts_id;
912289284Srpaulo		s->data.pending_setup_req_dlgt = dialog_token;
913289284Srpaulo		fst_printf_sframe(s, TRUE, MSG_INFO, "FST Setup Request sent");
914289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION,
915289284Srpaulo				      NULL);
916289284Srpaulo
917289284Srpaulo		fst_session_stt_arm(s);
918289284Srpaulo	}
919289284Srpaulo
920289284Srpaulo	return res;
921289284Srpaulo}
922289284Srpaulo
923289284Srpaulo
924289284Srpauloint fst_session_respond(struct fst_session *s, u8 status_code)
925289284Srpaulo{
926289284Srpaulo	struct fst_setup_res res;
927289284Srpaulo	enum hostapd_hw_mode hw_mode;
928289284Srpaulo	u8 channel;
929289284Srpaulo
930289284Srpaulo	if (!fst_session_is_ready_pending(s)) {
931289284Srpaulo		fst_printf_session(s, MSG_ERROR, "incorrect state: %s",
932289284Srpaulo				   fst_session_state_name(s->state));
933289284Srpaulo		return -EINVAL;
934289284Srpaulo	}
935289284Srpaulo
936289284Srpaulo	if (is_zero_ether_addr(s->data.old_peer_addr)) {
937289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No peer MAC address");
938289284Srpaulo		return -EINVAL;
939289284Srpaulo	}
940289284Srpaulo
941289284Srpaulo	if (!s->data.old_iface) {
942289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No old interface defined");
943289284Srpaulo		return -EINVAL;
944289284Srpaulo	}
945289284Srpaulo
946289284Srpaulo	if (!s->data.new_iface) {
947289284Srpaulo		fst_printf_session(s, MSG_ERROR, "No new interface defined");
948289284Srpaulo		return -EINVAL;
949289284Srpaulo	}
950289284Srpaulo
951289284Srpaulo	if (s->data.new_iface == s->data.old_iface) {
952289284Srpaulo		fst_printf_session(s, MSG_ERROR,
953289284Srpaulo				   "Same interface set as old and new");
954289284Srpaulo		return -EINVAL;
955289284Srpaulo	}
956289284Srpaulo
957337817Scy	if (!fst_iface_is_connected(s->data.old_iface,
958337817Scy				    s->data.old_peer_addr, FALSE)) {
959289284Srpaulo		fst_printf_session(s, MSG_ERROR,
960289284Srpaulo				   "The preset peer address is not in the peer list");
961289284Srpaulo		return -EINVAL;
962289284Srpaulo	}
963289284Srpaulo
964289284Srpaulo	fst_session_stt_disarm(s);
965289284Srpaulo
966289284Srpaulo	os_memset(&res, 0, sizeof(res));
967289284Srpaulo
968289284Srpaulo	res.action = FST_ACTION_SETUP_RESPONSE;
969289284Srpaulo	res.dialog_token = s->data.pending_setup_req_dlgt;
970289284Srpaulo	res.status_code = status_code;
971289284Srpaulo
972289284Srpaulo	res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
973289284Srpaulo	res.stie.length = sizeof(res.stie) - 2;
974289284Srpaulo
975289284Srpaulo	if (status_code == WLAN_STATUS_SUCCESS) {
976337817Scy		res.stie.fsts_id = host_to_le32(s->data.fsts_id);
977289284Srpaulo		res.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
978289284Srpaulo
979289284Srpaulo		fst_iface_get_channel_info(s->data.new_iface, &hw_mode,
980289284Srpaulo					   &channel);
981289284Srpaulo		res.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
982289284Srpaulo		res.stie.new_band_op = 1;
983289284Srpaulo		res.stie.new_band_setup = 0;
984289284Srpaulo
985289284Srpaulo		fst_iface_get_channel_info(s->data.old_iface, &hw_mode,
986289284Srpaulo					   &channel);
987289284Srpaulo		res.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
988289284Srpaulo		res.stie.old_band_op = 1;
989289284Srpaulo		res.stie.old_band_setup = 0;
990289284Srpaulo
991289284Srpaulo		fst_printf_session(s, MSG_INFO,
992289284Srpaulo				   "%s: FST Setup Request accepted for %s (llt=%u)",
993289284Srpaulo				   fst_iface_get_name(s->data.old_iface),
994289284Srpaulo				   fst_iface_get_name(s->data.new_iface),
995289284Srpaulo				   s->data.llt_ms);
996289284Srpaulo	} else {
997289284Srpaulo		fst_printf_session(s, MSG_WARNING,
998289284Srpaulo				   "%s: FST Setup Request rejected with code %d",
999289284Srpaulo				   fst_iface_get_name(s->data.old_iface),
1000289284Srpaulo				   status_code);
1001289284Srpaulo	}
1002289284Srpaulo
1003289284Srpaulo	if (fst_session_send_action(s, TRUE, &res, sizeof(res),
1004289284Srpaulo				    fst_iface_get_mbie(s->data.old_iface))) {
1005289284Srpaulo		fst_printf_sframe(s, TRUE, MSG_ERROR,
1006289284Srpaulo				  "cannot send FST Setup Response with code %d",
1007289284Srpaulo				  status_code);
1008289284Srpaulo		return -EINVAL;
1009289284Srpaulo	}
1010289284Srpaulo
1011289284Srpaulo	fst_printf_sframe(s, TRUE, MSG_INFO, "FST Setup Response sent");
1012289284Srpaulo
1013289284Srpaulo	if (status_code != WLAN_STATUS_SUCCESS) {
1014289284Srpaulo		union fst_session_state_switch_extra evext = {
1015289284Srpaulo			.to_initial = {
1016289284Srpaulo				.reason = REASON_REJECT,
1017289284Srpaulo				.reject_code = status_code,
1018289284Srpaulo				.initiator = FST_INITIATOR_LOCAL,
1019289284Srpaulo			},
1020289284Srpaulo		};
1021289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
1022289284Srpaulo	}
1023289284Srpaulo
1024289284Srpaulo	return 0;
1025289284Srpaulo}
1026289284Srpaulo
1027289284Srpaulo
1028289284Srpauloint fst_session_initiate_switch(struct fst_session *s)
1029289284Srpaulo{
1030289284Srpaulo	struct fst_ack_req req;
1031289284Srpaulo	int res;
1032289284Srpaulo	u8 dialog_token;
1033289284Srpaulo
1034289284Srpaulo	if (!fst_session_is_ready(s)) {
1035289284Srpaulo		fst_printf_session(s, MSG_ERROR,
1036289284Srpaulo				   "cannot initiate switch due to wrong setup state (%d)",
1037289284Srpaulo				   s->state);
1038289284Srpaulo		return -1;
1039289284Srpaulo	}
1040289284Srpaulo
1041289284Srpaulo	dialog_token = fst_group_assign_dialog_token(s->group);
1042289284Srpaulo
1043289284Srpaulo	WPA_ASSERT(s->data.new_iface != NULL);
1044289284Srpaulo	WPA_ASSERT(s->data.old_iface != NULL);
1045289284Srpaulo
1046289284Srpaulo	fst_printf_session(s, MSG_INFO, "initiating FST switch: %s => %s",
1047289284Srpaulo			   fst_iface_get_name(s->data.old_iface),
1048289284Srpaulo			   fst_iface_get_name(s->data.new_iface));
1049289284Srpaulo
1050289284Srpaulo	os_memset(&req, 0, sizeof(req));
1051289284Srpaulo
1052289284Srpaulo	req.action = FST_ACTION_ACK_REQUEST;
1053289284Srpaulo	req.dialog_token = dialog_token;
1054289284Srpaulo	req.fsts_id = host_to_le32(s->data.fsts_id);
1055289284Srpaulo
1056289284Srpaulo	res = fst_session_send_action(s, FALSE, &req, sizeof(req), NULL);
1057289284Srpaulo	if (!res) {
1058289284Srpaulo		fst_printf_sframe(s, FALSE, MSG_INFO, "FST Ack Request sent");
1059289284Srpaulo		fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE,
1060289284Srpaulo				      NULL);
1061289284Srpaulo		fst_session_stt_arm(s);
1062289284Srpaulo	} else {
1063289284Srpaulo		fst_printf_sframe(s, FALSE, MSG_ERROR,
1064289284Srpaulo				  "Cannot send FST Ack Request");
1065289284Srpaulo	}
1066289284Srpaulo
1067289284Srpaulo	return res;
1068289284Srpaulo}
1069289284Srpaulo
1070289284Srpaulo
1071289284Srpaulovoid fst_session_handle_action(struct fst_session *s,
1072289284Srpaulo			       struct fst_iface *iface,
1073289284Srpaulo			       const struct ieee80211_mgmt *mgmt,
1074289284Srpaulo			       size_t frame_len)
1075289284Srpaulo{
1076289284Srpaulo	switch (mgmt->u.action.u.fst_action.action) {
1077289284Srpaulo	case FST_ACTION_SETUP_REQUEST:
1078289284Srpaulo		WPA_ASSERT(0);
1079289284Srpaulo		break;
1080289284Srpaulo	case FST_ACTION_SETUP_RESPONSE:
1081289284Srpaulo		fst_session_handle_setup_response(s, iface, mgmt, frame_len);
1082289284Srpaulo		break;
1083289284Srpaulo	case FST_ACTION_TEAR_DOWN:
1084289284Srpaulo		fst_session_handle_tear_down(s, iface, mgmt, frame_len);
1085289284Srpaulo		break;
1086289284Srpaulo	case FST_ACTION_ACK_REQUEST:
1087289284Srpaulo		fst_session_handle_ack_request(s, iface, mgmt, frame_len);
1088289284Srpaulo		break;
1089289284Srpaulo	case FST_ACTION_ACK_RESPONSE:
1090289284Srpaulo		fst_session_handle_ack_response(s, iface, mgmt, frame_len);
1091289284Srpaulo		break;
1092289284Srpaulo	case FST_ACTION_ON_CHANNEL_TUNNEL:
1093289284Srpaulo	default:
1094289284Srpaulo		fst_printf_sframe(s, FALSE, MSG_ERROR,
1095289284Srpaulo				  "Unsupported FST Action frame");
1096289284Srpaulo		break;
1097289284Srpaulo	}
1098289284Srpaulo}
1099289284Srpaulo
1100289284Srpaulo
1101289284Srpauloint fst_session_tear_down_setup(struct fst_session *s)
1102289284Srpaulo{
1103289284Srpaulo	int res;
1104289284Srpaulo	union fst_session_state_switch_extra evext = {
1105289284Srpaulo		.to_initial = {
1106289284Srpaulo			.reason = REASON_TEARDOWN,
1107289284Srpaulo			.initiator = FST_INITIATOR_LOCAL,
1108289284Srpaulo		},
1109289284Srpaulo	};
1110289284Srpaulo
1111289284Srpaulo	res = fst_session_send_tear_down(s);
1112289284Srpaulo
1113289284Srpaulo	fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
1114289284Srpaulo
1115289284Srpaulo	return res;
1116289284Srpaulo}
1117289284Srpaulo
1118289284Srpaulo
1119289284Srpaulovoid fst_session_reset(struct fst_session *s)
1120289284Srpaulo{
1121289284Srpaulo	fst_session_reset_ex(s, REASON_RESET);
1122289284Srpaulo}
1123289284Srpaulo
1124289284Srpaulo
1125289284Srpaulovoid fst_session_delete(struct fst_session *s)
1126289284Srpaulo{
1127289284Srpaulo	fst_printf(MSG_INFO, "Session %u deleted", s->id);
1128289284Srpaulo	dl_list_del(&s->global_sessions_lentry);
1129289284Srpaulo	foreach_fst_ctrl_call(on_session_removed, s);
1130289284Srpaulo	os_free(s);
1131289284Srpaulo}
1132289284Srpaulo
1133289284Srpaulo
1134289284Srpaulostruct fst_group * fst_session_get_group(struct fst_session *s)
1135289284Srpaulo{
1136289284Srpaulo	return s->group;
1137289284Srpaulo}
1138289284Srpaulo
1139289284Srpaulo
1140289284Srpaulostruct fst_iface * fst_session_get_iface(struct fst_session *s, Boolean is_old)
1141289284Srpaulo{
1142289284Srpaulo	return is_old ? s->data.old_iface : s->data.new_iface;
1143289284Srpaulo}
1144289284Srpaulo
1145289284Srpaulo
1146289284Srpaulou32 fst_session_get_id(struct fst_session *s)
1147289284Srpaulo{
1148289284Srpaulo	return s->id;
1149289284Srpaulo}
1150289284Srpaulo
1151289284Srpaulo
1152289284Srpauloconst u8 * fst_session_get_peer_addr(struct fst_session *s, Boolean is_old)
1153289284Srpaulo{
1154289284Srpaulo	return is_old ? s->data.old_peer_addr : s->data.new_peer_addr;
1155289284Srpaulo}
1156289284Srpaulo
1157289284Srpaulo
1158289284Srpaulou32 fst_session_get_llt(struct fst_session *s)
1159289284Srpaulo{
1160289284Srpaulo	return s->data.llt_ms;
1161289284Srpaulo}
1162289284Srpaulo
1163289284Srpaulo
1164289284Srpauloenum fst_session_state fst_session_get_state(struct fst_session *s)
1165289284Srpaulo{
1166289284Srpaulo	return s->state;
1167289284Srpaulo}
1168289284Srpaulo
1169289284Srpaulo
1170289284Srpaulostruct fst_session * fst_session_get_by_id(u32 id)
1171289284Srpaulo{
1172289284Srpaulo	struct fst_session *s;
1173289284Srpaulo
1174289284Srpaulo	foreach_fst_session(s) {
1175289284Srpaulo		if (id == s->id)
1176289284Srpaulo			return s;
1177289284Srpaulo	}
1178289284Srpaulo
1179289284Srpaulo	return NULL;
1180289284Srpaulo}
1181289284Srpaulo
1182289284Srpaulo
1183289284Srpaulovoid fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx)
1184289284Srpaulo{
1185289284Srpaulo	struct fst_session *s;
1186289284Srpaulo
1187289284Srpaulo	foreach_fst_session(s) {
1188289284Srpaulo		if (!g || s->group == g)
1189289284Srpaulo			clb(s->group, s, ctx);
1190289284Srpaulo	}
1191289284Srpaulo}
1192289284Srpaulo
1193289284Srpaulo
1194289284Srpaulovoid fst_session_on_action_rx(struct fst_iface *iface,
1195289284Srpaulo			      const struct ieee80211_mgmt *mgmt,
1196289284Srpaulo			      size_t len)
1197289284Srpaulo{
1198289284Srpaulo	struct fst_session *s;
1199289284Srpaulo
1200289284Srpaulo	if (len < IEEE80211_HDRLEN + 2 ||
1201289284Srpaulo	    mgmt->u.action.category != WLAN_ACTION_FST) {
1202289284Srpaulo		fst_printf_iface(iface, MSG_ERROR,
1203289284Srpaulo				 "invalid Action frame received");
1204289284Srpaulo		return;
1205289284Srpaulo	}
1206289284Srpaulo
1207289284Srpaulo	if (mgmt->u.action.u.fst_action.action <= FST_ACTION_MAX_SUPPORTED) {
1208289284Srpaulo		fst_printf_iface(iface, MSG_DEBUG,
1209289284Srpaulo				 "FST Action '%s' received!",
1210289284Srpaulo				 fst_action_names[mgmt->u.action.u.fst_action.action]);
1211289284Srpaulo	} else {
1212289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
1213289284Srpaulo				 "unknown FST Action (%u) received!",
1214289284Srpaulo				 mgmt->u.action.u.fst_action.action);
1215289284Srpaulo		return;
1216289284Srpaulo	}
1217289284Srpaulo
1218289284Srpaulo	if (mgmt->u.action.u.fst_action.action == FST_ACTION_SETUP_REQUEST) {
1219289284Srpaulo		fst_session_handle_setup_request(iface, mgmt, len);
1220289284Srpaulo		return;
1221289284Srpaulo	}
1222289284Srpaulo
1223289284Srpaulo	s = fst_find_session_in_progress(mgmt->sa, fst_iface_get_group(iface));
1224289284Srpaulo	if (s) {
1225289284Srpaulo		fst_session_handle_action(s, iface, mgmt, len);
1226289284Srpaulo	} else {
1227289284Srpaulo		fst_printf_iface(iface, MSG_WARNING,
1228289284Srpaulo				 "FST Action '%s' dropped: no session in progress found",
1229289284Srpaulo				 fst_action_names[mgmt->u.action.u.fst_action.action]);
1230289284Srpaulo	}
1231289284Srpaulo}
1232289284Srpaulo
1233289284Srpaulo
1234289284Srpauloint fst_session_set_str_ifname(struct fst_session *s, const char *ifname,
1235289284Srpaulo			       Boolean is_old)
1236289284Srpaulo{
1237289284Srpaulo	struct fst_group *g = fst_session_get_group(s);
1238289284Srpaulo	struct fst_iface *i;
1239289284Srpaulo
1240289284Srpaulo	i = fst_group_get_iface_by_name(g, ifname);
1241289284Srpaulo	if (!i) {
1242289284Srpaulo		fst_printf_session(s, MSG_WARNING,
1243289284Srpaulo				   "Cannot set iface %s: no such iface within group '%s'",
1244289284Srpaulo				   ifname, fst_group_get_id(g));
1245289284Srpaulo		return -1;
1246289284Srpaulo	}
1247289284Srpaulo
1248289284Srpaulo	fst_session_set_iface(s, i, is_old);
1249289284Srpaulo
1250289284Srpaulo	return 0;
1251289284Srpaulo}
1252289284Srpaulo
1253289284Srpaulo
1254289284Srpauloint fst_session_set_str_peer_addr(struct fst_session *s, const char *mac,
1255289284Srpaulo				  Boolean is_old)
1256289284Srpaulo{
1257289284Srpaulo	u8 peer_addr[ETH_ALEN];
1258289284Srpaulo	int res = fst_read_peer_addr(mac, peer_addr);
1259289284Srpaulo
1260289284Srpaulo	if (res)
1261289284Srpaulo		return res;
1262289284Srpaulo
1263289284Srpaulo	fst_session_set_peer_addr(s, peer_addr, is_old);
1264289284Srpaulo
1265289284Srpaulo	return 0;
1266289284Srpaulo}
1267289284Srpaulo
1268289284Srpaulo
1269289284Srpauloint fst_session_set_str_llt(struct fst_session *s, const char *llt_str)
1270289284Srpaulo{
1271289284Srpaulo	char *endp;
1272289284Srpaulo	long int llt = strtol(llt_str, &endp, 0);
1273289284Srpaulo
1274289284Srpaulo	if (*endp || llt < 0 || (unsigned long int) llt > FST_MAX_LLT_MS) {
1275289284Srpaulo		fst_printf_session(s, MSG_WARNING,
1276289284Srpaulo				   "Cannot set llt %s: Invalid llt value (1..%u expected)",
1277289284Srpaulo				   llt_str, FST_MAX_LLT_MS);
1278289284Srpaulo		return -1;
1279289284Srpaulo	}
1280289284Srpaulo	fst_session_set_llt(s, (u32) llt);
1281289284Srpaulo
1282289284Srpaulo	return 0;
1283289284Srpaulo}
1284289284Srpaulo
1285289284Srpaulo
1286289284Srpaulovoid fst_session_global_on_iface_detached(struct fst_iface *iface)
1287289284Srpaulo{
1288289284Srpaulo	struct fst_session *s;
1289289284Srpaulo
1290289284Srpaulo	foreach_fst_session(s) {
1291289284Srpaulo		if (fst_session_is_in_progress(s) &&
1292289284Srpaulo		    (s->data.new_iface == iface ||
1293289284Srpaulo		     s->data.old_iface == iface))
1294289284Srpaulo			fst_session_reset_ex(s, REASON_DETACH_IFACE);
1295289284Srpaulo	}
1296289284Srpaulo}
1297289284Srpaulo
1298289284Srpaulo
1299289284Srpaulostruct fst_session * fst_session_global_get_first_by_group(struct fst_group *g)
1300289284Srpaulo{
1301289284Srpaulo	struct fst_session *s;
1302289284Srpaulo
1303289284Srpaulo	foreach_fst_session(s) {
1304289284Srpaulo		if (s->group == g)
1305289284Srpaulo			return s;
1306289284Srpaulo	}
1307289284Srpaulo
1308289284Srpaulo	return NULL;
1309289284Srpaulo}
1310289284Srpaulo
1311289284Srpaulo
1312289284Srpaulo#ifdef CONFIG_FST_TEST
1313289284Srpaulo
1314289284Srpaulostatic int get_group_fill_session(struct fst_group **g, struct fst_session *s)
1315289284Srpaulo{
1316289284Srpaulo	const u8 *old_addr, *new_addr;
1317289284Srpaulo	struct fst_get_peer_ctx *ctx;
1318289284Srpaulo
1319289284Srpaulo	os_memset(s, 0, sizeof(*s));
1320289284Srpaulo	foreach_fst_group(*g) {
1321289284Srpaulo		s->data.new_iface = fst_group_first_iface(*g);
1322289284Srpaulo		if (s->data.new_iface)
1323289284Srpaulo			break;
1324289284Srpaulo	}
1325289284Srpaulo	if (!s->data.new_iface)
1326289284Srpaulo		return -EINVAL;
1327289284Srpaulo
1328289284Srpaulo	s->data.old_iface = dl_list_entry(s->data.new_iface->group_lentry.next,
1329289284Srpaulo					  struct fst_iface, group_lentry);
1330289284Srpaulo	if (!s->data.old_iface)
1331289284Srpaulo		return -EINVAL;
1332289284Srpaulo
1333289284Srpaulo	old_addr = fst_iface_get_peer_first(s->data.old_iface, &ctx, TRUE);
1334289284Srpaulo	if (!old_addr)
1335289284Srpaulo		return -EINVAL;
1336289284Srpaulo
1337289284Srpaulo	new_addr = fst_iface_get_peer_first(s->data.new_iface, &ctx, TRUE);
1338289284Srpaulo	if (!new_addr)
1339289284Srpaulo		return -EINVAL;
1340289284Srpaulo
1341289284Srpaulo	os_memcpy(s->data.old_peer_addr, old_addr, ETH_ALEN);
1342289284Srpaulo	os_memcpy(s->data.new_peer_addr, new_addr, ETH_ALEN);
1343289284Srpaulo
1344289284Srpaulo	return 0;
1345289284Srpaulo}
1346289284Srpaulo
1347289284Srpaulo
1348289284Srpaulo#define FST_MAX_COMMAND_WORD_NAME_LENGTH 16
1349289284Srpaulo
1350289284Srpauloint fst_test_req_send_fst_request(const char *params)
1351289284Srpaulo{
1352289284Srpaulo	int fsts_id;
1353289284Srpaulo	Boolean is_valid;
1354289284Srpaulo	char *endp;
1355289284Srpaulo	struct fst_setup_req req;
1356289284Srpaulo	struct fst_session s;
1357289284Srpaulo	struct fst_group *g;
1358289284Srpaulo	enum hostapd_hw_mode hw_mode;
1359289284Srpaulo	u8 channel;
1360289284Srpaulo	char additional_param[FST_MAX_COMMAND_WORD_NAME_LENGTH];
1361289284Srpaulo
1362289284Srpaulo	if (params[0] != ' ')
1363289284Srpaulo		return -EINVAL;
1364289284Srpaulo	params++;
1365289284Srpaulo	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
1366289284Srpaulo	if (!is_valid)
1367289284Srpaulo		return -EINVAL;
1368289284Srpaulo
1369289284Srpaulo	if (get_group_fill_session(&g, &s))
1370289284Srpaulo		return -EINVAL;
1371289284Srpaulo
1372289284Srpaulo	req.action = FST_ACTION_SETUP_REQUEST;
1373289284Srpaulo	req.dialog_token = g->dialog_token;
1374289284Srpaulo	req.llt = host_to_le32(FST_LLT_MS_DEFAULT);
1375289284Srpaulo	/* 8.4.2.147 Session Transition element */
1376289284Srpaulo	req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
1377289284Srpaulo	req.stie.length = sizeof(req.stie) - 2;
1378289284Srpaulo	req.stie.fsts_id = host_to_le32(fsts_id);
1379289284Srpaulo	req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
1380289284Srpaulo
1381289284Srpaulo	fst_iface_get_channel_info(s.data.new_iface, &hw_mode, &channel);
1382289284Srpaulo	req.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
1383289284Srpaulo	req.stie.new_band_op = 1;
1384289284Srpaulo	req.stie.new_band_setup = 0;
1385289284Srpaulo
1386289284Srpaulo	fst_iface_get_channel_info(s.data.old_iface, &hw_mode, &channel);
1387289284Srpaulo	req.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
1388289284Srpaulo	req.stie.old_band_op = 1;
1389289284Srpaulo	req.stie.old_band_setup = 0;
1390289284Srpaulo
1391289284Srpaulo	if (!fst_read_next_text_param(endp, additional_param,
1392289284Srpaulo				       sizeof(additional_param), &endp)) {
1393289284Srpaulo		if (!os_strcasecmp(additional_param, FST_CTR_PVAL_BAD_NEW_BAND))
1394289284Srpaulo			req.stie.new_band_id = req.stie.old_band_id;
1395289284Srpaulo	}
1396289284Srpaulo
1397289284Srpaulo	return fst_session_send_action(&s, TRUE, &req, sizeof(req),
1398289284Srpaulo				       s.data.old_iface->mb_ie);
1399289284Srpaulo}
1400289284Srpaulo
1401289284Srpaulo
1402289284Srpauloint fst_test_req_send_fst_response(const char *params)
1403289284Srpaulo{
1404289284Srpaulo	int fsts_id;
1405289284Srpaulo	Boolean is_valid;
1406289284Srpaulo	char *endp;
1407289284Srpaulo	struct fst_setup_res res;
1408289284Srpaulo	struct fst_session s;
1409289284Srpaulo	struct fst_group *g;
1410289284Srpaulo	enum hostapd_hw_mode hw_mode;
1411289284Srpaulo	u8 status_code;
1412289284Srpaulo	u8 channel;
1413289284Srpaulo	char response[FST_MAX_COMMAND_WORD_NAME_LENGTH];
1414289284Srpaulo	struct fst_session *_s;
1415289284Srpaulo
1416289284Srpaulo	if (params[0] != ' ')
1417289284Srpaulo		return -EINVAL;
1418289284Srpaulo	params++;
1419289284Srpaulo	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
1420289284Srpaulo	if (!is_valid)
1421289284Srpaulo		return -EINVAL;
1422289284Srpaulo
1423289284Srpaulo	if (get_group_fill_session(&g, &s))
1424289284Srpaulo		return -EINVAL;
1425289284Srpaulo
1426289284Srpaulo	status_code = WLAN_STATUS_SUCCESS;
1427289284Srpaulo	if (!fst_read_next_text_param(endp, response, sizeof(response),
1428289284Srpaulo				      &endp)) {
1429289284Srpaulo		if (!os_strcasecmp(response, FST_CS_PVAL_RESPONSE_REJECT))
1430289284Srpaulo			status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
1431289284Srpaulo	}
1432289284Srpaulo
1433289284Srpaulo	os_memset(&res, 0, sizeof(res));
1434289284Srpaulo
1435289284Srpaulo	res.action = FST_ACTION_SETUP_RESPONSE;
1436289284Srpaulo	/*
1437289284Srpaulo	 * If some session has just received an FST Setup Request, then
1438289284Srpaulo	 * use the correct dialog token copied from this request.
1439289284Srpaulo	 */
1440289284Srpaulo	_s = fst_find_session_in_progress(fst_session_get_peer_addr(&s, TRUE),
1441289284Srpaulo					  g);
1442289284Srpaulo	res.dialog_token = (_s && fst_session_is_ready_pending(_s)) ?
1443289284Srpaulo		_s->data.pending_setup_req_dlgt : g->dialog_token;
1444289284Srpaulo	res.status_code  = status_code;
1445289284Srpaulo
1446289284Srpaulo	res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
1447289284Srpaulo	res.stie.length = sizeof(res.stie) - 2;
1448289284Srpaulo
1449289284Srpaulo	if (res.status_code == WLAN_STATUS_SUCCESS) {
1450337817Scy		res.stie.fsts_id = host_to_le32(fsts_id);
1451289284Srpaulo		res.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
1452289284Srpaulo
1453289284Srpaulo		fst_iface_get_channel_info(s.data.new_iface, &hw_mode,
1454289284Srpaulo					    &channel);
1455289284Srpaulo		res.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
1456289284Srpaulo		res.stie.new_band_op = 1;
1457289284Srpaulo		res.stie.new_band_setup = 0;
1458289284Srpaulo
1459289284Srpaulo		fst_iface_get_channel_info(s.data.old_iface, &hw_mode,
1460289284Srpaulo					   &channel);
1461289284Srpaulo		res.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
1462289284Srpaulo		res.stie.old_band_op = 1;
1463289284Srpaulo		res.stie.old_band_setup = 0;
1464289284Srpaulo	}
1465289284Srpaulo
1466289284Srpaulo	if (!fst_read_next_text_param(endp, response, sizeof(response),
1467289284Srpaulo				      &endp)) {
1468289284Srpaulo		if (!os_strcasecmp(response, FST_CTR_PVAL_BAD_NEW_BAND))
1469289284Srpaulo			res.stie.new_band_id = res.stie.old_band_id;
1470289284Srpaulo	}
1471289284Srpaulo
1472289284Srpaulo	return fst_session_send_action(&s, TRUE, &res, sizeof(res),
1473289284Srpaulo				       s.data.old_iface->mb_ie);
1474289284Srpaulo}
1475289284Srpaulo
1476289284Srpaulo
1477289284Srpauloint fst_test_req_send_ack_request(const char *params)
1478289284Srpaulo{
1479289284Srpaulo	int fsts_id;
1480289284Srpaulo	Boolean is_valid;
1481289284Srpaulo	char *endp;
1482289284Srpaulo	struct fst_ack_req req;
1483289284Srpaulo	struct fst_session s;
1484289284Srpaulo	struct fst_group *g;
1485289284Srpaulo
1486289284Srpaulo	if (params[0] != ' ')
1487289284Srpaulo		return -EINVAL;
1488289284Srpaulo	params++;
1489289284Srpaulo	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
1490289284Srpaulo	if (!is_valid)
1491289284Srpaulo		return -EINVAL;
1492289284Srpaulo
1493289284Srpaulo	if (get_group_fill_session(&g, &s))
1494289284Srpaulo		return -EINVAL;
1495289284Srpaulo
1496289284Srpaulo	os_memset(&req, 0, sizeof(req));
1497289284Srpaulo	req.action = FST_ACTION_ACK_REQUEST;
1498289284Srpaulo	req.dialog_token = g->dialog_token;
1499337817Scy	req.fsts_id = host_to_le32(fsts_id);
1500289284Srpaulo
1501289284Srpaulo	return fst_session_send_action(&s, FALSE, &req, sizeof(req), NULL);
1502289284Srpaulo}
1503289284Srpaulo
1504289284Srpaulo
1505289284Srpauloint fst_test_req_send_ack_response(const char *params)
1506289284Srpaulo{
1507289284Srpaulo	int fsts_id;
1508289284Srpaulo	Boolean is_valid;
1509289284Srpaulo	char *endp;
1510289284Srpaulo	struct fst_ack_res res;
1511289284Srpaulo	struct fst_session s;
1512289284Srpaulo	struct fst_group *g;
1513289284Srpaulo
1514289284Srpaulo	if (params[0] != ' ')
1515289284Srpaulo		return -EINVAL;
1516289284Srpaulo	params++;
1517289284Srpaulo	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
1518289284Srpaulo	if (!is_valid)
1519289284Srpaulo		return -EINVAL;
1520289284Srpaulo
1521289284Srpaulo	if (get_group_fill_session(&g, &s))
1522289284Srpaulo		return -EINVAL;
1523289284Srpaulo
1524289284Srpaulo	os_memset(&res, 0, sizeof(res));
1525289284Srpaulo	res.action = FST_ACTION_ACK_RESPONSE;
1526289284Srpaulo	res.dialog_token = g->dialog_token;
1527337817Scy	res.fsts_id = host_to_le32(fsts_id);
1528289284Srpaulo
1529289284Srpaulo	return fst_session_send_action(&s, FALSE, &res, sizeof(res), NULL);
1530289284Srpaulo}
1531289284Srpaulo
1532289284Srpaulo
1533289284Srpauloint fst_test_req_send_tear_down(const char *params)
1534289284Srpaulo{
1535289284Srpaulo	int fsts_id;
1536289284Srpaulo	Boolean is_valid;
1537289284Srpaulo	char *endp;
1538289284Srpaulo	struct fst_tear_down td;
1539289284Srpaulo	struct fst_session s;
1540289284Srpaulo	struct fst_group *g;
1541289284Srpaulo
1542289284Srpaulo	if (params[0] != ' ')
1543289284Srpaulo		return -EINVAL;
1544289284Srpaulo	params++;
1545289284Srpaulo	fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
1546289284Srpaulo	if (!is_valid)
1547289284Srpaulo		return -EINVAL;
1548289284Srpaulo
1549289284Srpaulo	if (get_group_fill_session(&g, &s))
1550289284Srpaulo		return -EINVAL;
1551289284Srpaulo
1552289284Srpaulo	os_memset(&td, 0, sizeof(td));
1553289284Srpaulo	td.action = FST_ACTION_TEAR_DOWN;
1554337817Scy	td.fsts_id = host_to_le32(fsts_id);
1555289284Srpaulo
1556289284Srpaulo	return fst_session_send_action(&s, TRUE, &td, sizeof(td), NULL);
1557289284Srpaulo}
1558289284Srpaulo
1559289284Srpaulo
1560289284Srpaulou32 fst_test_req_get_fsts_id(const char *params)
1561289284Srpaulo{
1562289284Srpaulo	int sid;
1563289284Srpaulo	Boolean is_valid;
1564289284Srpaulo	char *endp;
1565289284Srpaulo	struct fst_session *s;
1566289284Srpaulo
1567289284Srpaulo	if (params[0] != ' ')
1568289284Srpaulo		return FST_FSTS_ID_NOT_FOUND;
1569289284Srpaulo	params++;
1570289284Srpaulo	sid = fst_read_next_int_param(params, &is_valid, &endp);
1571289284Srpaulo	if (!is_valid)
1572289284Srpaulo		return FST_FSTS_ID_NOT_FOUND;
1573289284Srpaulo
1574289284Srpaulo	s = fst_session_get_by_id(sid);
1575289284Srpaulo	if (!s)
1576289284Srpaulo		return FST_FSTS_ID_NOT_FOUND;
1577289284Srpaulo
1578289284Srpaulo	return s->data.fsts_id;
1579289284Srpaulo}
1580289284Srpaulo
1581289284Srpaulo
1582289284Srpauloint fst_test_req_get_local_mbies(const char *request, char *buf, size_t buflen)
1583289284Srpaulo{
1584289284Srpaulo	char *endp;
1585289284Srpaulo	char ifname[FST_MAX_COMMAND_WORD_NAME_LENGTH];
1586289284Srpaulo	struct fst_group *g;
1587289284Srpaulo	struct fst_iface *iface;
1588289284Srpaulo
1589289284Srpaulo	if (request[0] != ' ')
1590289284Srpaulo		return -EINVAL;
1591289284Srpaulo	request++;
1592289284Srpaulo	if (fst_read_next_text_param(request, ifname, sizeof(ifname), &endp) ||
1593289284Srpaulo	    !*ifname)
1594289284Srpaulo		goto problem;
1595289284Srpaulo	g = dl_list_first(&fst_global_groups_list, struct fst_group,
1596289284Srpaulo			  global_groups_lentry);
1597289284Srpaulo	if (!g)
1598289284Srpaulo		goto problem;
1599289284Srpaulo	iface = fst_group_get_iface_by_name(g, ifname);
1600289284Srpaulo	if (!iface || !iface->mb_ie)
1601289284Srpaulo		goto problem;
1602289284Srpaulo	return wpa_snprintf_hex(buf, buflen, wpabuf_head(iface->mb_ie),
1603289284Srpaulo				wpabuf_len(iface->mb_ie));
1604289284Srpaulo
1605289284Srpauloproblem:
1606289284Srpaulo	return os_snprintf(buf, buflen, "FAIL\n");
1607289284Srpaulo}
1608289284Srpaulo
1609289284Srpaulo#endif /* CONFIG_FST_TEST */
1610