aboutsummaryrefslogtreecommitdiffstats
path: root/src/fst
diff options
context:
space:
mode:
authorAnton Nayshtut <qca_antonn@qca.qualcomm.com>2015-02-18 14:59:21 (GMT)
committerJouni Malinen <j@w1.fi>2015-07-16 15:26:15 (GMT)
commit717333f4e4d027c69e7c1241dc124ef7d59a6c85 (patch)
tree419a40dfe84a90ab5dc83a580e28070c94a7c0e8 /src/fst
parent290078a734f231b8bf6d057834feb2c6dbc659a4 (diff)
downloadhostap-717333f4e4d027c69e7c1241dc124ef7d59a6c85.zip
hostap-717333f4e4d027c69e7c1241dc124ef7d59a6c85.tar.gz
hostap-717333f4e4d027c69e7c1241dc124ef7d59a6c85.tar.bz2
FST: Add the Fast Session Transfer (FST) module
Fast Session Transfer (FST) is the transfer of a session from a channel to another channel in a different frequency band. The term "session" refers to non-physical layer state information kept by a pair of stations (STAs) that communicate directly (i.e., excludes forwarding). The FST is implemented in accordance with IEEE Std 802.11ad-2012. Definitions * FST interface - an interface for which FST functionality is enabled * FST group - a bunch of FST interfaces representing single multi-band STA * FST peer - a multi-band capable STA connected * FST module - multi-band operation functionality implemented in accordance with IEEE Std 802.11ad-2012 (see 10.32 Multi-band operation) as a part of hostapd/wpa_supplicant * FST manager - an external application that implements custom FST related logic, using the FST module's interfaces accessible via CLI or D-Bus This commit introduces only the FST module. Integration of the FST module into the hostapd/wpa_supplicant and corresponding CLI/D-Bus interfaces and FST related tests are covered in separate commits. FST manager application is out of scope of these commits. As FST aggregates a few interfaces into FST group, the FST module uses global CLI for both commands and notifications. It also exposes alternative non-interface based D-Bus subtree for this purposes. Configuration and Initialization * FST functionality can enabled by compilation flag (CONFIG_FST) * hostapd/wpa_supplicant controlling multiple interfaces are used for FST * once enabled by compilation, the FST can be enabled for specific interfaces in the configuration files * FST interfaces are aggregated in FST groups (fst_group_id config file entry), where each FST group: - represents one multi-band device - should have two or more FST interfaces in it * priority (fst_priority config file entry) must be configured for each FST interface. FST interface with higher priority is the interface FST will always try to switch to. Thus, for example, for the maximal throughput, it should be the fastest FST interface in the FST setup. * default Link Loss Timeout (LLT) value can be configured for each FST interface (fst_llt config file entry). It represents LLT to be used by FST when this interface is active. * FST interfaces advertise the Multi-band capability by including the Multi-band element in the corresponding frames FST CLI commands: * fst list_groups - list FST groups configured. * fst list_ifaces - list FST interfaces which belong to specific group * fst iface_peers - list Multi-Band STAs connected to specific interface * fst list_sessions - list existing FST sessions * fst session_get - get FST session info * fst session_add - create FST session object * fst session_set - set FST session parameters (old_iface, new_iface, peer_addr, llt) * fst session_initiate - initiate FST setup * fst session_respond - respond to FST setup establishemnt attempt by counterpart * fst session_transfer - initiate FST switch * fst session_teardown - tear down FST Setup but leave the session object for reuse * fst session_remove - remove FST session object FST CLI notifications: * FST-EVENT-PEER - peer state changed (CONNECT/DISCONNECT) * FST-EVENT-SESSION - FST session level notification with following sub-events: - EVENT_FST_SESSION_STATE - FST session state changed - EVENT_FST_ESTABLISHED - previously initiated FST session became established - EVENT_FST_SETUP - new FST session object created due to FST session negotiation attempt by counterpart All the FST CLI commands and notifications are also implemented on D-Bus for wpa_supplicant. IEEE 802.11 standard compliance FST module implements FST setup statemachine in compliance with IEEE 802.11ad (P802.11-REVmc/D3.3), as it described in 10.32 Multi-band operation (see also Figure 10-34 - States of the FST setup protocol). Thus, for example, the FST module initiates FST switch automatically when FST setup becomes established with LLT=0 in accordance with 10.32.2.2 Transitioning between states. At the moment, FST module only supports non-transparent STA-based FST (see 10.32.1 General). Signed-off-by: Jouni Malinen <jouni@qca.qualcomm.com>
Diffstat (limited to 'src/fst')
-rw-r--r--src/fst/Makefile8
-rw-r--r--src/fst/fst.c227
-rw-r--r--src/fst/fst.h296
-rw-r--r--src/fst/fst_ctrl_aux.c69
-rw-r--r--src/fst/fst_ctrl_aux.h91
-rw-r--r--src/fst/fst_ctrl_defs.h95
-rw-r--r--src/fst/fst_ctrl_iface.c922
-rw-r--r--src/fst/fst_ctrl_iface.h48
-rw-r--r--src/fst/fst_defs.h87
-rw-r--r--src/fst/fst_group.c447
-rw-r--r--src/fst/fst_group.h79
-rw-r--r--src/fst/fst_iface.c78
-rw-r--r--src/fst/fst_iface.h135
-rw-r--r--src/fst/fst_internal.h88
-rw-r--r--src/fst/fst_session.c1274
-rw-r--r--src/fst/fst_session.h65
16 files changed, 4009 insertions, 0 deletions
diff --git a/src/fst/Makefile b/src/fst/Makefile
new file mode 100644
index 0000000..9c41962
--- /dev/null
+++ b/src/fst/Makefile
@@ -0,0 +1,8 @@
+all:
+ @echo Nothing to be made.
+
+clean:
+ rm -f *~ *.o *.d
+
+install:
+ @echo Nothing to be made.
diff --git a/src/fst/fst.c b/src/fst/fst.c
new file mode 100644
index 0000000..b70ef36
--- /dev/null
+++ b/src/fst/fst.c
@@ -0,0 +1,227 @@
+/*
+ * FST module implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "fst/fst.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+#include "fst/fst_ctrl_iface.h"
+
+struct dl_list fst_global_ctrls_list;
+
+
+static void fst_ctrl_iface_notify_peer_state_change(struct fst_iface *iface,
+ Boolean connected,
+ const u8 *peer_addr)
+{
+ union fst_event_extra extra;
+
+ extra.peer_state.connected = connected;
+ os_strlcpy(extra.peer_state.ifname, fst_iface_get_name(iface),
+ sizeof(extra.peer_state.ifname));
+ os_memcpy(extra.peer_state.addr, peer_addr, ETH_ALEN);
+
+ foreach_fst_ctrl_call(on_event, EVENT_PEER_STATE_CHANGED,
+ iface, NULL, &extra);
+}
+
+
+static struct fst_group * fst_find_group_by_iface(const char *ifname)
+{
+ struct fst_group *g;
+
+ foreach_fst_group(g) {
+ if (fst_group_get_iface_by_name(g, ifname))
+ return g;
+ }
+
+ return NULL;
+}
+
+
+struct fst_iface * fst_attach(const char *ifname, const u8 *own_addr,
+ const struct fst_wpa_obj *iface_obj,
+ const struct fst_iface_cfg *cfg)
+{
+ struct fst_group *g;
+ struct fst_group *group = NULL;
+ struct fst_iface *iface = NULL;
+ Boolean new_group = FALSE;
+
+ WPA_ASSERT(ifname != NULL);
+ WPA_ASSERT(iface_obj != NULL);
+ WPA_ASSERT(cfg != NULL);
+
+ g = fst_find_group_by_iface(ifname);
+ if (g) {
+ fst_printf(MSG_ERROR,
+ "%s: iface is already part of an FST group: %s",
+ ifname, g->group_id);
+ return NULL;
+ }
+
+ foreach_fst_group(g) {
+ if (os_strcmp(cfg->group_id, fst_group_get_id(g)) == 0) {
+ group = g;
+ break;
+ }
+ }
+
+ if (!group) {
+ group = fst_group_create(cfg->group_id);
+ if (!group) {
+ fst_printf(MSG_ERROR, "%s: FST group cannot be created",
+ cfg->group_id);
+ return NULL;
+ }
+ new_group = TRUE;
+ }
+
+ iface = fst_iface_create(group, ifname, own_addr, iface_obj, cfg);
+ if (!iface) {
+ fst_printf_group(group, MSG_ERROR, "cannot create iface for %s",
+ ifname);
+ if (new_group)
+ fst_group_delete(group);
+ return NULL;
+ }
+
+ fst_group_attach_iface(group, iface);
+ fst_group_update_ie(group, FALSE);
+
+ foreach_fst_ctrl_call(on_iface_added, iface);
+
+ fst_printf_iface(iface, MSG_DEBUG,
+ "iface attached to group %s (prio=%d, llt=%d)",
+ cfg->group_id, cfg->priority, cfg->llt);
+
+ return iface;
+}
+
+
+void fst_detach(struct fst_iface *iface)
+{
+ struct fst_group *group = fst_iface_get_group(iface);
+
+ fst_printf_iface(iface, MSG_DEBUG, "iface detached from group %s",
+ fst_group_get_id(group));
+ fst_session_global_on_iface_detached(iface);
+ foreach_fst_ctrl_call(on_iface_removed, iface);
+ fst_group_detach_iface(group, iface);
+ fst_iface_delete(iface);
+ fst_group_update_ie(group, FALSE);
+ fst_group_delete_if_empty(group);
+}
+
+
+int fst_global_init(void)
+{
+ dl_list_init(&fst_global_groups_list);
+ dl_list_init(&fst_global_ctrls_list);
+ fst_session_global_init();
+ return 0;
+}
+
+
+void fst_global_deinit(void)
+{
+ struct fst_group *group;
+
+ fst_session_global_deinit();
+ while ((group = fst_first_group()) != NULL)
+ fst_group_delete(group);
+ while (!dl_list_empty(&fst_global_ctrls_list)) {
+ struct fst_ctrl_handle *h;
+
+ h = dl_list_first(&fst_global_ctrls_list,
+ struct fst_ctrl_handle, global_ctrls_lentry);
+ fst_global_del_ctrl(h);
+ }
+}
+
+
+struct fst_ctrl_handle * fst_global_add_ctrl(const struct fst_ctrl *ctrl)
+{
+ struct fst_ctrl_handle *h;
+
+ if (!ctrl)
+ return NULL;
+
+ h = os_zalloc(sizeof(*h));
+ if (!h)
+ return NULL;
+
+ if (ctrl->init && ctrl->init()) {
+ os_free(h);
+ return NULL;
+ }
+
+ h->ctrl = *ctrl;
+ dl_list_add_tail(&fst_global_ctrls_list, &h->global_ctrls_lentry);
+
+ return h;
+}
+
+
+void fst_global_del_ctrl(struct fst_ctrl_handle *h)
+{
+ dl_list_del(&h->global_ctrls_lentry);
+ if (h->ctrl.deinit)
+ h->ctrl.deinit();
+ os_free(h);
+}
+
+
+void fst_rx_action(struct fst_iface *iface, const struct ieee80211_mgmt *mgmt,
+ size_t len)
+{
+ if (fst_iface_is_connected(iface, mgmt->sa))
+ fst_session_on_action_rx(iface, mgmt, len);
+}
+
+
+void fst_notify_peer_connected(struct fst_iface *iface, const u8 *addr)
+{
+ if (is_zero_ether_addr(addr))
+ return;
+
+#ifndef HOSTAPD
+ fst_group_update_ie(fst_iface_get_group(iface), FALSE);
+#endif /* HOSTAPD */
+
+ fst_printf_iface(iface, MSG_DEBUG, MACSTR " became connected",
+ MAC2STR(addr));
+
+ fst_ctrl_iface_notify_peer_state_change(iface, TRUE, addr);
+}
+
+
+void fst_notify_peer_disconnected(struct fst_iface *iface, const u8 *addr)
+{
+ if (is_zero_ether_addr(addr))
+ return;
+
+#ifndef HOSTAPD
+ fst_group_update_ie(fst_iface_get_group(iface), FALSE);
+#endif /* HOSTAPD */
+
+ fst_printf_iface(iface, MSG_DEBUG, MACSTR " became disconnected",
+ MAC2STR(addr));
+
+ fst_ctrl_iface_notify_peer_state_change(iface, FALSE, addr);
+}
+
+
+Boolean fst_are_ifaces_aggregated(struct fst_iface *iface1,
+ struct fst_iface *iface2)
+{
+ return fst_iface_get_group(iface1) == fst_iface_get_group(iface2);
+}
diff --git a/src/fst/fst.h b/src/fst/fst.h
new file mode 100644
index 0000000..76c3823
--- /dev/null
+++ b/src/fst/fst.h
@@ -0,0 +1,296 @@
+/*
+ * FST module - interface definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_H
+#define FST_H
+
+#ifdef CONFIG_FST
+
+#include "common/defs.h"
+#include "fst/fst_ctrl_iface.h"
+
+/* FST module hostap integration API */
+
+#define US_IN_MS 1000
+#define LLT_UNIT_US 32 /* See 10.32.2.2 Transitioning between states */
+
+#define FST_LLT_MS_TO_VAL(m) (((u32) (m)) * US_IN_MS / LLT_UNIT_US)
+#define FST_LLT_VAL_TO_MS(v) (((u32) (v)) * LLT_UNIT_US / US_IN_MS)
+
+#define FST_MAX_LLT_MS FST_LLT_VAL_TO_MS(-1)
+#define FST_MAX_PRIO_VALUE ((u8) -1)
+#define FST_MAX_GROUP_ID_LEN IFNAMSIZ
+
+#define FST_DEFAULT_LLT_CFG_VALUE 50
+
+struct hostapd_hw_modes;
+struct ieee80211_mgmt;
+struct fst_iface;
+struct fst_group;
+struct fst_session;
+struct fst_get_peer_ctx;
+struct fst_ctrl_handle;
+
+struct fst_wpa_obj {
+ void *ctx;
+
+ /**
+ * get_bssid - Get BSSID of the interface
+ * @ctx: User context %ctx
+ * Returns: BSSID for success, %NULL for failure.
+ *
+ * NOTE: For AP it returns the own BSSID, while for STA - the BSSID of
+ * the associated AP.
+ */
+ const u8 * (*get_bssid)(void *ctx);
+
+ /**
+ * get_channel_info - Get current channel info
+ * @ctx: User context %ctx
+ * @hw_mode: OUT, current HW mode
+ * @channel: OUT, current channel
+ */
+ void (*get_channel_info)(void *ctx, enum hostapd_hw_mode *hw_mode,
+ u8 *channel);
+
+ /**
+ * get_hw_modes - Get hardware modes
+ * @ctx: User context %ctx
+ * @modes: OUT, pointer on array of hw modes
+ *
+ * Returns: Number of hw modes available.
+ */
+ int (*get_hw_modes)(void *ctx, struct hostapd_hw_modes **modes);
+
+ /**
+ * set_ies - Set interface's MB IE
+ * @ctx: User context %ctx
+ * @fst_ies: MB IE buffer
+ */
+ void (*set_ies)(void *ctx, struct wpabuf *fst_ies);
+
+ /**
+ * send_action - Send FST Action frame via the interface
+ * @ctx: User context %ctx
+ * @addr: Address of the destination STA
+ * @data: Action frame buffer
+ * Returns: 0 for success, negative error code for failure.
+ */
+ int (*send_action)(void *ctx, const u8 *addr, struct wpabuf *data);
+
+ /**
+ * get_mb_ie - Get last MB IE received from STA
+ * @ctx: User context %ctx
+ * @addr: Address of the STA
+ * Returns: MB IE buffer, %NULL if no MB IE received from the STA
+ */
+ struct wpabuf * (*get_mb_ie)(void *ctx, const u8 *addr);
+
+ /**
+ * update_mb_ie - Update last MB IE received from STA
+ * @ctx: User context %ctx
+ * @addr: Address of the STA
+ * @buf: Buffer that contains the MB IEs data
+ * @size: Size of data in %buf
+ */
+ void (*update_mb_ie)(void *ctx, const u8 *addr,
+ const u8 *buf, size_t size);
+
+ /**
+ * get_peer_first - Get MAC address of the 1st connected STA
+ * @ctx: User context %ctx
+ * @get_ctx: Context to be used for %get_peer_next call
+ * @mb_only: %TRUE if only multi-band capable peer should be reported
+ * Returns: Address of the 1st connected STA, %NULL if no STAs connected
+ */
+ const u8 * (*get_peer_first)(void *ctx,
+ struct fst_get_peer_ctx **get_ctx,
+ Boolean mb_only);
+ /**
+ * get_peer_next - Get MAC address of the next connected STA
+ * @ctx: User context %ctx
+ * @get_ctx: Context received from %get_peer_first or previous
+ * %get_peer_next call
+ * @mb_only: %TRUE if only multi-band capable peer should be reported
+ * Returns: Address of the next connected STA, %NULL if no more STAs
+ * connected
+ */
+ const u8 * (*get_peer_next)(void *ctx,
+ struct fst_get_peer_ctx **get_ctx,
+ Boolean mb_only);
+};
+
+/**
+ * fst_global_init - Global FST module initiator
+ * Returns: 0 for success, negative error code for failure.
+ * Note: The purpose of this function is to allocate and initiate global
+ * FST module data structures (linked lists, static data etc.)
+ * This function should be called prior to the 1st %fst_attach call.
+ */
+int fst_global_init(void);
+
+/**
+ * fst_global_deinit - Global FST module de-initiator
+ * Note: The purpose of this function is to deallocate and de-initiate global
+ * FST module data structures (linked lists, static data etc.)
+ */
+void fst_global_deinit(void);
+
+/**
+ * struct fst_ctrl - Notification interface for FST module
+ */
+struct fst_ctrl {
+ /**
+ * init - Initialize the notification interface
+ * Returns: 0 for success, negative error code for failure.
+ */
+ int (*init)(void);
+
+ /**
+ * deinit - Deinitialize the notification interface
+ */
+ void (*deinit)(void);
+
+ /**
+ * on_group_created - Notify about FST group creation
+ * Returns: 0 for success, negative error code for failure.
+ */
+ int (*on_group_created)(struct fst_group *g);
+
+ /**
+ * on_group_deleted - Notify about FST group deletion
+ */
+ void (*on_group_deleted)(struct fst_group *g);
+
+ /**
+ * on_iface_added - Notify about interface addition
+ * Returns: 0 for success, negative error code for failure.
+ */
+ int (*on_iface_added)(struct fst_iface *i);
+
+ /**
+ * on_iface_removed - Notify about interface removal
+ */
+ void (*on_iface_removed)(struct fst_iface *i);
+
+ /**
+ * on_session_added - Notify about FST session addition
+ * Returns: 0 for success, negative error code for failure.
+ */
+ int (*on_session_added)(struct fst_session *s);
+
+ /**
+ * on_session_removed - Notify about FST session removal
+ */
+ void (*on_session_removed)(struct fst_session *s);
+
+ /**
+ * on_event - Notify about FST event
+ * @event_type: Event type
+ * @i: Interface object that relates to the event or NULL
+ * @g: Group object that relates to the event or NULL
+ * @extra - Event specific data (see fst_ctrl_iface.h for more info)
+ */
+ void (*on_event)(enum fst_event_type event_type, struct fst_iface *i,
+ struct fst_session *s,
+ const union fst_event_extra *extra);
+};
+
+struct fst_ctrl_handle * fst_global_add_ctrl(const struct fst_ctrl *ctrl);
+void fst_global_del_ctrl(struct fst_ctrl_handle *h);
+
+/**
+ * NOTE: These values have to be read from configuration file
+ */
+struct fst_iface_cfg {
+ char group_id[FST_MAX_GROUP_ID_LEN + 1];
+ u8 priority;
+ u32 llt;
+};
+
+/**
+ * fst_attach - Attach interface to an FST group according to configuration read
+ * @ifname: Interface name
+ * @own_addr: Own interface MAC address
+ * @iface_obj: Callbacks to be used by FST module to communicate with
+ * hostapd/wpa_supplicant
+ * @cfg: FST-related interface configuration read from the configuration file
+ * Returns: FST interface object for success, %NULL for failure.
+ */
+struct fst_iface * fst_attach(const char *ifname,
+ const u8 *own_addr,
+ const struct fst_wpa_obj *iface_obj,
+ const struct fst_iface_cfg *cfg);
+
+/**
+ * fst_detach - Detach an interface
+ * @iface: FST interface object
+ */
+void fst_detach(struct fst_iface *iface);
+
+/* FST module inputs */
+/**
+ * fst_rx_action - FST Action frames handler
+ * @iface: FST interface object
+ * @mgmt: Action frame arrived
+ * @len: Action frame length
+ */
+void fst_rx_action(struct fst_iface *iface, const struct ieee80211_mgmt *mgmt,
+ size_t len);
+
+/**
+ * fst_notify_peer_connected - FST STA connect handler
+ * @iface: FST interface object
+ * @addr: Address of the connected STA
+ */
+void fst_notify_peer_connected(struct fst_iface *iface, const u8 *addr);
+
+/**
+ * fst_notify_peer_disconnected - FST STA disconnect handler
+ * @iface: FST interface object
+ * @addr: Address of the disconnected STA
+ */
+void fst_notify_peer_disconnected(struct fst_iface *iface, const u8 *addr);
+
+/* FST module auxiliary routines */
+
+/**
+ * fst_are_ifaces_aggregated - Determines whether 2 interfaces belong to the
+ * same FST group
+ * @iface1: 1st FST interface object
+ * @iface1: 2nd FST interface object
+ *
+ * Returns: %TRUE if the interfaces belong to the same FST group,
+ * %FALSE otherwise
+ */
+Boolean fst_are_ifaces_aggregated(struct fst_iface *iface1,
+ struct fst_iface *iface2);
+
+#else /* CONFIG_FST */
+
+static inline int fst_global_init(void)
+{
+ return 0;
+}
+
+static inline int fst_global_start(void)
+{
+ return 0;
+}
+
+static inline void fst_global_stop(void)
+{
+}
+
+static inline void fst_global_deinit(void)
+{
+}
+
+#endif /* CONFIG_FST */
+
+#endif /* FST_H */
diff --git a/src/fst/fst_ctrl_aux.c b/src/fst/fst_ctrl_aux.c
new file mode 100644
index 0000000..dc7b2a7
--- /dev/null
+++ b/src/fst/fst_ctrl_aux.c
@@ -0,0 +1,69 @@
+/*
+ * FST module implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "fst_ctrl_defs.h"
+#include "fst_ctrl_aux.h"
+
+
+static const char *session_event_names[] = {
+ [EVENT_FST_ESTABLISHED] FST_PVAL_EVT_TYPE_ESTABLISHED,
+ [EVENT_FST_SETUP] FST_PVAL_EVT_TYPE_SETUP,
+ [EVENT_FST_SESSION_STATE_CHANGED] FST_PVAL_EVT_TYPE_SESSION_STATE,
+};
+
+static const char *reason_names[] = {
+ [REASON_TEARDOWN] FST_CS_PVAL_REASON_TEARDOWN,
+ [REASON_SETUP] FST_CS_PVAL_REASON_SETUP,
+ [REASON_SWITCH] FST_CS_PVAL_REASON_SWITCH,
+ [REASON_STT] FST_CS_PVAL_REASON_STT,
+ [REASON_REJECT] FST_CS_PVAL_REASON_REJECT,
+ [REASON_ERROR_PARAMS] FST_CS_PVAL_REASON_ERROR_PARAMS,
+ [REASON_RESET] FST_CS_PVAL_REASON_RESET,
+ [REASON_DETACH_IFACE] FST_CS_PVAL_REASON_DETACH_IFACE,
+};
+
+static const char *session_state_names[] = {
+ [FST_SESSION_STATE_INITIAL] FST_CS_PVAL_STATE_INITIAL,
+ [FST_SESSION_STATE_SETUP_COMPLETION] FST_CS_PVAL_STATE_SETUP_COMPLETION,
+ [FST_SESSION_STATE_TRANSITION_DONE] FST_CS_PVAL_STATE_TRANSITION_DONE,
+ [FST_SESSION_STATE_TRANSITION_CONFIRMED]
+ FST_CS_PVAL_STATE_TRANSITION_CONFIRMED,
+};
+
+
+/* helpers */
+const char * fst_get_str_name(unsigned index, const char *names[],
+ size_t names_size)
+{
+ if (index >= names_size || !names[index])
+ return FST_NAME_UNKNOWN;
+ return names[index];
+}
+
+
+const char * fst_session_event_type_name(enum fst_event_type event)
+{
+ return fst_get_str_name(event, session_event_names,
+ ARRAY_SIZE(session_event_names));
+}
+
+
+const char * fst_reason_name(enum fst_reason reason)
+{
+ return fst_get_str_name(reason, reason_names, ARRAY_SIZE(reason_names));
+}
+
+
+const char * fst_session_state_name(enum fst_session_state state)
+{
+ return fst_get_str_name(state, session_state_names,
+ ARRAY_SIZE(session_state_names));
+}
diff --git a/src/fst/fst_ctrl_aux.h b/src/fst/fst_ctrl_aux.h
new file mode 100644
index 0000000..e2133f5
--- /dev/null
+++ b/src/fst/fst_ctrl_aux.h
@@ -0,0 +1,91 @@
+/*
+ * FST module - miscellaneous definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_CTRL_AUX_H
+#define FST_CTRL_AUX_H
+
+#include "common/defs.h"
+
+/* FST module control interface API */
+#define FST_INVALID_SESSION_ID ((u32) -1)
+#define FST_MAX_GROUP_ID_SIZE 32
+#define FST_MAX_INTERFACE_SIZE 32
+
+enum fst_session_state {
+ FST_SESSION_STATE_INITIAL,
+ FST_SESSION_STATE_SETUP_COMPLETION,
+ FST_SESSION_STATE_TRANSITION_DONE,
+ FST_SESSION_STATE_TRANSITION_CONFIRMED,
+ FST_SESSION_STATE_LAST
+};
+
+enum fst_event_type {
+ EVENT_FST_IFACE_STATE_CHANGED, /* An interface has been either attached
+ * to or detached from an FST group */
+ EVENT_FST_ESTABLISHED, /* FST Session has been established */
+ EVENT_FST_SETUP, /* FST Session request received */
+ EVENT_FST_SESSION_STATE_CHANGED,/* FST Session state has been changed */
+ EVENT_PEER_STATE_CHANGED /* FST related generic event occurred,
+ * see struct fst_hostap_event_data for
+ * more info */
+};
+
+enum fst_initiator {
+ FST_INITIATOR_UNDEFINED,
+ FST_INITIATOR_LOCAL,
+ FST_INITIATOR_REMOTE,
+};
+
+union fst_event_extra {
+ struct fst_event_extra_iface_state {
+ Boolean attached;
+ char ifname[FST_MAX_INTERFACE_SIZE];
+ char group_id[FST_MAX_GROUP_ID_SIZE];
+ } iface_state; /* for EVENT_FST_IFACE_STATE_CHANGED */
+ struct fst_event_extra_peer_state {
+ Boolean connected;
+ char ifname[FST_MAX_INTERFACE_SIZE];
+ u8 addr[ETH_ALEN];
+ } peer_state; /* for EVENT_PEER_STATE_CHANGED */
+ struct fst_event_extra_session_state {
+ enum fst_session_state old_state;
+ enum fst_session_state new_state;
+ union fst_session_state_switch_extra {
+ struct {
+ enum fst_reason {
+ REASON_TEARDOWN,
+ REASON_SETUP,
+ REASON_SWITCH,
+ REASON_STT,
+ REASON_REJECT,
+ REASON_ERROR_PARAMS,
+ REASON_RESET,
+ REASON_DETACH_IFACE,
+ } reason;
+ u8 reject_code; /* REASON_REJECT */
+ /* REASON_SWITCH,
+ * REASON_TEARDOWN,
+ * REASON_REJECT
+ */
+ enum fst_initiator initiator;
+ } to_initial;
+ } extra;
+ } session_state; /* for EVENT_FST_SESSION_STATE_CHANGED */
+};
+
+/* helpers - prints enum in string form */
+#define FST_NAME_UNKNOWN "UNKNOWN"
+
+const char * fst_get_str_name(unsigned index, const char *names[],
+ size_t names_size);
+
+const char * fst_session_event_type_name(enum fst_event_type);
+const char * fst_reason_name(enum fst_reason reason);
+const char * fst_session_state_name(enum fst_session_state state);
+
+#endif /* FST_CTRL_AUX_H */
diff --git a/src/fst/fst_ctrl_defs.h b/src/fst/fst_ctrl_defs.h
new file mode 100644
index 0000000..0e883b1
--- /dev/null
+++ b/src/fst/fst_ctrl_defs.h
@@ -0,0 +1,95 @@
+/*
+ * FST module - shared Control interface definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_CTRL_DEFS_H
+#define FST_CTRL_DEFS_H
+
+/* Undefined value */
+#define FST_CTRL_PVAL_NONE "NONE"
+
+/* FST-ATTACH parameters */
+#define FST_ATTACH_CMD_PNAME_LLT "llt" /* pval = desired LLT */
+#define FST_ATTACH_CMD_PNAME_PRIORITY "priority" /* pval = desired priority */
+
+/* FST-MANAGER parameters */
+/* FST Session states */
+#define FST_CS_PVAL_STATE_INITIAL "INITIAL"
+#define FST_CS_PVAL_STATE_SETUP_COMPLETION "SETUP_COMPLETION"
+#define FST_CS_PVAL_STATE_TRANSITION_DONE "TRANSITION_DONE"
+#define FST_CS_PVAL_STATE_TRANSITION_CONFIRMED "TRANSITION_CONFIRMED"
+
+/* FST Session reset reasons */
+#define FST_CS_PVAL_REASON_TEARDOWN "REASON_TEARDOWN"
+#define FST_CS_PVAL_REASON_SETUP "REASON_SETUP"
+#define FST_CS_PVAL_REASON_SWITCH "REASON_SWITCH"
+#define FST_CS_PVAL_REASON_STT "REASON_STT"
+#define FST_CS_PVAL_REASON_REJECT "REASON_REJECT"
+#define FST_CS_PVAL_REASON_ERROR_PARAMS "REASON_ERROR_PARAMS"
+#define FST_CS_PVAL_REASON_RESET "REASON_RESET"
+#define FST_CS_PVAL_REASON_DETACH_IFACE "REASON_DETACH_IFACE"
+
+/* FST Session responses */
+#define FST_CS_PVAL_RESPONSE_ACCEPT "ACCEPT"
+#define FST_CS_PVAL_RESPONSE_REJECT "REJECT"
+
+/* FST Session action initiator */
+#define FST_CS_PVAL_INITIATOR_LOCAL "LOCAL"
+#define FST_CS_PVAL_INITIATOR_REMOTE "REMOTE"
+
+/* FST-CLI subcommands and parameter names */
+#define FST_CMD_LIST_GROUPS "list_groups"
+#define FST_CMD_LIST_IFACES "list_ifaces"
+#define FST_CMD_IFACE_PEERS "iface_peers"
+#define FST_CMD_GET_PEER_MBIES "get_peer_mbies"
+#define FST_CMD_LIST_SESSIONS "list_sessions"
+#define FST_CMD_SESSION_ADD "session_add"
+#define FST_CMD_SESSION_REMOVE "session_remove"
+#define FST_CMD_SESSION_GET "session_get"
+#define FST_CSG_PNAME_OLD_PEER_ADDR "old_peer_addr" /* pval = address string */
+#define FST_CSG_PNAME_NEW_PEER_ADDR "new_peer_addr" /* pval = address string */
+#define FST_CSG_PNAME_OLD_IFNAME "old_ifname" /* pval = ifname */
+#define FST_CSG_PNAME_NEW_IFNAME "new_ifname" /* pval = ifname */
+#define FST_CSG_PNAME_LLT "llt" /* pval = numeric llt value */
+#define FST_CSG_PNAME_STATE "state" /* pval = FST_CS_PVAL_STATE_... */
+#define FST_CMD_SESSION_SET "session_set"
+#define FST_CSS_PNAME_OLD_PEER_ADDR FST_CSG_PNAME_OLD_PEER_ADDR
+#define FST_CSS_PNAME_NEW_PEER_ADDR FST_CSG_PNAME_NEW_PEER_ADDR
+#define FST_CSS_PNAME_OLD_IFNAME FST_CSG_PNAME_OLD_IFNAME
+#define FST_CSS_PNAME_NEW_IFNAME FST_CSG_PNAME_NEW_IFNAME
+#define FST_CSS_PNAME_LLT FST_CSG_PNAME_LLT
+#define FST_CMD_SESSION_INITIATE "session_initiate"
+#define FST_CMD_SESSION_RESPOND "session_respond"
+#define FST_CMD_SESSION_TRANSFER "session_transfer"
+#define FST_CMD_SESSION_TEARDOWN "session_teardown"
+
+/* Events */
+#define FST_CTRL_EVENT_IFACE "FST-EVENT-IFACE"
+#define FST_CEI_PNAME_IFNAME "ifname"
+#define FST_CEI_PNAME_GROUP "group"
+#define FST_CEI_PNAME_ATTACHED "attached"
+#define FST_CEI_PNAME_DETACHED "detached"
+#define FST_CTRL_EVENT_PEER "FST-EVENT-PEER"
+#define FST_CEP_PNAME_IFNAME "ifname"
+#define FST_CEP_PNAME_ADDR "peer_addr"
+#define FST_CEP_PNAME_CONNECTED "connected"
+#define FST_CEP_PNAME_DISCONNECTED "disconnected"
+#define FST_CTRL_EVENT_SESSION "FST-EVENT-SESSION"
+#define FST_CES_PNAME_SESSION_ID "session_id"
+#define FST_CES_PNAME_EVT_TYPE "event_type"
+#define FST_PVAL_EVT_TYPE_SESSION_STATE "EVENT_FST_SESSION_STATE"
+/* old_state/new_state: pval = FST_CS_PVAL_STATE_... */
+#define FST_CES_PNAME_OLD_STATE "old_state"
+#define FST_CES_PNAME_NEW_STATE "new_state"
+#define FST_CES_PNAME_REASON "reason" /* pval = FST_CS_PVAL_REASON_... */
+#define FST_CES_PNAME_REJECT_CODE "reject_code" /* pval = u8 code */
+/* pval = FST_CS_PVAL_INITIATOR_... */
+#define FST_CES_PNAME_INITIATOR "initiator"
+#define FST_PVAL_EVT_TYPE_ESTABLISHED "EVENT_FST_ESTABLISHED"
+#define FST_PVAL_EVT_TYPE_SETUP "EVENT_FST_SETUP"
+
+#endif /* FST_CTRL_DEFS_H */
diff --git a/src/fst/fst_ctrl_iface.c b/src/fst/fst_ctrl_iface.c
new file mode 100644
index 0000000..6d7887e
--- /dev/null
+++ b/src/fst/fst_ctrl_iface.c
@@ -0,0 +1,922 @@
+/*
+ * FST module - Control Interface implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "list.h"
+#include "fst/fst.h"
+#include "fst/fst_internal.h"
+#include "fst_ctrl_defs.h"
+#include "fst_ctrl_iface.h"
+
+
+static struct fst_group * get_fst_group_by_id(const char *id)
+{
+ struct fst_group *g;
+
+ foreach_fst_group(g) {
+ const char *group_id = fst_group_get_id(g);
+
+ if (os_strncmp(group_id, id, os_strlen(group_id)) == 0)
+ return g;
+ }
+
+ return NULL;
+}
+
+
+/* notifications */
+Boolean format_session_state_extra(const union fst_event_extra *extra,
+ char *buffer, int size)
+{
+ int len;
+ char reject_str[32] = FST_CTRL_PVAL_NONE;
+ const char *initiator = FST_CTRL_PVAL_NONE;
+ const struct fst_event_extra_session_state *ss;
+
+ if (!extra)
+ return TRUE;
+
+ ss = &extra->session_state;
+ if (ss->new_state != FST_SESSION_STATE_INITIAL)
+ return TRUE;
+
+ switch (ss->extra.to_initial.reason) {
+ case REASON_REJECT:
+ if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS)
+ os_snprintf(reject_str, sizeof(reject_str), "%u",
+ ss->extra.to_initial.reject_code);
+ /* no break */
+ case REASON_TEARDOWN:
+ case REASON_SWITCH:
+ switch (ss->extra.to_initial.initiator) {
+ case FST_INITIATOR_LOCAL:
+ initiator = FST_CS_PVAL_INITIATOR_LOCAL;
+ break;
+ case FST_INITIATOR_REMOTE:
+ initiator = FST_CS_PVAL_INITIATOR_REMOTE;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+
+ len = os_snprintf(buffer, size,
+ FST_CES_PNAME_REASON "=%s "
+ FST_CES_PNAME_REJECT_CODE "=%s "
+ FST_CES_PNAME_INITIATOR "=%s",
+ fst_reason_name(ss->extra.to_initial.reason),
+ reject_str, initiator);
+
+ return !os_snprintf_error(size, len);
+}
+
+
+static void fst_ctrl_iface_notify(u32 session_id,
+ enum fst_event_type event_type,
+ const union fst_event_extra *extra)
+{
+ struct fst_group *g;
+ struct fst_iface *f;
+ char extra_str[128] = "";
+ const struct fst_event_extra_session_state *ss;
+ const struct fst_event_extra_iface_state *is;
+ const struct fst_event_extra_peer_state *ps;
+
+ /*
+ * FST can use any of interface objects as it only sends messages
+ * on global Control Interface, so we just pick the 1st one.
+ */
+
+ g = fst_first_group();
+ if (!g)
+ return;
+
+ f = fst_group_first_iface(g);
+ if (!f)
+ return;
+
+ WPA_ASSERT(f->iface_obj.ctx);
+
+ switch (event_type) {
+ case EVENT_FST_IFACE_STATE_CHANGED:
+ if (!extra)
+ return;
+ is = &extra->iface_state;
+ wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO,
+ FST_CTRL_EVENT_IFACE " %s "
+ FST_CEI_PNAME_IFNAME "=%s "
+ FST_CEI_PNAME_GROUP "=%s",
+ is->attached ? FST_CEI_PNAME_ATTACHED :
+ FST_CEI_PNAME_DETACHED,
+ is->ifname, is->group_id);
+ break;
+ case EVENT_PEER_STATE_CHANGED:
+ if (!extra)
+ return;
+ ps = &extra->peer_state;
+ wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
+ FST_CTRL_EVENT_PEER " %s "
+ FST_CEP_PNAME_IFNAME "=%s "
+ FST_CEP_PNAME_ADDR "=" MACSTR,
+ ps->connected ? FST_CEP_PNAME_CONNECTED :
+ FST_CEP_PNAME_DISCONNECTED,
+ ps->ifname, MAC2STR(ps->addr));
+ break;
+ case EVENT_FST_SESSION_STATE_CHANGED:
+ if (!extra)
+ return;
+ if (!format_session_state_extra(extra, extra_str,
+ sizeof(extra_str))) {
+ fst_printf(MSG_ERROR,
+ "CTRL: Cannot format STATE_CHANGE extra");
+ extra_str[0] = 0;
+ }
+ ss = &extra->session_state;
+ wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
+ FST_CTRL_EVENT_SESSION " "
+ FST_CES_PNAME_SESSION_ID "=%u "
+ FST_CES_PNAME_EVT_TYPE "=%s "
+ FST_CES_PNAME_OLD_STATE "=%s "
+ FST_CES_PNAME_NEW_STATE "=%s %s",
+ session_id,
+ fst_session_event_type_name(event_type),
+ fst_session_state_name(ss->old_state),
+ fst_session_state_name(ss->new_state),
+ extra_str);
+ break;
+ case EVENT_FST_ESTABLISHED:
+ case EVENT_FST_SETUP:
+ wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
+ FST_CTRL_EVENT_SESSION " "
+ FST_CES_PNAME_SESSION_ID "=%u "
+ FST_CES_PNAME_EVT_TYPE "=%s",
+ session_id,
+ fst_session_event_type_name(event_type));
+ break;
+ }
+}
+
+
+/* command processors */
+
+/* fst session_get */
+static int session_get(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ struct fst_iface *new_iface, *old_iface;
+ const u8 *old_peer_addr, *new_peer_addr;
+ u32 id;
+
+ id = strtoul(session_id, NULL, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ old_peer_addr = fst_session_get_peer_addr(s, TRUE);
+ new_peer_addr = fst_session_get_peer_addr(s, FALSE);
+ new_iface = fst_session_get_iface(s, FALSE);
+ old_iface = fst_session_get_iface(s, TRUE);
+
+ return os_snprintf(buf, buflen,
+ FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n"
+ FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n"
+ FST_CSG_PNAME_NEW_IFNAME "=%s\n"
+ FST_CSG_PNAME_OLD_IFNAME "=%s\n"
+ FST_CSG_PNAME_LLT "=%u\n"
+ FST_CSG_PNAME_STATE "=%s\n",
+ MAC2STR(old_peer_addr),
+ MAC2STR(new_peer_addr),
+ new_iface ? fst_iface_get_name(new_iface) :
+ FST_CTRL_PVAL_NONE,
+ old_iface ? fst_iface_get_name(old_iface) :
+ FST_CTRL_PVAL_NONE,
+ fst_session_get_llt(s),
+ fst_session_state_name(fst_session_get_state(s)));
+}
+
+
+/* fst session_set */
+static int session_set(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ char *p, *q;
+ u32 id;
+ int ret;
+
+ id = strtoul(session_id, &p, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ if (*p != ' ' || !(q = os_strchr(p + 1, '=')))
+ return os_snprintf(buf, buflen, "FAIL\n");
+ p++;
+
+ if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) {
+ ret = fst_session_set_str_ifname(s, q + 1, TRUE);
+ } else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) {
+ ret = fst_session_set_str_ifname(s, q + 1, FALSE);
+ } else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) {
+ ret = fst_session_set_str_peer_addr(s, q + 1, TRUE);
+ } else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) {
+ ret = fst_session_set_str_peer_addr(s, q + 1, FALSE);
+ } else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) {
+ ret = fst_session_set_str_llt(s, q + 1);
+ } else {
+ fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
+}
+
+
+/* fst session_add/remove */
+static int session_add(const char *group_id, char *buf, size_t buflen)
+{
+ struct fst_group *g;
+ struct fst_session *s;
+
+ g = get_fst_group_by_id(group_id);
+ if (!g) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+ group_id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ s = fst_session_create(g);
+ if (!s) {
+ fst_printf(MSG_ERROR,
+ "CTRL: Cannot create session for group '%s'",
+ group_id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s));
+}
+
+
+static int session_remove(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ struct fst_group *g;
+ u32 id;
+
+ id = strtoul(session_id, NULL, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ g = fst_session_get_group(s);
+ fst_session_reset(s);
+ fst_session_delete(s);
+ fst_group_delete_if_empty(g);
+
+ return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_initiate */
+static int session_initiate(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ u32 id;
+
+ id = strtoul(session_id, NULL, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ if (fst_session_initiate_setup(s)) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_respond */
+static int session_respond(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ char *p;
+ u32 id;
+ u8 status_code;
+
+ id = strtoul(session_id, &p, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ if (*p != ' ')
+ return os_snprintf(buf, buflen, "FAIL\n");
+ p++;
+
+ if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) {
+ status_code = WLAN_STATUS_SUCCESS;
+ } else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) {
+ status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
+ } else {
+ fst_printf(MSG_WARNING,
+ "CTRL: session %u: unknown response status: %s",
+ id, p);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ if (fst_session_respond(s, status_code)) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u",
+ id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ fst_printf(MSG_INFO, "CTRL: session %u responded", id);
+
+ return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_transfer */
+static int session_transfer(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ u32 id;
+
+ id = strtoul(session_id, NULL, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ if (fst_session_initiate_switch(s)) {
+ fst_printf(MSG_WARNING,
+ "CTRL: Cannot initiate ST for session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ return os_snprintf(buf, buflen, "OK\n");
+}
+
+
+/* fst session_teardown */
+static int session_teardown(const char *session_id, char *buf, size_t buflen)
+{
+ struct fst_session *s;
+ u32 id;
+
+ id = strtoul(session_id, NULL, 0);
+
+ s = fst_session_get_by_id(id);
+ if (!s) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ if (fst_session_tear_down_setup(s)) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u",
+ id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ return os_snprintf(buf, buflen, "OK\n");
+}
+
+/* fst list_sessions */
+struct list_sessions_cb_ctx {
+ char *buf;
+ size_t buflen;
+ size_t reply_len;
+};
+
+
+static void list_session_enum_cb(struct fst_group *g, struct fst_session *s,
+ void *ctx)
+{
+ struct list_sessions_cb_ctx *c = ctx;
+ int ret;
+
+ ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s));
+
+ c->buf += ret;
+ c->buflen -= ret;
+ c->reply_len += ret;
+}
+
+
+static int list_sessions(const char *group_id, char *buf, size_t buflen)
+{
+ struct list_sessions_cb_ctx ctx;
+ struct fst_group *g;
+
+ g = get_fst_group_by_id(group_id);
+ if (!g) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+ group_id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ ctx.buf = buf;
+ ctx.buflen = buflen;
+ ctx.reply_len = 0;
+
+ fst_session_enum(g, list_session_enum_cb, &ctx);
+
+ ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n");
+
+ return ctx.reply_len;
+}
+
+
+/* fst iface_peers */
+static int iface_peers(const char *group_id, char *buf, size_t buflen)
+{
+ const char *ifname;
+ struct fst_group *g;
+ struct fst_iface *f;
+ struct fst_get_peer_ctx *ctx;
+ const u8 *addr;
+ unsigned found = 0;
+ int ret = 0;
+
+ g = get_fst_group_by_id(group_id);
+ if (!g) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+ group_id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ ifname = os_strchr(group_id, ' ');
+ if (!ifname)
+ return os_snprintf(buf, buflen, "FAIL\n");
+ ifname++;
+
+ foreach_fst_group_iface(g, f) {
+ const char *in = fst_iface_get_name(f);
+
+ if (os_strncmp(ifname, in, os_strlen(in)) == 0) {
+ found = 1;
+ break;
+ }
+ }
+
+ if (!found)
+ return os_snprintf(buf, buflen, "FAIL\n");
+
+ addr = fst_iface_get_peer_first(f, &ctx, FALSE);
+ for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, FALSE)) {
+ int res;
+
+ res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n",
+ MAC2STR(addr));
+ if (os_snprintf_error(buflen - ret, res))
+ break;
+ ret += res;
+ }
+
+ return ret;
+}
+
+
+static int get_peer_mbies(const char *params, char *buf, size_t buflen)
+{
+ char *endp;
+ char ifname[FST_MAX_INTERFACE_SIZE];
+ u8 peer_addr[ETH_ALEN];
+ struct fst_group *g;
+ struct fst_iface *iface = NULL;
+ struct wpabuf *mbies;
+ int ret;
+
+ if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) ||
+ !*ifname)
+ goto problem;
+
+ while (isspace(*endp))
+ endp++;
+ if (fst_read_peer_addr(endp, peer_addr))
+ goto problem;
+
+ foreach_fst_group(g) {
+ iface = fst_group_get_iface_by_name(g, ifname);
+ if (!iface)
+ continue;
+ }
+ if (!iface)
+ goto problem;
+
+ mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
+ if (!mbies)
+ goto problem;
+
+ ret = print_mb_ies(mbies, buf, buflen);
+ if ((size_t) ret != wpabuf_len(mbies) * 2)
+ fst_printf(MSG_WARNING, "MB IEs copied only partially");
+
+ return ret;
+
+problem:
+ return os_snprintf(buf, buflen, "FAIL\n");
+}
+
+
+/* fst list_ifaces */
+static int list_ifaces(const char *group_id, char *buf, size_t buflen)
+{
+ struct fst_group *g;
+ struct fst_iface *f;
+ int ret = 0;
+
+ g = get_fst_group_by_id(group_id);
+ if (!g) {
+ fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
+ group_id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ }
+
+ foreach_fst_group_iface(g, f) {
+ int res;
+ const u8 *iface_addr = fst_iface_get_addr(f);
+
+ res = os_snprintf(buf + ret, buflen - ret,
+ "%s|" MACSTR "|%u|%u\n",
+ fst_iface_get_name(f),
+ MAC2STR(iface_addr),
+ fst_iface_get_priority(f),
+ fst_iface_get_llt(f));
+ if (os_snprintf_error(buflen - ret, res))
+ break;
+ ret += res;
+ }
+
+ return ret;
+}
+
+
+/* fst list_groups */
+static int list_groups(const char *cmd, char *buf, size_t buflen)
+{
+ struct fst_group *g;
+ int ret = 0;
+
+ foreach_fst_group(g) {
+ int res;
+
+ res = os_snprintf(buf + ret, buflen - ret, "%s\n",
+ fst_group_get_id(g));
+ if (os_snprintf_error(buflen - ret, res))
+ break;
+ ret += res;
+ }
+
+ return ret;
+}
+
+
+int print_mb_ies(struct wpabuf *mbies, char *buf, size_t buflen)
+{
+ const u8 *p = wpabuf_head(mbies);
+ size_t s = wpabuf_len(mbies);
+ int ret = 0;
+
+ while ((size_t) ret < buflen && s--) {
+ int res;
+
+ res = os_snprintf(buf + ret, buflen - ret, "%02x", *p++);
+ if (os_snprintf_error(buflen - ret, res))
+ break;
+ ret += res;
+ }
+
+ return ret;
+}
+
+
+static const char * band_freq(enum mb_band_id band)
+{
+ static const char *band_names[] = {
+ [MB_BAND_ID_WIFI_2_4GHZ] "2.4GHZ",
+ [MB_BAND_ID_WIFI_5GHZ] "5GHZ",
+ [MB_BAND_ID_WIFI_60GHZ] "60GHZ",
+ };
+
+ return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names));
+}
+
+
+static int print_band(unsigned num, struct fst_iface *iface, const u8 *addr,
+ char *buf, size_t buflen)
+{
+ struct wpabuf *wpabuf;
+ enum hostapd_hw_mode hw_mode;
+ u8 channel;
+ int ret = 0;
+
+ fst_iface_get_channel_info(iface, &hw_mode, &channel);
+
+ ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n",
+ num, band_freq(fst_hw_mode_to_band(hw_mode)));
+ ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n",
+ num, fst_iface_get_name(iface));
+ wpabuf = fst_iface_get_peer_mb_ie(iface, addr);
+ if (wpabuf) {
+ ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=",
+ num);
+ ret += print_mb_ies(wpabuf, buf + ret, buflen - ret);
+ ret += os_snprintf(buf + ret, buflen - ret, "\n");
+ }
+ ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n",
+ num, fst_iface_get_group_id(iface));
+ ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n",
+ num, fst_iface_get_priority(iface));
+ ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n",
+ num, fst_iface_get_llt(iface));
+
+ return ret;
+}
+
+
+static void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i,
+ Boolean attached)
+{
+ union fst_event_extra extra;
+
+ os_memset(&extra, 0, sizeof(extra));
+ extra.iface_state.attached = attached;
+ os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i),
+ sizeof(extra.iface_state.ifname));
+ os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i),
+ sizeof(extra.iface_state.group_id));
+
+ fst_ctrl_iface_notify(FST_INVALID_SESSION_ID,
+ EVENT_FST_IFACE_STATE_CHANGED, &extra);
+}
+
+
+static int fst_ctrl_iface_on_iface_added(struct fst_iface *i)
+{
+ fst_ctrl_iface_on_iface_state_changed(i, TRUE);
+ return 0;
+}
+
+
+static void fst_ctrl_iface_on_iface_removed(struct fst_iface *i)
+{
+ fst_ctrl_iface_on_iface_state_changed(i, FALSE);
+}
+
+
+static void fst_ctrl_iface_on_event(enum fst_event_type event_type,
+ struct fst_iface *i, struct fst_session *s,
+ const union fst_event_extra *extra)
+{
+ u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID;
+
+ fst_ctrl_iface_notify(session_id, event_type, extra);
+}
+
+
+static const struct fst_ctrl ctrl_cli = {
+ .on_iface_added = fst_ctrl_iface_on_iface_added,
+ .on_iface_removed = fst_ctrl_iface_on_iface_removed,
+ .on_event = fst_ctrl_iface_on_event,
+};
+
+const struct fst_ctrl *fst_ctrl_cli = &ctrl_cli;
+
+
+int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
+{
+ struct fst_group *g;
+ struct fst_iface *f;
+ unsigned num = 0;
+ int ret = 0;
+
+ foreach_fst_group(g) {
+ foreach_fst_group_iface(g, f) {
+ if (fst_iface_is_connected(f, addr)) {
+ ret += print_band(num++, f, addr,
+ buf + ret, buflen - ret);
+ }
+ }
+ }
+
+ return ret;
+}
+
+
+/* fst ctrl processor */
+int fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size)
+{
+ static const struct fst_command {
+ const char *name;
+ unsigned has_param;
+ int (*process)(const char *group_id, char *buf, size_t buflen);
+ } commands[] = {
+ { FST_CMD_LIST_GROUPS, 0, list_groups},
+ { FST_CMD_LIST_IFACES, 1, list_ifaces},
+ { FST_CMD_IFACE_PEERS, 1, iface_peers},
+ { FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies},
+ { FST_CMD_LIST_SESSIONS, 1, list_sessions},
+ { FST_CMD_SESSION_ADD, 1, session_add},
+ { FST_CMD_SESSION_REMOVE, 1, session_remove},
+ { FST_CMD_SESSION_GET, 1, session_get},
+ { FST_CMD_SESSION_SET, 1, session_set},
+ { FST_CMD_SESSION_INITIATE, 1, session_initiate},
+ { FST_CMD_SESSION_RESPOND, 1, session_respond},
+ { FST_CMD_SESSION_TRANSFER, 1, session_transfer},
+ { FST_CMD_SESSION_TEARDOWN, 1, session_teardown},
+ { NULL, 0, NULL }
+ };
+ const struct fst_command *c;
+ const char *p;
+ const char *temp;
+ Boolean non_spaces_found;
+
+ for (c = commands; c->name; c++) {
+ if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0)
+ continue;
+ p = cmd + os_strlen(c->name);
+ if (c->has_param) {
+ if (!isspace(p[0]))
+ return os_snprintf(reply, reply_size, "FAIL\n");
+ p++;
+ temp = p;
+ non_spaces_found = FALSE;
+ while (*temp) {
+ if (!isspace(*temp)) {
+ non_spaces_found = TRUE;
+ break;
+ }
+ temp++;
+ }
+ if (!non_spaces_found)
+ return os_snprintf(reply, reply_size, "FAIL\n");
+ }
+ return c->process(p, reply, reply_size);
+ }
+
+ return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n");
+}
+
+
+int fst_read_next_int_param(const char *params, Boolean *valid, char **endp)
+{
+ int ret = -1;
+ const char *curp;
+
+ *valid = FALSE;
+ *endp = (char *) params;
+ curp = params;
+ if (*curp) {
+ ret = (int) strtol(curp, endp, 0);
+ if (!**endp || isspace(**endp))
+ *valid = TRUE;
+ }
+
+ return ret;
+}
+
+
+int fst_read_next_text_param(const char *params, char *buf, size_t buflen,
+ char **endp)
+{
+ size_t max_chars_to_copy;
+ char *cur_dest;
+
+ if (buflen <= 1)
+ return EINVAL;
+
+ *endp = (char *) params;
+ while (isspace(**endp))
+ (*endp)++;
+ if (!**endp)
+ return EINVAL;
+
+ max_chars_to_copy = buflen - 1;
+ /* We need 1 byte for the terminating zero */
+ cur_dest = buf;
+ while (**endp && !isspace(**endp) && max_chars_to_copy > 0) {
+ *cur_dest = **endp;
+ (*endp)++;
+ cur_dest++;
+ max_chars_to_copy--;
+ }
+ *cur_dest = 0;
+
+ return 0;
+}
+
+
+int fst_read_peer_addr(const char *mac, u8 *peer_addr)
+{
+ if (hwaddr_aton(mac, peer_addr)) {
+ fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string",
+ mac);
+ return -1;
+ }
+
+ if (is_zero_ether_addr(peer_addr) ||
+ is_multicast_ether_addr(peer_addr)) {
+ fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr",
+ mac);
+ return -1;
+ }
+
+ return 0;
+}
+
+
+int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
+ struct fst_iface_cfg *cfg)
+{
+ char *pos;
+ char *endp;
+ Boolean is_valid;
+ int val;
+
+ if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) ||
+ fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id),
+ &endp))
+ return EINVAL;
+
+ cfg->llt = FST_DEFAULT_LLT_CFG_VALUE;
+ cfg->priority = 0;
+ pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT);
+ if (pos) {
+ pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT);
+ if (*pos == '=') {
+ val = fst_read_next_int_param(pos + 1, &is_valid,
+ &endp);
+ if (is_valid)
+ cfg->llt = val;
+ }
+ }
+ pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY);
+ if (pos) {
+ pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY);
+ if (*pos == '=') {
+ val = fst_read_next_int_param(pos + 1, &is_valid,
+ &endp);
+ if (is_valid)
+ cfg->priority = (u8) val;
+ }
+ }
+
+ return 0;
+}
+
+
+int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size)
+{
+ char *endp;
+
+ if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp))
+ return EINVAL;
+
+ return 0;
+}
+
+
+/* fst iface_detach */
+int fst_iface_detach(const char *ifname)
+{
+ struct fst_group *g;
+ struct fst_iface *f;
+
+ foreach_fst_group(g) {
+ f = fst_group_get_iface_by_name(g, ifname);
+ if (f) {
+ fst_detach(f);
+ return 0;
+ }
+ }
+
+ return EINVAL;
+}
diff --git a/src/fst/fst_ctrl_iface.h b/src/fst/fst_ctrl_iface.h
new file mode 100644
index 0000000..c3b4873
--- /dev/null
+++ b/src/fst/fst_ctrl_iface.h
@@ -0,0 +1,48 @@
+/*
+ * FST module - internal Control interface definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_CTRL_IFACE_H
+#define FST_CTRL_IFACE_H
+
+#include "fst/fst_ctrl_aux.h"
+
+#ifdef CONFIG_FST
+
+/* receiver */
+int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen);
+
+int fst_ctrl_iface_receive(const char *txtaddr, char *buf, size_t buflen);
+
+extern const struct fst_ctrl *fst_ctrl_cli;
+
+#else /* CONFIG_FST */
+
+static inline int
+fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
+{
+ return 0;
+}
+
+#endif /* CONFIG_FST */
+
+
+int print_mb_ies(struct wpabuf *mbies, char *buf, size_t buflen);
+
+int fst_read_next_int_param(const char *params, Boolean *valid, char **endp);
+int fst_read_next_text_param(const char *params, char *buf, size_t buflen,
+ char **endp);
+int fst_read_peer_addr(const char *mac, u8 *peer_addr);
+
+struct fst_iface_cfg;
+
+int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
+ struct fst_iface_cfg *cfg);
+int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size);
+int fst_iface_detach(const char *ifname);
+
+#endif /* CTRL_IFACE_FST_H */
diff --git a/src/fst/fst_defs.h b/src/fst/fst_defs.h
new file mode 100644
index 0000000..8ddcc61
--- /dev/null
+++ b/src/fst/fst_defs.h
@@ -0,0 +1,87 @@
+/*
+ * FST module - FST related definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef IEEE_80211_FST_DEFS_H
+#define IEEE_80211_FST_DEFS_H
+
+/* IEEE Std 802.11ad */
+
+#define MB_STA_CHANNEL_ALL 0
+
+enum session_type {
+ SESSION_TYPE_BSS = 0, /* Infrastructure BSS */
+ SESSION_TYPE_IBSS = 1,
+ SESSION_TYPE_DLS = 2,
+ SESSION_TYPE_TDLS = 3,
+ SESSION_TYPE_PBSS = 4
+};
+
+#define SESSION_CONTROL(session_type, switch_intent) \
+ (((u8) ((session_type) & 0x7)) | ((switch_intent) ? 0x10 : 0x00))
+
+#define GET_SESSION_CONTROL_TYPE(session_control) \
+ ((u8) ((session_control) & 0x7))
+
+#define GET_SESSION_CONTROL_SWITCH_INTENT(session_control) \
+ (((session_control) & 0x10) >> 4)
+
+/* 8.4.2.147 Session Transition element */
+struct session_transition_ie {
+ u8 element_id;
+ u8 length;
+ u32 fsts_id;
+ u8 session_control;
+ u8 new_band_id;
+ u8 new_band_setup;
+ u8 new_band_op;
+ u8 old_band_id;
+ u8 old_band_setup;
+ u8 old_band_op;
+} STRUCT_PACKED;
+
+struct fst_setup_req {
+ u8 action;
+ u8 dialog_token;
+ u32 llt;
+ struct session_transition_ie stie;
+ /* Multi-band (optional) */
+ /* Wakeup Schedule (optional) */
+ /* Awake Window (optional) */
+ /* Switching Stream (optional) */
+} STRUCT_PACKED;
+
+struct fst_setup_res {
+ u8 action;
+ u8 dialog_token;
+ u8 status_code;
+ struct session_transition_ie stie;
+ /* Multi-band (optional) */
+ /* Wakeup Schedule (optional) */
+ /* Awake Window (optional) */
+ /* Switching Stream (optional) */
+ /* Timeout Interval (optional) */
+} STRUCT_PACKED;
+
+struct fst_ack_req {
+ u8 action;
+ u8 dialog_token;
+ u32 fsts_id;
+} STRUCT_PACKED;
+
+struct fst_ack_res {
+ u8 action;
+ u8 dialog_token;
+ u32 fsts_id;
+} STRUCT_PACKED;
+
+struct fst_tear_down {
+ u8 action;
+ u32 fsts_id;
+} STRUCT_PACKED;
+
+#endif /* IEEE_80211_FST_DEFS_H */
diff --git a/src/fst/fst_group.c b/src/fst/fst_group.c
new file mode 100644
index 0000000..8e73137
--- /dev/null
+++ b/src/fst/fst_group.c
@@ -0,0 +1,447 @@
+/*
+ * FST module - FST group object implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "common/ieee802_11_defs.h"
+#include "common/ieee802_11_common.h"
+#include "drivers/driver.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+
+
+struct dl_list fst_global_groups_list;
+
+#ifndef HOSTAPD
+static Boolean fst_has_fst_peer(struct fst_iface *iface, Boolean *has_peer)
+{
+ const u8 *bssid;
+
+ bssid = fst_iface_get_bssid(iface);
+ if (!bssid) {
+ *has_peer = FALSE;
+ return FALSE;
+ }
+
+ *has_peer = TRUE;
+ return fst_iface_get_peer_mb_ie(iface, bssid) != NULL;
+}
+#endif /* HOSTAPD */
+
+
+static void fst_dump_mb_ies(const char *group_id, const char *ifname,
+ struct wpabuf *mbies)
+{
+ const u8 *p = wpabuf_head(mbies);
+ size_t s = wpabuf_len(mbies);
+
+ while (s >= offsetof(struct multi_band_ie, mb_ctrl)) {
+ const struct multi_band_ie *mbie =
+ (const struct multi_band_ie *) p;
+ WPA_ASSERT(mbie->eid == WLAN_EID_MULTI_BAND);
+ WPA_ASSERT(IE_BUFFER_LENGTH(mbie->len) >= sizeof(*mbie));
+
+ fst_printf(MSG_WARNING,
+ "%s: %s: mb_ctrl=%u band_id=%u op_class=%u chan=%u bssid="
+ MACSTR
+ " beacon_int=%u tsf_offs=[%u %u %u %u %u %u %u %u] mb_cc=0x%02x tmout=%u",
+ group_id, ifname,
+ mbie->mb_ctrl, mbie->band_id, mbie->op_class,
+ mbie->chan, MAC2STR(mbie->bssid), mbie->beacon_int,
+ mbie->tsf_offs[0], mbie->tsf_offs[1],
+ mbie->tsf_offs[2], mbie->tsf_offs[3],
+ mbie->tsf_offs[4], mbie->tsf_offs[5],
+ mbie->tsf_offs[6], mbie->tsf_offs[7],
+ mbie->mb_connection_capability,
+ mbie->fst_session_tmout);
+
+ p += IE_BUFFER_LENGTH(mbie->len);
+ s -= IE_BUFFER_LENGTH(mbie->len);
+ }
+}
+
+
+static void fst_fill_mb_ie(struct wpabuf *buf, const u8 *bssid,
+ const u8 *own_addr, enum mb_band_id band, u8 channel)
+{
+ struct multi_band_ie *mbie;
+ size_t len = sizeof(*mbie);
+
+ if (own_addr)
+ len += ETH_ALEN;
+
+ mbie = wpabuf_put(buf, len);
+
+ os_memset(mbie, 0, len);
+
+ mbie->eid = WLAN_EID_MULTI_BAND;
+ mbie->len = len - IE_HEADER_SIZE;
+#ifdef HOSTAPD
+ mbie->mb_ctrl = MB_STA_ROLE_AP;
+ mbie->mb_connection_capability = MB_CONNECTION_CAPABILITY_AP;
+#else /* HOSTAPD */
+ mbie->mb_ctrl = MB_STA_ROLE_NON_PCP_NON_AP;
+ mbie->mb_connection_capability = 0;
+#endif /* HOSTAPD */
+ if (bssid)
+ os_memcpy(mbie->bssid, bssid, ETH_ALEN);
+ mbie->band_id = band;
+ mbie->op_class = 0; /* means all */
+ mbie->chan = channel;
+ mbie->fst_session_tmout = FST_DEFAULT_SESSION_TIMEOUT_TU;
+
+ if (own_addr) {
+ mbie->mb_ctrl |= MB_CTRL_STA_MAC_PRESENT;
+ os_memcpy(&mbie[1], own_addr, ETH_ALEN);
+ }
+}
+
+
+static unsigned fst_fill_iface_mb_ies(struct fst_iface *f, struct wpabuf *buf)
+{
+ const u8 *bssid;
+
+ bssid = fst_iface_get_bssid(f);
+ if (bssid) {
+ enum hostapd_hw_mode hw_mode;
+ u8 channel;
+
+ if (buf) {
+ fst_iface_get_channel_info(f, &hw_mode, &channel);
+ fst_fill_mb_ie(buf, bssid, fst_iface_get_addr(f),
+ fst_hw_mode_to_band(hw_mode), channel);
+ }
+ return 1;
+ } else {
+ unsigned bands[MB_BAND_ID_WIFI_60GHZ + 1] = {};
+ struct hostapd_hw_modes *modes;
+ enum mb_band_id b;
+ int num_modes = fst_iface_get_hw_modes(f, &modes);
+ int ret = 0;
+
+ while (num_modes--) {
+ b = fst_hw_mode_to_band(modes->mode);
+ modes++;
+ if (b >= ARRAY_SIZE(bands) || bands[b]++)
+ continue;
+ ret++;
+ if (buf)
+ fst_fill_mb_ie(buf, NULL, fst_iface_get_addr(f),
+ b, MB_STA_CHANNEL_ALL);
+ }
+ return ret;
+ }
+}
+
+
+static struct wpabuf * fst_group_create_mb_ie(struct fst_group *g,
+ struct fst_iface *i)
+{
+ struct wpabuf *buf;
+ struct fst_iface *f;
+ unsigned int nof_mbies = 0;
+ unsigned int nof_ifaces_added = 0;
+#ifndef HOSTAPD
+ Boolean has_peer;
+ Boolean has_fst_peer;
+
+ foreach_fst_group_iface(g, f) {
+ has_fst_peer = fst_has_fst_peer(f, &has_peer);
+ if (has_peer && !has_fst_peer)
+ return NULL;
+ }
+#endif /* HOSTAPD */
+
+ foreach_fst_group_iface(g, f) {
+ if (f == i)
+ continue;
+ nof_mbies += fst_fill_iface_mb_ies(f, NULL);
+ }
+
+ buf = wpabuf_alloc(nof_mbies *
+ (sizeof(struct multi_band_ie) + ETH_ALEN));
+ if (!buf) {
+ fst_printf_iface(i, MSG_ERROR,
+ "cannot allocate mem for %u MB IEs",
+ nof_mbies);
+ return NULL;
+ }
+
+ /* The list is sorted in descending order by priorities, so MB IEs will
+ * be arranged in the same order, as required by spec (see corresponding
+ * comment in.fst_attach().
+ */
+ foreach_fst_group_iface(g, f) {
+ if (f == i)
+ continue;
+
+ fst_fill_iface_mb_ies(f, buf);
+ ++nof_ifaces_added;
+
+ fst_printf_iface(i, MSG_DEBUG, "added to MB IE");
+ }
+
+ if (!nof_ifaces_added) {
+ wpabuf_free(buf);
+ buf = NULL;
+ fst_printf_iface(i, MSG_INFO,
+ "cannot add MB IE: no backup ifaces");
+ } else {
+ fst_dump_mb_ies(fst_group_get_id(g), fst_iface_get_name(i),
+ buf);
+ }
+
+ return buf;
+}
+
+
+static struct fst_iface *
+fst_group_get_new_iface_by_mbie_and_band_id(struct fst_group *g,
+ const u8 *mb_ies_buff,
+ size_t mb_ies_size,
+ u8 band_id,
+ u8 *iface_peer_addr)
+{
+ while (mb_ies_size >= offsetof(struct multi_band_ie, mb_ctrl)) {
+ const struct multi_band_ie *mbie =
+ (const struct multi_band_ie *) mb_ies_buff;
+
+ if (mbie->eid != WLAN_EID_MULTI_BAND ||
+ IE_BUFFER_LENGTH(mbie->len) < sizeof(*mbie))
+ break;
+
+ if (mbie->band_id == band_id) {
+ struct fst_iface *iface;
+
+ foreach_fst_group_iface(g, iface) {
+ const u8 *peer_addr =
+ fst_mbie_get_peer_addr(mbie);
+
+ if (peer_addr &&
+ fst_iface_is_connected(iface, peer_addr) &&
+ band_id == fst_iface_get_band_id(iface)) {
+ os_memcpy(iface_peer_addr, peer_addr,
+ ETH_ALEN);
+ return iface;
+ }
+ }
+ break;
+ }
+
+ mb_ies_buff += IE_BUFFER_LENGTH(mbie->len);
+ mb_ies_size -= IE_BUFFER_LENGTH(mbie->len);
+ }
+
+ return NULL;
+}
+
+
+struct fst_iface * fst_group_get_iface_by_name(struct fst_group *g,
+ const char *ifname)
+{
+ struct fst_iface *f;
+
+ foreach_fst_group_iface(g, f) {
+ const char *in = fst_iface_get_name(f);
+
+ if (os_strncmp(in, ifname, os_strlen(in)) == 0)
+ return f;
+ }
+
+ return NULL;
+}
+
+
+u8 fst_group_assign_dialog_token(struct fst_group *g)
+{
+ g->dialog_token++;
+ if (g->dialog_token == 0)
+ g->dialog_token++;
+ return g->dialog_token;
+}
+
+
+u32 fst_group_assign_fsts_id(struct fst_group *g)
+{
+ g->fsts_id++;
+ return g->fsts_id;
+}
+
+
+static Boolean
+fst_group_does_iface_appear_in_other_mbies(struct fst_group *g,
+ struct fst_iface *iface,
+ struct fst_iface *other,
+ u8 *peer_addr)
+{
+ struct fst_get_peer_ctx *ctx;
+ const u8 *addr;
+ const u8 *iface_addr;
+ enum mb_band_id iface_band_id;
+
+ WPA_ASSERT(g == fst_iface_get_group(iface));
+ WPA_ASSERT(g == fst_iface_get_group(other));
+
+ iface_addr = fst_iface_get_addr(iface);
+ iface_band_id = fst_iface_get_band_id(iface);
+
+ addr = fst_iface_get_peer_first(other, &ctx, TRUE);
+ for (; addr; addr = fst_iface_get_peer_next(other, &ctx, TRUE)) {
+ struct wpabuf *mbies = fst_iface_get_peer_mb_ie(other, addr);
+
+ if (mbies) {
+ u8 other_iface_peer_addr[ETH_ALEN];
+ struct fst_iface *other_new_iface =
+ fst_group_get_new_iface_by_mbie_and_band_id(
+ g,
+ wpabuf_head(mbies), wpabuf_len(mbies),
+ iface_band_id, other_iface_peer_addr);
+ if (other_new_iface == iface &&
+ os_memcmp(iface_addr, other_iface_peer_addr,
+ ETH_ALEN)) {
+ os_memcpy(peer_addr, addr, ETH_ALEN);
+ return TRUE;
+ }
+ }
+ }
+
+ return FALSE;
+}
+
+
+struct fst_iface *
+fst_group_find_new_iface_by_stie(struct fst_group *g,
+ struct fst_iface *iface,
+ const u8 *peer_addr,
+ const struct session_transition_ie *stie,
+ u8 *iface_peer_addr)
+{
+ struct fst_iface *i;
+
+ foreach_fst_group_iface(g, i) {
+ if (i == iface ||
+ stie->new_band_id != fst_iface_get_band_id(i))
+ continue;
+ if (fst_group_does_iface_appear_in_other_mbies(g, iface, i,
+ iface_peer_addr))
+ return i;
+ break;
+ }
+ return NULL;
+}
+
+
+struct fst_iface *
+fst_group_get_new_iface_by_stie_and_mbie(
+ struct fst_group *g, const u8 *mb_ies_buff, size_t mb_ies_size,
+ const struct session_transition_ie *stie, u8 *iface_peer_addr)
+{
+ return fst_group_get_new_iface_by_mbie_and_band_id(
+ g, mb_ies_buff, mb_ies_size, stie->new_band_id,
+ iface_peer_addr);
+}
+
+
+struct fst_group * fst_group_create(const char *group_id)
+{
+ struct fst_group *g;
+
+ g = os_zalloc(sizeof(*g));
+ if (g == NULL) {
+ fst_printf(MSG_ERROR, "%s: Cannot alloc group", group_id);
+ return NULL;
+ }
+
+ dl_list_init(&g->ifaces);
+ os_strlcpy(g->group_id, group_id, sizeof(g->group_id));
+
+ dl_list_add_tail(&fst_global_groups_list, &g->global_groups_lentry);
+ fst_printf_group(g, MSG_DEBUG, "instance created");
+
+ foreach_fst_ctrl_call(on_group_created, g);
+
+ return g;
+}
+
+
+void fst_group_attach_iface(struct fst_group *g, struct fst_iface *i)
+{
+ struct dl_list *list = &g->ifaces;
+ struct fst_iface *f;
+
+ /*
+ * Add new interface to the list.
+ * The list is sorted in descending order by priority to allow
+ * multiple MB IEs creation according to the spec (see 10.32 Multi-band
+ * operation, 10.32.1 General), as they should be ordered according to
+ * priorities.
+ */
+ foreach_fst_group_iface(g, f) {
+ if (fst_iface_get_priority(f) < fst_iface_get_priority(i))
+ break;
+ list = &f->group_lentry;
+ }
+ dl_list_add(list, &i->group_lentry);
+}
+
+
+void fst_group_detach_iface(struct fst_group *g, struct fst_iface *i)
+{
+ dl_list_del(&i->group_lentry);
+}
+
+
+void fst_group_delete(struct fst_group *group)
+{
+ struct fst_session *s;
+
+ dl_list_del(&group->global_groups_lentry);
+ WPA_ASSERT(dl_list_empty(&group->ifaces));
+ foreach_fst_ctrl_call(on_group_deleted, group);
+ fst_printf_group(group, MSG_DEBUG, "instance deleted");
+ while ((s = fst_session_global_get_first_by_group(group)) != NULL)
+ fst_session_delete(s);
+ os_free(group);
+}
+
+
+Boolean fst_group_delete_if_empty(struct fst_group *group)
+{
+ Boolean is_empty = !fst_group_has_ifaces(group) &&
+ !fst_session_global_get_first_by_group(group);
+
+ if (is_empty)
+ fst_group_delete(group);
+
+ return is_empty;
+}
+
+
+void fst_group_update_ie(struct fst_group *g, Boolean cleaning_up)
+{
+ struct fst_iface *i;
+
+ foreach_fst_group_iface(g, i) {
+ if (!cleaning_up) {
+ struct wpabuf *mbie = fst_group_create_mb_ie(g, i);
+
+ if (!mbie)
+ fst_printf_iface(i, MSG_WARNING,
+ "cannot create MB IE");
+
+ fst_iface_attach_mbie(i, mbie);
+ fst_iface_set_ies(i, mbie);
+ fst_printf_iface(i, MSG_DEBUG,
+ "multi-band IE set to %p", mbie);
+ } else {
+ fst_iface_attach_mbie(i, NULL);
+ fst_iface_set_ies(i, NULL);
+ }
+ }
+}
diff --git a/src/fst/fst_group.h b/src/fst/fst_group.h
new file mode 100644
index 0000000..4f33599
--- /dev/null
+++ b/src/fst/fst_group.h
@@ -0,0 +1,79 @@
+/*
+ * FST module - FST group object definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_GROUP_H
+#define FST_GROUP_H
+
+struct fst_group {
+ char group_id[IFNAMSIZ + 1];
+ struct dl_list ifaces;
+ u32 dialog_token;
+ u32 fsts_id;
+ struct dl_list global_groups_lentry;
+};
+
+struct session_transition_ie;
+
+#define foreach_fst_group_iface(g, i) \
+ dl_list_for_each((i), &(g)->ifaces, struct fst_iface, group_lentry)
+
+struct fst_group * fst_group_create(const char *group_id);
+void fst_group_attach_iface(struct fst_group *g, struct fst_iface *i);
+void fst_group_detach_iface(struct fst_group *g, struct fst_iface *i);
+void fst_group_delete(struct fst_group *g);
+
+void fst_group_update_ie(struct fst_group *g, Boolean cleaning_up);
+
+static inline Boolean fst_group_has_ifaces(struct fst_group *g)
+{
+ return !dl_list_empty(&g->ifaces);
+}
+
+static inline struct fst_iface * fst_group_first_iface(struct fst_group *g)
+{
+ if (dl_list_empty(&g->ifaces))
+ return NULL;
+ return dl_list_first(&g->ifaces, struct fst_iface, group_lentry);
+}
+
+static inline const char * fst_group_get_id(struct fst_group *g)
+{
+ return g->group_id;
+}
+
+Boolean fst_group_delete_if_empty(struct fst_group *group);
+struct fst_iface * fst_group_get_iface_by_name(struct fst_group *g,
+ const char *ifname);
+struct fst_iface *
+fst_group_find_new_iface_by_stie(struct fst_group *g,
+ struct fst_iface *iface,
+ const u8 *peer_addr,
+ const struct session_transition_ie *stie,
+ u8 *iface_peer_addr);
+struct fst_iface *
+fst_group_get_new_iface_by_stie_and_mbie(
+ struct fst_group *g, const u8 *mb_ies_buff, size_t mb_ies_size,
+ const struct session_transition_ie *stie, u8 *iface_peer_addr);
+u8 fst_group_assign_dialog_token(struct fst_group *g);
+u32 fst_group_assign_fsts_id(struct fst_group *g);
+
+extern struct dl_list fst_global_groups_list;
+
+#define foreach_fst_group(g) \
+ dl_list_for_each((g), &fst_global_groups_list, \
+ struct fst_group, global_groups_lentry)
+
+static inline struct fst_group * fst_first_group(void)
+{
+ if (dl_list_empty(&fst_global_groups_list))
+ return NULL;
+ return dl_list_first(&fst_global_groups_list, struct fst_group,
+ global_groups_lentry);
+}
+
+#endif /* FST_GROUP_H */
diff --git a/src/fst/fst_iface.c b/src/fst/fst_iface.c
new file mode 100644
index 0000000..4fba9b1
--- /dev/null
+++ b/src/fst/fst_iface.c
@@ -0,0 +1,78 @@
+/*
+ * FST module - FST interface object implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+
+
+struct fst_iface * fst_iface_create(struct fst_group *g, const char *ifname,
+ const u8 *own_addr,
+ const struct fst_wpa_obj *iface_obj,
+ const struct fst_iface_cfg *cfg)
+{
+ struct fst_iface *i;
+
+ i = os_zalloc(sizeof(*i));
+ if (!i) {
+ fst_printf_group(g, MSG_ERROR, "cannot allocate iface for %s",
+ ifname);
+ return NULL;
+ }
+
+ i->cfg = *cfg;
+ i->iface_obj = *iface_obj;
+ i->group = g;
+ os_strlcpy(i->ifname, ifname, sizeof(i->ifname));
+ os_memcpy(i->own_addr, own_addr, sizeof(i->own_addr));
+
+ if (!i->cfg.llt) {
+ fst_printf_iface(i, MSG_WARNING, "Zero llt adjusted");
+ i->cfg.llt = FST_DEFAULT_LLT_CFG_VALUE;
+ }
+
+ return i;
+}
+
+
+void fst_iface_delete(struct fst_iface *i)
+{
+ wpabuf_free(i->mb_ie);
+ os_free(i);
+}
+
+
+Boolean fst_iface_is_connected(struct fst_iface *iface, const u8 *addr)
+{
+ struct fst_get_peer_ctx *ctx;
+ const u8 *a = fst_iface_get_peer_first(iface, &ctx, TRUE);
+
+ for (; a != NULL; a = fst_iface_get_peer_next(iface, &ctx, TRUE))
+ if (os_memcmp(addr, a, ETH_ALEN) == 0)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+void fst_iface_attach_mbie(struct fst_iface *i, struct wpabuf *mbie)
+{
+ wpabuf_free(i->mb_ie);
+ i->mb_ie = mbie;
+}
+
+
+enum mb_band_id fst_iface_get_band_id(struct fst_iface *i)
+{
+ enum hostapd_hw_mode hw_mode;
+ u8 channel;
+
+ fst_iface_get_channel_info(i, &hw_mode, &channel);
+ return fst_hw_mode_to_band(hw_mode);
+}
diff --git a/src/fst/fst_iface.h b/src/fst/fst_iface.h
new file mode 100644
index 0000000..4ccea8e
--- /dev/null
+++ b/src/fst/fst_iface.h
@@ -0,0 +1,135 @@
+/*
+ * FST module - FST interface object definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+
+#ifndef FST_IFACE_H
+#define FST_IFACE_H
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "list.h"
+#include "fst.h"
+
+struct fst_iface {
+ struct fst_group *group;
+ struct fst_wpa_obj iface_obj;
+ u8 own_addr[ETH_ALEN];
+ struct wpabuf *mb_ie;
+ char ifname[IFNAMSIZ + 1];
+ struct fst_iface_cfg cfg;
+ struct dl_list group_lentry;
+};
+
+struct fst_iface * fst_iface_create(struct fst_group *g, const char *ifname,
+ const u8 *own_addr,
+ const struct fst_wpa_obj *iface_obj,
+ const struct fst_iface_cfg *cfg);
+void fst_iface_delete(struct fst_iface *i);
+
+static inline struct fst_group * fst_iface_get_group(struct fst_iface *i)
+{
+ return i->group;
+}
+
+static inline const char * fst_iface_get_name(struct fst_iface *i)
+{
+ return i->ifname;
+}
+
+static inline const u8 * fst_iface_get_addr(struct fst_iface *i)
+{
+ return i->own_addr;
+}
+
+static inline const char * fst_iface_get_group_id(struct fst_iface *i)
+{
+ return i->cfg.group_id;
+}
+
+static inline u8 fst_iface_get_priority(struct fst_iface *i)
+{
+ return i->cfg.priority;
+}
+
+static inline u32 fst_iface_get_llt(struct fst_iface *i)
+{
+ return i->cfg.llt;
+}
+
+static inline const struct wpabuf * fst_iface_get_mbie(struct fst_iface *i)
+{
+ return i->mb_ie;
+}
+
+static inline const u8 * fst_iface_get_bssid(struct fst_iface *i)
+{
+ return i->iface_obj.get_bssid(i->iface_obj.ctx);
+}
+
+static inline void fst_iface_get_channel_info(struct fst_iface *i,
+ enum hostapd_hw_mode *hw_mode,
+ u8 *channel)
+{
+ i->iface_obj.get_channel_info(i->iface_obj.ctx, hw_mode, channel);
+}
+
+static inline int fst_iface_get_hw_modes(struct fst_iface *i,
+ struct hostapd_hw_modes **modes)
+{
+ return i->iface_obj.get_hw_modes(i->iface_obj.ctx, modes);
+}
+
+static inline void fst_iface_set_ies(struct fst_iface *i,
+ struct wpabuf *fst_ies)
+{
+ i->iface_obj.set_ies(i->iface_obj.ctx, fst_ies);
+}
+
+static inline int fst_iface_send_action(struct fst_iface *i,
+ const u8 *addr, struct wpabuf *data)
+{
+ return i->iface_obj.send_action(i->iface_obj.ctx, addr, data);
+}
+
+static inline struct wpabuf * fst_iface_get_peer_mb_ie(struct fst_iface *i,
+ const u8 *addr)
+{
+ return i->iface_obj.get_mb_ie(i->iface_obj.ctx, addr);
+}
+
+static inline void fst_iface_update_mb_ie(struct fst_iface *i,
+ const u8 *addr,
+ const u8 *buf, size_t size)
+{
+ return i->iface_obj.update_mb_ie(i->iface_obj.ctx, addr, buf, size);
+}
+
+static inline const u8 * fst_iface_get_peer_first(struct fst_iface *i,
+ struct fst_get_peer_ctx **ctx,
+ Boolean mb_only)
+{
+ return i->iface_obj.get_peer_first(i->iface_obj.ctx, ctx, mb_only);
+}
+
+static inline const u8 * fst_iface_get_peer_next(struct fst_iface *i,
+ struct fst_get_peer_ctx **ctx,
+ Boolean mb_only)
+{
+ return i->iface_obj.get_peer_next(i->iface_obj.ctx, ctx, mb_only);
+}
+
+Boolean fst_iface_is_connected(struct fst_iface *iface, const u8 *addr);
+void fst_iface_attach_mbie(struct fst_iface *i, struct wpabuf *mbie);
+enum mb_band_id fst_iface_get_band_id(struct fst_iface *i);
+
+static inline void * fst_iface_get_wpa_obj_ctx(struct fst_iface *i)
+{
+ return i->iface_obj.ctx;
+}
+
+#endif /* FST_IFACE_H */
diff --git a/src/fst/fst_internal.h b/src/fst/fst_internal.h
new file mode 100644
index 0000000..948c556
--- /dev/null
+++ b/src/fst/fst_internal.h
@@ -0,0 +1,88 @@
+/*
+ * FST module - auxiliary definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_INTERNAL_H
+#define FST_INTERNAL_H
+
+#include "utils/includes.h"
+#include "utils/common.h"
+#include "common/defs.h"
+#include "common/ieee802_11_defs.h"
+#include "fst/fst_iface.h"
+#include "fst/fst_group.h"
+#include "fst/fst_session.h"
+
+#define fst_printf(level, format, ...) \
+ wpa_printf((level), "FST: " format, ##__VA_ARGS__)
+
+#define fst_printf_group(group, level, format, ...) \
+ wpa_printf((level), "FST: %s: " format, \
+ fst_group_get_id(group), ##__VA_ARGS__)
+
+#define fst_printf_iface(iface, level, format, ...) \
+ fst_printf_group(fst_iface_get_group(iface), (level), "%s: " format, \
+ fst_iface_get_name(iface), ##__VA_ARGS__)
+
+static inline enum mb_band_id
+fst_hw_mode_to_band(enum hostapd_hw_mode mode)
+{
+ switch (mode) {
+ case HOSTAPD_MODE_IEEE80211B:
+ case HOSTAPD_MODE_IEEE80211G:
+ return MB_BAND_ID_WIFI_2_4GHZ;
+ case HOSTAPD_MODE_IEEE80211A:
+ return MB_BAND_ID_WIFI_5GHZ;
+ case HOSTAPD_MODE_IEEE80211AD:
+ return MB_BAND_ID_WIFI_60GHZ;
+ default:
+ WPA_ASSERT(0);
+ return MB_BAND_ID_WIFI_2_4GHZ;
+ }
+}
+
+#define IE_HEADER_SIZE ((u8) (2 * sizeof(u8)))
+#define IE_BUFFER_LENGTH(ie_len_val) ((size_t) ((ie_len_val) + IE_HEADER_SIZE))
+
+static inline const u8 *
+fst_mbie_get_peer_addr(const struct multi_band_ie *mbie)
+{
+ const u8 *peer_addr = NULL;
+
+ switch (MB_CTRL_ROLE(mbie->mb_ctrl)) {
+ case MB_STA_ROLE_AP:
+ peer_addr = mbie->bssid;
+ break;
+ case MB_STA_ROLE_NON_PCP_NON_AP:
+ if (mbie->mb_ctrl & MB_CTRL_STA_MAC_PRESENT &&
+ IE_BUFFER_LENGTH(mbie->len) >= sizeof(*mbie) + ETH_ALEN)
+ peer_addr = (const u8 *) &mbie[1];
+ break;
+ default:
+ break;
+ }
+
+ return peer_addr;
+}
+
+struct fst_ctrl_handle {
+ struct fst_ctrl ctrl;
+ struct dl_list global_ctrls_lentry;
+};
+
+extern struct dl_list fst_global_ctrls_list;
+
+#define foreach_fst_ctrl_call(clb, ...) \
+ do { \
+ struct fst_ctrl_handle *__fst_ctrl_h; \
+ dl_list_for_each(__fst_ctrl_h, &fst_global_ctrls_list, \
+ struct fst_ctrl_handle, global_ctrls_lentry) \
+ if (__fst_ctrl_h->ctrl.clb) \
+ __fst_ctrl_h->ctrl.clb(__VA_ARGS__);\
+ } while (0)
+
+#endif /* FST_INTERNAL_H */
diff --git a/src/fst/fst_session.c b/src/fst/fst_session.c
new file mode 100644
index 0000000..da994d8
--- /dev/null
+++ b/src/fst/fst_session.c
@@ -0,0 +1,1274 @@
+/*
+ * FST module - FST Session implementation
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#include "utils/includes.h"
+
+#include "utils/common.h"
+#include "utils/eloop.h"
+#include "common/defs.h"
+#include "fst/fst_internal.h"
+#include "fst/fst_defs.h"
+#include "fst/fst_ctrl_iface.h"
+
+#define US_80211_TU 1024
+
+#define US_TO_TU(m) ((m) * / US_80211_TU)
+#define TU_TO_US(m) ((m) * US_80211_TU)
+
+#define FST_LLT_SWITCH_IMMEDIATELY 0
+
+#define fst_printf_session(s, level, format, ...) \
+ fst_printf((level), "%u (0x%08x): [" MACSTR "," MACSTR "] :" format, \
+ (s)->id, (s)->data.fsts_id, \
+ MAC2STR((s)->data.old_peer_addr), \
+ MAC2STR((s)->data.new_peer_addr), \
+ ##__VA_ARGS__)
+
+#define fst_printf_siface(s, iface, level, format, ...) \
+ fst_printf_session((s), (level), "%s: " format, \
+ fst_iface_get_name(iface), ##__VA_ARGS__)
+
+#define fst_printf_sframe(s, is_old, level, format, ...) \
+ fst_printf_siface((s), \
+ (is_old) ? (s)->data.old_iface : (s)->data.new_iface, \
+ (level), format, ##__VA_ARGS__)
+
+#define FST_LLT_MS_DEFAULT 50
+#define FST_ACTION_MAX_SUPPORTED FST_ACTION_ON_CHANNEL_TUNNEL
+
+const char * const fst_action_names[] = {
+ [FST_ACTION_SETUP_REQUEST] = "Setup Request",
+ [FST_ACTION_SETUP_RESPONSE] = "Setup Response",
+ [FST_ACTION_TEAR_DOWN] = "Tear Down",
+ [FST_ACTION_ACK_REQUEST] = "Ack Request",
+ [FST_ACTION_ACK_RESPONSE] = "Ack Response",
+ [FST_ACTION_ON_CHANNEL_TUNNEL] = "On Channel Tunnel",
+};
+
+struct fst_session {
+ struct {
+ /* Session configuration that can be zeroed on reset */
+ u8 old_peer_addr[ETH_ALEN];
+ u8 new_peer_addr[ETH_ALEN];
+ struct fst_iface *new_iface;
+ struct fst_iface *old_iface;
+ u32 llt_ms;
+ u8 pending_setup_req_dlgt;
+ u32 fsts_id; /* FSTS ID, see spec, 8.4.2.147
+ * Session Transition element */
+ } data;
+ /* Session object internal fields which won't be zeroed on reset */
+ struct dl_list global_sessions_lentry;
+ u32 id; /* Session object ID used to identify
+ * specific session object */
+ struct fst_group *group;
+ enum fst_session_state state;
+ Boolean stt_armed;
+};
+
+static struct dl_list global_sessions_list;
+static u32 global_session_id = 0;
+
+#define foreach_fst_session(s) \
+ dl_list_for_each((s), &global_sessions_list, \
+ struct fst_session, global_sessions_lentry)
+
+#define foreach_fst_session_safe(s, temp) \
+ dl_list_for_each_safe((s), (temp), &global_sessions_list, \
+ struct fst_session, global_sessions_lentry)
+
+
+static void fst_session_global_inc_id(void)
+{
+ global_session_id++;
+ if (global_session_id == FST_INVALID_SESSION_ID)
+ global_session_id++;
+}
+
+
+int fst_session_global_init(void)
+{
+ dl_list_init(&global_sessions_list);
+ return 0;
+}
+
+
+void fst_session_global_deinit(void)
+{
+ WPA_ASSERT(dl_list_empty(&global_sessions_list));
+}
+
+
+static inline void fst_session_notify_ctrl(struct fst_session *s,
+ enum fst_event_type event_type,
+ union fst_event_extra *extra)
+{
+ foreach_fst_ctrl_call(on_event, event_type, NULL, s, extra);
+}
+
+
+static void fst_session_set_state(struct fst_session *s,
+ enum fst_session_state state,
+ union fst_session_state_switch_extra *extra)
+{
+ if (s->state != state) {
+ union fst_event_extra evext = {
+ .session_state = {
+ .old_state = s->state,
+ .new_state = state,
+ },
+ };
+
+ if (extra)
+ evext.session_state.extra = *extra;
+ fst_session_notify_ctrl(s, EVENT_FST_SESSION_STATE_CHANGED,
+ &evext);
+ fst_printf_session(s, MSG_INFO, "State: %s => %s",
+ fst_session_state_name(s->state),
+ fst_session_state_name(state));
+ s->state = state;
+ }
+}
+
+
+static u32 fst_find_free_session_id(void)
+{
+ u32 i, id = FST_INVALID_SESSION_ID;
+ struct fst_session *s;
+
+ for (i = 0; i < (u32) -1; i++) {
+ Boolean in_use = FALSE;
+
+ foreach_fst_session(s) {
+ if (s->id == global_session_id) {
+ fst_session_global_inc_id();
+ in_use = TRUE;
+ break;
+ }
+ }
+ if (!in_use) {
+ id = global_session_id;
+ fst_session_global_inc_id();
+ break;
+ }
+ }
+
+ return id;
+}
+
+
+static void fst_session_timeout_handler(void *eloop_data, void *user_ctx)
+{
+ struct fst_session *s = user_ctx;
+ union fst_session_state_switch_extra extra = {
+ .to_initial = {
+ .reason = REASON_STT,
+ },
+ };
+
+ fst_printf_session(s, MSG_WARNING, "Session State Timeout");
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &extra);
+}
+
+
+static void fst_session_stt_arm(struct fst_session *s)
+{
+ eloop_register_timeout(0, TU_TO_US(FST_DEFAULT_SESSION_TIMEOUT_TU),
+ fst_session_timeout_handler, NULL, s);
+ s->stt_armed = TRUE;
+}
+
+
+static void fst_session_stt_disarm(struct fst_session *s)
+{
+ if (s->stt_armed) {
+ eloop_cancel_timeout(fst_session_timeout_handler, NULL, s);
+ s->stt_armed = FALSE;
+ }
+}
+
+
+static Boolean fst_session_is_in_transition(struct fst_session *s)
+{
+ /* See spec, 10.32.2.2 Transitioning between states */
+ return s->stt_armed;
+}
+
+
+static int fst_session_is_in_progress(struct fst_session *s)
+{
+ return s->state != FST_SESSION_STATE_INITIAL;
+}
+
+
+static int fst_session_is_ready_pending(struct fst_session *s)
+{
+ return s->state == FST_SESSION_STATE_SETUP_COMPLETION &&
+ fst_session_is_in_transition(s);
+}
+
+
+static int fst_session_is_ready(struct fst_session *s)
+{
+ return s->state == FST_SESSION_STATE_SETUP_COMPLETION &&
+ !fst_session_is_in_transition(s);
+}
+
+
+static int fst_session_is_switch_requested(struct fst_session *s)
+{
+ return s->state == FST_SESSION_STATE_TRANSITION_DONE &&
+ fst_session_is_in_transition(s);
+}
+
+
+static struct fst_session *
+fst_find_session_in_progress(const u8 *peer_addr, struct fst_group *g)
+{
+ struct fst_session *s;
+
+ foreach_fst_session(s) {
+ if (s->group == g &&
+ (os_memcmp(s->data.old_peer_addr, peer_addr,
+ ETH_ALEN) == 0 ||
+ os_memcmp(s->data.new_peer_addr, peer_addr,
+ ETH_ALEN) == 0) &&
+ fst_session_is_in_progress(s))
+ return s;
+ }
+
+ return NULL;
+}
+
+
+static void fst_session_reset_ex(struct fst_session *s, enum fst_reason reason)
+{
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = reason,
+ },
+ };
+
+ if (s->state == FST_SESSION_STATE_SETUP_COMPLETION ||
+ s->state == FST_SESSION_STATE_TRANSITION_DONE)
+ fst_session_tear_down_setup(s);
+ fst_session_stt_disarm(s);
+ os_memset(&s->data, 0, sizeof(s->data));
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+}
+
+
+static int fst_session_send_action(struct fst_session *s, Boolean old_iface,
+ const void *payload, size_t size,
+ const struct wpabuf *extra_buf)
+{
+ size_t len;
+ int res;
+ struct wpabuf *buf;
+ u8 action;
+ struct fst_iface *iface =
+ old_iface ? s->data.old_iface : s->data.new_iface;
+
+ WPA_ASSERT(payload != NULL);
+ WPA_ASSERT(size != 0);
+
+ action = *(const u8 *) payload;
+
+ WPA_ASSERT(action <= FST_ACTION_MAX_SUPPORTED);
+
+ if (!iface) {
+ fst_printf_session(s, MSG_ERROR,
+ "no %s interface for FST Action '%s' sending",
+ old_iface ? "old" : "new",
+ fst_action_names[action]);
+ return -1;
+ }
+
+ len = sizeof(u8) /* category */ + size;
+ if (extra_buf)
+ len += wpabuf_size(extra_buf);
+
+ buf = wpabuf_alloc(len);
+ if (!buf) {
+ fst_printf_session(s, MSG_ERROR,
+ "cannot allocate buffer of %zu bytes for FST Action '%s' sending",
+ len, fst_action_names[action]);
+ return -1;
+ }
+
+ wpabuf_put_u8(buf, WLAN_ACTION_FST);
+ wpabuf_put_data(buf, payload, size);
+ if (extra_buf)
+ wpabuf_put_buf(buf, extra_buf);
+
+ res = fst_iface_send_action(iface,
+ old_iface ? s->data.old_peer_addr :
+ s->data.new_peer_addr, buf);
+ if (res < 0)
+ fst_printf_siface(s, iface, MSG_ERROR,
+ "failed to send FST Action '%s'",
+ fst_action_names[action]);
+ else
+ fst_printf_siface(s, iface, MSG_DEBUG, "FST Action '%s' sent",
+ fst_action_names[action]);
+ wpabuf_free(buf);
+
+ return res;
+}
+
+
+static int fst_session_send_tear_down(struct fst_session *s)
+{
+ struct fst_tear_down td;
+ int res;
+
+ if (!fst_session_is_in_progress(s)) {
+ fst_printf_session(s, MSG_ERROR, "No FST setup to tear down");
+ return -1;
+ }
+
+ WPA_ASSERT(s->data.old_iface != NULL);
+ WPA_ASSERT(s->data.new_iface != NULL);
+
+ os_memset(&td, 0, sizeof(td));
+
+ td.action = FST_ACTION_TEAR_DOWN;
+ td.fsts_id = host_to_le32(s->data.fsts_id);
+
+ res = fst_session_send_action(s, TRUE, &td, sizeof(td), NULL);
+ if (!res)
+ fst_printf_sframe(s, TRUE, MSG_INFO, "FST TearDown sent");
+ else
+ fst_printf_sframe(s, TRUE, MSG_ERROR,
+ "failed to send FST TearDown");
+
+ return res;
+}
+
+
+static void fst_session_handle_setup_request(struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len)
+{
+ struct fst_session *s;
+ const struct fst_setup_req *req =
+ (const struct fst_setup_req *) &mgmt->u.action.u.fst_action;
+ struct fst_iface *new_iface = NULL;
+ struct fst_group *g;
+ u8 new_iface_peer_addr[ETH_ALEN];
+ struct wpabuf *peer_mbies;
+
+ if (frame_len < sizeof(*req)) {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Request dropped: too short (%zu < %zu)",
+ frame_len, sizeof(*req));
+ return;
+ }
+
+ if (req->stie.new_band_id == req->stie.old_band_id) {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Request dropped: new and old band IDs are the same");
+ return;
+ }
+
+ g = fst_iface_get_group(iface);
+
+ if (frame_len > sizeof(*req)) {
+ fst_iface_update_mb_ie(iface, mgmt->sa, (const u8 *) (req + 1),
+ frame_len - sizeof(*req));
+ fst_printf_iface(iface, MSG_INFO,
+ "FST Request: MB IEs updated for " MACSTR,
+ MAC2STR(mgmt->sa));
+ }
+
+ peer_mbies = fst_iface_get_peer_mb_ie(iface, mgmt->sa);
+ if (peer_mbies) {
+ new_iface = fst_group_get_new_iface_by_stie_and_mbie(
+ g, wpabuf_head(peer_mbies), wpabuf_len(peer_mbies),
+ &req->stie, new_iface_peer_addr);
+ if (new_iface)
+ fst_printf_iface(iface, MSG_INFO,
+ "FST Request: new iface (%s:" MACSTR
+ ") found by MB IEs",
+ fst_iface_get_name(new_iface),
+ MAC2STR(new_iface_peer_addr));
+ }
+
+ if (!new_iface) {
+ new_iface = fst_group_find_new_iface_by_stie(
+ g, iface, mgmt->sa, &req->stie,
+ new_iface_peer_addr);
+ if (new_iface)
+ fst_printf_iface(iface, MSG_INFO,
+ "FST Request: new iface (%s:" MACSTR
+ ") found by others",
+ fst_iface_get_name(new_iface),
+ MAC2STR(new_iface_peer_addr));
+ }
+
+ if (!new_iface) {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Request dropped: new iface not found");
+ return;
+ }
+
+ s = fst_find_session_in_progress(mgmt->sa, g);
+ if (s) {
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_SETUP,
+ },
+ };
+
+ /*
+ * 10.32.2.2 Transitioning between states:
+ * Upon receipt of an FST Setup Request frame, the responder
+ * shall respond with an FST Setup Response frame unless it has
+ * a pending FST Setup Request frame addressed to the initiator
+ * and the responder has a numerically larger MAC address than
+ * the initiator’s MAC address, in which case, the responder
+ * shall delete the received FST Setup Request.
+ */
+ if (os_memcmp(mgmt->da, mgmt->sa, ETH_ALEN) > 0) {
+ fst_printf_session(s, MSG_WARNING,
+ "FST Request dropped due to MAC comparison (our MAC is "
+ MACSTR ")",
+ MAC2STR(mgmt->da));
+ return;
+ }
+
+ if (!fst_session_is_ready_pending(s)) {
+ fst_printf_session(s, MSG_WARNING,
+ "FST Request from " MACSTR
+ " dropped due to inappropriate state %s",
+ MAC2STR(mgmt->da),
+ fst_session_state_name(s->state));
+ return;
+ }
+
+
+ /*
+ * If FST Setup Request arrived with the same FSTS ID as one we
+ * initialized before, it means the other side either didn't
+ * receive our FST Request or skipped it for some reason (for
+ * example, due to numerical MAC comparison).
+ *
+ * In this case, there's no need to tear down the session.
+ * Moreover, as FSTS ID is the same, the other side will
+ * associate this tear down with the session it initiated that
+ * will break the sync.
+ */
+ if (le_to_host32(req->stie.fsts_id) != s->data.fsts_id)
+ fst_session_send_tear_down(s);
+ else
+ fst_printf_session(s, MSG_WARNING,
+ "Skipping TearDown as the FST request has the same FSTS ID as initiated");
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+ fst_session_stt_disarm(s);
+ fst_printf_session(s, MSG_WARNING, "reset due to FST request");
+ }
+
+ s = fst_session_create(g);
+ if (!s) {
+ fst_printf(MSG_WARNING,
+ "FST Request dropped: cannot create session for %s and %s",
+ fst_iface_get_name(iface),
+ fst_iface_get_name(new_iface));
+ return;
+ }
+
+ fst_session_set_iface(s, iface, TRUE);
+ fst_session_set_peer_addr(s, mgmt->sa, TRUE);
+ fst_session_set_iface(s, new_iface, FALSE);
+ fst_session_set_peer_addr(s, new_iface_peer_addr, FALSE);
+ fst_session_set_llt(s, FST_LLT_VAL_TO_MS(le_to_host32(req->llt)));
+ s->data.pending_setup_req_dlgt = req->dialog_token;
+ s->data.fsts_id = le_to_host32(req->stie.fsts_id);
+
+ fst_session_stt_arm(s);
+
+ fst_session_notify_ctrl(s, EVENT_FST_SETUP, NULL);
+
+ fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION, NULL);
+}
+
+
+static void fst_session_handle_setup_response(struct fst_session *s,
+ struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len)
+{
+ const struct fst_setup_res *res =
+ (const struct fst_setup_res *) &mgmt->u.action.u.fst_action;
+ enum hostapd_hw_mode hw_mode;
+ u8 channel;
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {0},
+ };
+
+ if (iface != s->data.old_iface) {
+ fst_printf_session(s, MSG_WARNING,
+ "FST Response dropped: %s is not the old iface",
+ fst_iface_get_name(iface));
+ return;
+ }
+
+ if (!fst_session_is_ready_pending(s)) {
+ fst_printf_session(s, MSG_WARNING,
+ "FST Response dropped due to wrong state: %s",
+ fst_session_state_name(s->state));
+ return;
+ }
+
+ if (res->dialog_token != s->data.pending_setup_req_dlgt) {
+ fst_printf_session(s, MSG_WARNING,
+ "FST Response dropped due to wrong dialog token (%u != %u)",
+ s->data.pending_setup_req_dlgt,
+ res->dialog_token);
+ return;
+ }
+
+ if (res->status_code == WLAN_STATUS_SUCCESS &&
+ le_to_host32(res->stie.fsts_id) != s->data.fsts_id) {
+ fst_printf_session(s, MSG_WARNING,
+ "FST Response dropped due to wrong FST Session ID (%u)",
+ le_to_host32(res->stie.fsts_id));
+ return;
+ }
+
+ fst_session_stt_disarm(s);
+
+ if (res->status_code != WLAN_STATUS_SUCCESS) {
+ /*
+ * 10.32.2.2 Transitioning between states
+ * The initiator shall set the STT to the value of the
+ * FSTSessionTimeOut field at ... and at each ACK frame sent in
+ * response to a received FST Setup Response with the Status
+ * Code field equal to PENDING_ADMITTING_FST_SESSION or
+ * PENDING_GAP_IN_BA_WINDOW.
+ */
+ evext.to_initial.reason = REASON_REJECT;
+ evext.to_initial.reject_code = res->status_code;
+ evext.to_initial.initiator = FST_INITIATOR_REMOTE;
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+ fst_printf_session(s, MSG_WARNING,
+ "FST Setup rejected by remote side with status %u",
+ res->status_code);
+ return;
+ }
+
+ fst_iface_get_channel_info(s->data.new_iface, &hw_mode, &channel);
+
+ if (fst_hw_mode_to_band(hw_mode) != res->stie.new_band_id) {
+ evext.to_initial.reason = REASON_ERROR_PARAMS;
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+ fst_printf_session(s, MSG_WARNING,
+ "invalid FST Setup parameters");
+ fst_session_tear_down_setup(s);
+ return;
+ }
+
+ fst_printf_session(s, MSG_INFO,
+ "%s: FST Setup established for %s (llt=%u)",
+ fst_iface_get_name(s->data.old_iface),
+ fst_iface_get_name(s->data.new_iface),
+ s->data.llt_ms);
+
+ fst_session_notify_ctrl(s, EVENT_FST_ESTABLISHED, NULL);
+
+ if (s->data.llt_ms == FST_LLT_SWITCH_IMMEDIATELY)
+ fst_session_initiate_switch(s);
+}
+
+
+static void fst_session_handle_tear_down(struct fst_session *s,
+ struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len)
+{
+ const struct fst_tear_down *td =
+ (const struct fst_tear_down *) &mgmt->u.action.u.fst_action;
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_TEARDOWN,
+ .initiator = FST_INITIATOR_REMOTE,
+ },
+ };
+
+ if (!fst_session_is_in_progress(s)) {
+ fst_printf_session(s, MSG_WARNING, "no FST Setup to tear down");
+ return;
+ }
+
+ if (le_to_host32(td->fsts_id) != s->data.fsts_id) {
+ fst_printf_siface(s, iface, MSG_WARNING,
+ "tear down for wrong FST Setup ID (%u)",
+ le_to_host32(td->fsts_id));
+ return;
+ }
+
+ fst_session_stt_disarm(s);
+
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+}
+
+
+static void fst_session_handle_ack_request(struct fst_session *s,
+ struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len)
+{
+ const struct fst_ack_req *req =
+ (const struct fst_ack_req *) &mgmt->u.action.u.fst_action;
+ struct fst_ack_res res;
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_SWITCH,
+ .initiator = FST_INITIATOR_REMOTE,
+ },
+ };
+
+ if (!fst_session_is_ready(s) && !fst_session_is_switch_requested(s)) {
+ fst_printf_siface(s, iface, MSG_ERROR,
+ "cannot initiate switch due to wrong session state (%s)",
+ fst_session_state_name(s->state));
+ return;
+ }
+
+ WPA_ASSERT(s->data.new_iface != NULL);
+
+ if (iface != s->data.new_iface) {
+ fst_printf_siface(s, iface, MSG_ERROR,
+ "Ack received on wrong interface");
+ return;
+ }
+
+ if (le_to_host32(req->fsts_id) != s->data.fsts_id) {
+ fst_printf_siface(s, iface, MSG_WARNING,
+ "Ack for wrong FST Setup ID (%u)",
+ le_to_host32(req->fsts_id));
+ return;
+ }
+
+ os_memset(&res, 0, sizeof(res));
+
+ res.action = FST_ACTION_ACK_RESPONSE;
+ res.dialog_token = req->dialog_token;
+ res.fsts_id = req->fsts_id;
+
+ if (!fst_session_send_action(s, FALSE, &res, sizeof(res), NULL)) {
+ fst_printf_sframe(s, FALSE, MSG_INFO, "FST Ack Response sent");
+ fst_session_stt_disarm(s);
+ fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE,
+ NULL);
+ fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED,
+ NULL);
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+ }
+}
+
+
+static void
+fst_session_handle_ack_response(struct fst_session *s,
+ struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len)
+{
+ const struct fst_ack_res *res =
+ (const struct fst_ack_res *) &mgmt->u.action.u.fst_action;
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_SWITCH,
+ .initiator = FST_INITIATOR_LOCAL,
+ },
+ };
+
+ if (!fst_session_is_switch_requested(s)) {
+ fst_printf_siface(s, iface, MSG_ERROR,
+ "Ack Response in inappropriate session state (%s)",
+ fst_session_state_name(s->state));
+ return;
+ }
+
+ WPA_ASSERT(s->data.new_iface != NULL);
+
+ if (iface != s->data.new_iface) {
+ fst_printf_siface(s, iface, MSG_ERROR,
+ "Ack response received on wrong interface");
+ return;
+ }
+
+ if (le_to_host32(res->fsts_id) != s->data.fsts_id) {
+ fst_printf_siface(s, iface, MSG_ERROR,
+ "Ack response for wrong FST Setup ID (%u)",
+ le_to_host32(res->fsts_id));
+ return;
+ }
+
+ fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_CONFIRMED, NULL);
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+
+ fst_session_stt_disarm(s);
+}
+
+
+struct fst_session * fst_session_create(struct fst_group *g)
+{
+ struct fst_session *s;
+ u32 id;
+
+ WPA_ASSERT(!is_zero_ether_addr(own_addr));
+
+ id = fst_find_free_session_id();
+ if (id == FST_INVALID_SESSION_ID) {
+ fst_printf(MSG_ERROR, "Cannot assign new session ID");
+ return NULL;
+ }
+
+ s = os_zalloc(sizeof(*s));
+ if (!s) {
+ fst_printf(MSG_ERROR, "Cannot allocate new session object");
+ return NULL;
+ }
+
+ s->id = id;
+ s->group = g;
+ s->state = FST_SESSION_STATE_INITIAL;
+
+ s->data.llt_ms = FST_LLT_MS_DEFAULT;
+
+ fst_printf(MSG_INFO, "Session %u created", s->id);
+
+ dl_list_add_tail(&global_sessions_list, &s->global_sessions_lentry);
+
+ foreach_fst_ctrl_call(on_session_added, s);
+
+ return s;
+}
+
+
+void fst_session_set_iface(struct fst_session *s, struct fst_iface *iface,
+ Boolean is_old)
+{
+ if (is_old)
+ s->data.old_iface = iface;
+ else
+ s->data.new_iface = iface;
+
+}
+
+
+void fst_session_set_llt(struct fst_session *s, u32 llt)
+{
+ s->data.llt_ms = llt;
+}
+
+
+void fst_session_set_peer_addr(struct fst_session *s, const u8 *addr,
+ Boolean is_old)
+{
+ u8 *a = is_old ? s->data.old_peer_addr : s->data.new_peer_addr;
+
+ os_memcpy(a, addr, ETH_ALEN);
+}
+
+
+int fst_session_initiate_setup(struct fst_session *s)
+{
+ struct fst_setup_req req;
+ int res;
+ u32 fsts_id;
+ u8 dialog_token;
+ struct fst_session *_s;
+
+ if (fst_session_is_in_progress(s)) {
+ fst_printf_session(s, MSG_ERROR, "Session in progress");
+ return -EINVAL;
+ }
+
+ if (is_zero_ether_addr(s->data.old_peer_addr)) {
+ fst_printf_session(s, MSG_ERROR, "No old peer MAC address");
+ return -EINVAL;
+ }
+
+ if (is_zero_ether_addr(s->data.new_peer_addr)) {
+ fst_printf_session(s, MSG_ERROR, "No new peer MAC address");
+ return -EINVAL;
+ }
+
+ if (!s->data.old_iface) {
+ fst_printf_session(s, MSG_ERROR, "No old interface defined");
+ return -EINVAL;
+ }
+
+ if (!s->data.new_iface) {
+ fst_printf_session(s, MSG_ERROR, "No new interface defined");
+ return -EINVAL;
+ }
+
+ if (s->data.new_iface == s->data.old_iface) {
+ fst_printf_session(s, MSG_ERROR,
+ "Same interface set as old and new");
+ return -EINVAL;
+ }
+
+ if (!fst_iface_is_connected(s->data.old_iface, s->data.old_peer_addr)) {
+ fst_printf_session(s, MSG_ERROR,
+ "The preset old peer address is not connected");
+ return -EINVAL;
+ }
+
+ if (!fst_iface_is_connected(s->data.new_iface, s->data.new_peer_addr)) {
+ fst_printf_session(s, MSG_ERROR,
+ "The preset new peer address is not connected");
+ return -EINVAL;
+ }
+
+ _s = fst_find_session_in_progress(s->data.old_peer_addr, s->group);
+ if (_s) {
+ fst_printf_session(s, MSG_ERROR,
+ "There is another session in progress (old): %u",
+ _s->id);
+ return -EINVAL;
+ }
+
+ _s = fst_find_session_in_progress(s->data.new_peer_addr, s->group);
+ if (_s) {
+ fst_printf_session(s, MSG_ERROR,
+ "There is another session in progress (new): %u",
+ _s->id);
+ return -EINVAL;
+ }
+
+ dialog_token = fst_group_assign_dialog_token(s->group);
+ fsts_id = fst_group_assign_fsts_id(s->group);
+
+ os_memset(&req, 0, sizeof(req));
+
+ fst_printf_siface(s, s->data.old_iface, MSG_INFO,
+ "initiating FST setup for %s (llt=%u ms)",
+ fst_iface_get_name(s->data.new_iface), s->data.llt_ms);
+
+ req.action = FST_ACTION_SETUP_REQUEST;
+ req.dialog_token = dialog_token;
+ req.llt = host_to_le32(FST_LLT_MS_TO_VAL(s->data.llt_ms));
+ /* 8.4.2.147 Session Transition element */
+ req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+ req.stie.length = sizeof(req.stie);
+ req.stie.fsts_id = host_to_le32(fsts_id);
+ req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+ req.stie.new_band_id = fst_iface_get_band_id(s->data.new_iface);
+ req.stie.new_band_op = 1;
+ req.stie.new_band_setup = 0;
+
+ req.stie.old_band_id = fst_iface_get_band_id(s->data.old_iface);
+ req.stie.old_band_op = 1;
+ req.stie.old_band_setup = 0;
+
+ res = fst_session_send_action(s, TRUE, &req, sizeof(req),
+ fst_iface_get_mbie(s->data.old_iface));
+ if (!res) {
+ s->data.fsts_id = fsts_id;
+ s->data.pending_setup_req_dlgt = dialog_token;
+ fst_printf_sframe(s, TRUE, MSG_INFO, "FST Setup Request sent");
+ fst_session_set_state(s, FST_SESSION_STATE_SETUP_COMPLETION,
+ NULL);
+
+ fst_session_stt_arm(s);
+ }
+
+ return res;
+}
+
+
+int fst_session_respond(struct fst_session *s, u8 status_code)
+{
+ struct fst_setup_res res;
+ enum hostapd_hw_mode hw_mode;
+ u8 channel;
+
+ if (!fst_session_is_ready_pending(s)) {
+ fst_printf_session(s, MSG_ERROR, "incorrect state: %s",
+ fst_session_state_name(s->state));
+ return -EINVAL;
+ }
+
+ if (is_zero_ether_addr(s->data.old_peer_addr)) {
+ fst_printf_session(s, MSG_ERROR, "No peer MAC address");
+ return -EINVAL;
+ }
+
+ if (!s->data.old_iface) {
+ fst_printf_session(s, MSG_ERROR, "No old interface defined");
+ return -EINVAL;
+ }
+
+ if (!s->data.new_iface) {
+ fst_printf_session(s, MSG_ERROR, "No new interface defined");
+ return -EINVAL;
+ }
+
+ if (s->data.new_iface == s->data.old_iface) {
+ fst_printf_session(s, MSG_ERROR,
+ "Same interface set as old and new");
+ return -EINVAL;
+ }
+
+ if (!fst_iface_is_connected(s->data.old_iface, s->data.old_peer_addr)) {
+ fst_printf_session(s, MSG_ERROR,
+ "The preset peer address is not in the peer list");
+ return -EINVAL;
+ }
+
+ fst_session_stt_disarm(s);
+
+ os_memset(&res, 0, sizeof(res));
+
+ res.action = FST_ACTION_SETUP_RESPONSE;
+ res.dialog_token = s->data.pending_setup_req_dlgt;
+ res.status_code = status_code;
+
+ if (status_code == WLAN_STATUS_SUCCESS) {
+ res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+ res.stie.length = sizeof(res.stie);
+ res.stie.fsts_id = s->data.fsts_id;
+ res.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+ fst_iface_get_channel_info(s->data.new_iface, &hw_mode,
+ &channel);
+ res.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
+ res.stie.new_band_op = 1;
+ res.stie.new_band_setup = 0;
+
+ fst_iface_get_channel_info(s->data.old_iface, &hw_mode,
+ &channel);
+ res.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
+ res.stie.old_band_op = 1;
+ res.stie.old_band_setup = 0;
+
+ fst_printf_session(s, MSG_INFO,
+ "%s: FST Setup Request accepted for %s (llt=%u)",
+ fst_iface_get_name(s->data.old_iface),
+ fst_iface_get_name(s->data.new_iface),
+ s->data.llt_ms);
+ } else {
+ fst_printf_session(s, MSG_WARNING,
+ "%s: FST Setup Request rejected with code %d",
+ fst_iface_get_name(s->data.old_iface),
+ status_code);
+ }
+
+ if (fst_session_send_action(s, TRUE, &res, sizeof(res),
+ fst_iface_get_mbie(s->data.old_iface))) {
+ fst_printf_sframe(s, TRUE, MSG_ERROR,
+ "cannot send FST Setup Response with code %d",
+ status_code);
+ return -EINVAL;
+ }
+
+ fst_printf_sframe(s, TRUE, MSG_INFO, "FST Setup Response sent");
+
+ if (status_code != WLAN_STATUS_SUCCESS) {
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_REJECT,
+ .reject_code = status_code,
+ .initiator = FST_INITIATOR_LOCAL,
+ },
+ };
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+ }
+
+ return 0;
+}
+
+
+int fst_session_initiate_switch(struct fst_session *s)
+{
+ struct fst_ack_req req;
+ int res;
+ u8 dialog_token;
+
+ if (!fst_session_is_ready(s)) {
+ fst_printf_session(s, MSG_ERROR,
+ "cannot initiate switch due to wrong setup state (%d)",
+ s->state);
+ return -1;
+ }
+
+ dialog_token = fst_group_assign_dialog_token(s->group);
+
+ WPA_ASSERT(s->data.new_iface != NULL);
+ WPA_ASSERT(s->data.old_iface != NULL);
+
+ fst_printf_session(s, MSG_INFO, "initiating FST switch: %s => %s",
+ fst_iface_get_name(s->data.old_iface),
+ fst_iface_get_name(s->data.new_iface));
+
+ os_memset(&req, 0, sizeof(req));
+
+ req.action = FST_ACTION_ACK_REQUEST;
+ req.dialog_token = dialog_token;
+ req.fsts_id = host_to_le32(s->data.fsts_id);
+
+ res = fst_session_send_action(s, FALSE, &req, sizeof(req), NULL);
+ if (!res) {
+ fst_printf_sframe(s, FALSE, MSG_INFO, "FST Ack Request sent");
+ fst_session_set_state(s, FST_SESSION_STATE_TRANSITION_DONE,
+ NULL);
+ fst_session_stt_arm(s);
+ } else {
+ fst_printf_sframe(s, FALSE, MSG_ERROR,
+ "Cannot send FST Ack Request");
+ }
+
+ return res;
+}
+
+
+void fst_session_handle_action(struct fst_session *s,
+ struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len)
+{
+ switch (mgmt->u.action.u.fst_action.action) {
+ case FST_ACTION_SETUP_REQUEST:
+ WPA_ASSERT(0);
+ break;
+ case FST_ACTION_SETUP_RESPONSE:
+ fst_session_handle_setup_response(s, iface, mgmt, frame_len);
+ break;
+ case FST_ACTION_TEAR_DOWN:
+ fst_session_handle_tear_down(s, iface, mgmt, frame_len);
+ break;
+ case FST_ACTION_ACK_REQUEST:
+ fst_session_handle_ack_request(s, iface, mgmt, frame_len);
+ break;
+ case FST_ACTION_ACK_RESPONSE:
+ fst_session_handle_ack_response(s, iface, mgmt, frame_len);
+ break;
+ case FST_ACTION_ON_CHANNEL_TUNNEL:
+ default:
+ fst_printf_sframe(s, FALSE, MSG_ERROR,
+ "Unsupported FST Action frame");
+ break;
+ }
+}
+
+
+int fst_session_tear_down_setup(struct fst_session *s)
+{
+ int res;
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_TEARDOWN,
+ .initiator = FST_INITIATOR_LOCAL,
+ },
+ };
+
+ res = fst_session_send_tear_down(s);
+
+ fst_session_set_state(s, FST_SESSION_STATE_INITIAL, &evext);
+
+ return res;
+}
+
+
+void fst_session_reset(struct fst_session *s)
+{
+ fst_session_reset_ex(s, REASON_RESET);
+}
+
+
+void fst_session_delete(struct fst_session *s)
+{
+ fst_printf(MSG_INFO, "Session %u deleted", s->id);
+ dl_list_del(&s->global_sessions_lentry);
+ foreach_fst_ctrl_call(on_session_removed, s);
+ os_free(s);
+}
+
+
+struct fst_group * fst_session_get_group(struct fst_session *s)
+{
+ return s->group;
+}
+
+
+struct fst_iface * fst_session_get_iface(struct fst_session *s, Boolean is_old)
+{
+ return is_old ? s->data.old_iface : s->data.new_iface;
+}
+
+
+u32 fst_session_get_id(struct fst_session *s)
+{
+ return s->id;
+}
+
+
+const u8 * fst_session_get_peer_addr(struct fst_session *s, Boolean is_old)
+{
+ return is_old ? s->data.old_peer_addr : s->data.new_peer_addr;
+}
+
+
+u32 fst_session_get_llt(struct fst_session *s)
+{
+ return s->data.llt_ms;
+}
+
+
+enum fst_session_state fst_session_get_state(struct fst_session *s)
+{
+ return s->state;
+}
+
+
+struct fst_session * fst_session_get_by_id(u32 id)
+{
+ struct fst_session *s;
+
+ foreach_fst_session(s) {
+ if (id == s->id)
+ return s;
+ }
+
+ return NULL;
+}
+
+
+void fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx)
+{
+ struct fst_session *s;
+
+ foreach_fst_session(s) {
+ if (!g || s->group == g)
+ clb(s->group, s, ctx);
+ }
+}
+
+
+void fst_session_on_action_rx(struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t len)
+{
+ struct fst_session *s;
+
+ if (mgmt->u.action.category != WLAN_ACTION_FST) {
+ fst_printf_iface(iface, MSG_ERROR,
+ "action frame of wrong category (%u) received!",
+ mgmt->u.action.category);
+ return;
+ }
+
+ if (mgmt->u.action.u.fst_action.action <= FST_ACTION_MAX_SUPPORTED) {
+ fst_printf_iface(iface, MSG_DEBUG,
+ "FST Action '%s' received!",
+ fst_action_names[mgmt->u.action.u.fst_action.action]);
+ } else {
+ fst_printf_iface(iface, MSG_WARNING,
+ "unknown FST Action (%u) received!",
+ mgmt->u.action.u.fst_action.action);
+ return;
+ }
+
+ if (mgmt->u.action.u.fst_action.action == FST_ACTION_SETUP_REQUEST) {
+ fst_session_handle_setup_request(iface, mgmt, len);
+ return;
+ }
+
+ s = fst_find_session_in_progress(mgmt->sa, fst_iface_get_group(iface));
+ if (s) {
+ fst_session_handle_action(s, iface, mgmt, len);
+ } else {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Action '%s' dropped: no session in progress found",
+ fst_action_names[mgmt->u.action.u.fst_action.action]);
+ }
+}
+
+
+int fst_session_set_str_ifname(struct fst_session *s, const char *ifname,
+ Boolean is_old)
+{
+ struct fst_group *g = fst_session_get_group(s);
+ struct fst_iface *i;
+
+ i = fst_group_get_iface_by_name(g, ifname);
+ if (!i) {
+ fst_printf_session(s, MSG_WARNING,
+ "Cannot set iface %s: no such iface within group '%s'",
+ ifname, fst_group_get_id(g));
+ return -1;
+ }
+
+ fst_session_set_iface(s, i, is_old);
+
+ return 0;
+}
+
+
+int fst_session_set_str_peer_addr(struct fst_session *s, const char *mac,
+ Boolean is_old)
+{
+ u8 peer_addr[ETH_ALEN];
+ int res = fst_read_peer_addr(mac, peer_addr);
+
+ if (res)
+ return res;
+
+ fst_session_set_peer_addr(s, peer_addr, is_old);
+
+ return 0;
+}
+
+
+int fst_session_set_str_llt(struct fst_session *s, const char *llt_str)
+{
+ char *endp;
+ long int llt = strtol(llt_str, &endp, 0);
+
+ if (*endp || llt < 0 || (unsigned long int) llt > FST_MAX_LLT_MS) {
+ fst_printf_session(s, MSG_WARNING,
+ "Cannot set llt %s: Invalid llt value (1..%u expected)",
+ llt_str, FST_MAX_LLT_MS);
+ return -1;
+ }
+ fst_session_set_llt(s, (u32) llt);
+
+ return 0;
+}
+
+
+void fst_session_global_on_iface_detached(struct fst_iface *iface)
+{
+ struct fst_session *s;
+
+ foreach_fst_session(s) {
+ if (fst_session_is_in_progress(s) &&
+ (s->data.new_iface == iface ||
+ s->data.old_iface == iface))
+ fst_session_reset_ex(s, REASON_DETACH_IFACE);
+ }
+}
+
+
+struct fst_session * fst_session_global_get_first_by_group(struct fst_group *g)
+{
+ struct fst_session *s;
+
+ foreach_fst_session(s) {
+ if (s->group == g)
+ return s;
+ }
+
+ return NULL;
+}
+
diff --git a/src/fst/fst_session.h b/src/fst/fst_session.h
new file mode 100644
index 0000000..4370d2c
--- /dev/null
+++ b/src/fst/fst_session.h
@@ -0,0 +1,65 @@
+/*
+ * FST module - FST Session related definitions
+ * Copyright (c) 2014, Qualcomm Atheros, Inc.
+ *
+ * This software may be distributed under the terms of the BSD license.
+ * See README for more details.
+ */
+
+#ifndef FST_SESSION_H
+#define FST_SESSION_H
+
+#define FST_DEFAULT_SESSION_TIMEOUT_TU 255 /* u8 */
+
+struct fst_iface;
+struct fst_group;
+struct fst_session;
+enum fst_session_state;
+
+int fst_session_global_init(void);
+void fst_session_global_deinit(void);
+void fst_session_global_on_iface_detached(struct fst_iface *iface);
+struct fst_session *
+fst_session_global_get_first_by_group(struct fst_group *g);
+
+struct fst_session * fst_session_create(struct fst_group *g);
+void fst_session_set_iface(struct fst_session *s, struct fst_iface *iface,
+ Boolean is_old);
+void fst_session_set_llt(struct fst_session *s, u32 llt);
+void fst_session_set_peer_addr(struct fst_session *s, const u8 *addr,
+ Boolean is_old);
+int fst_session_initiate_setup(struct fst_session *s);
+int fst_session_respond(struct fst_session *s, u8 status_code);
+int fst_session_initiate_switch(struct fst_session *s);
+void fst_session_handle_action(struct fst_session *s, struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt,
+ size_t frame_len);
+int fst_session_tear_down_setup(struct fst_session *s);
+void fst_session_reset(struct fst_session *s);
+void fst_session_delete(struct fst_session *s);
+
+struct fst_group * fst_session_get_group(struct fst_session *s);
+struct fst_iface * fst_session_get_iface(struct fst_session *s, Boolean is_old);
+const u8 * fst_session_get_peer_addr(struct fst_session *s, Boolean is_old);
+u32 fst_session_get_id(struct fst_session *s);
+u32 fst_session_get_llt(struct fst_session *s);
+enum fst_session_state fst_session_get_state(struct fst_session *s);
+
+struct fst_session *fst_session_get_by_id(u32 id);
+
+typedef void (*fst_session_enum_clb)(struct fst_group *g, struct fst_session *s,
+ void *ctx);
+
+void fst_session_enum(struct fst_group *g, fst_session_enum_clb clb, void *ctx);
+
+void fst_session_on_action_rx(struct fst_iface *iface,
+ const struct ieee80211_mgmt *mgmt, size_t len);
+
+
+int fst_session_set_str_ifname(struct fst_session *s, const char *ifname,
+ Boolean is_old);
+int fst_session_set_str_peer_addr(struct fst_session *s, const char *mac,
+ Boolean is_old);
+int fst_session_set_str_llt(struct fst_session *s, const char *llt_str);
+
+#endif /* FST_SESSION_H */