Cumulative patch from commit f10ff62e4eda7917a8e28fe492fd98723a5e97c4
f10ff62 Describe preferred mechanism for submitting contributions
fcdb359 Use "STATUS-NO_EVENTS" instead of "STATUS" in get_wpa_status function
73ed03f wpa_supplicant: Add GTK RSC relaxation workaround
ea6030c Restore previous wpa_state in scan-only result handler
1e74ae4 WNM: Clear BSS TM data if already associated with preferred candidate
d129b02 EAP-pwd: Add support for Brainpool Elliptic Curves
a34eace dbus: Remove unused dict helper functions
cdcb2d0 wpa_cli: Add support for vendor_elem_* commands
17b7032 EAP peer: Clear ignore flag in INITIALIZE state
1f1e619 Add test programs for checking libwpa_client linking
736b7cb wpa_supplicant/Makefile: Fix libwpa_client build
2e38079 TLS: Fix memory leak with multiple TLS server instances
7b0f550 eap_sim_db: Implement eap_sim_db_expire_pending()
45c3e72 Add frequency to operating class determination for 5 GHz 100..140
e50c50d dbus: Expose interface globals via D-Bus properties
1aa0fb7 dbus: Pass property description to getters/setters
c93b7e1 RSN: Check result of EAPOL-Key frame send request
95be79f Allow -1 as value to disable frag_threshold
bc50bb0 Extend the range of values for the RTS threshold
053693d hostapd: Add feature to start all interfaces at the same time in sync
9578413 Reserve QCA vendor specific nl80211 commands 110..114
5d4c508 Assign QCA commands and attributes for Tx power scaling and OTA testing
5d1d69a P2P: Filter control chars in group client device name similarly to peer
f67d1a0 TDLS: Do not send error case of TPK M3 if TX fails
1248e58 wpa_supplicant: Reopen debug log file upon receipt of SIGHUP signal
d8fd633 Do not write ERROR level log entries if debug file is not used
67deaa5 l2_packet: Add build option to disable Linux packet socket workaround
fa46426 RSN: Do not try to connect if PMF disabled and AP requires it
8acbe7f WNM: Verify WNM Sleep Mode element length
dacd789 WNM: Mark set TFS buffer const
...
f24b979 OpenSSL: Merge error returns
84d6a17 TLS: Remove unused tls_capabilities()
7867227 ms_funcs: Merge similar return cases
3596361 hw_features: Merge similar return case in check_40mhz_2g4()
aac1efe Reject the initial 4-way handshake if initial GTK setup fails
2da5256 Add backtrace-based error path testing mechanism
55413ce P2P: Do not allow 40 MHz co-ex PRI/SEC switch to force MCC
Next patches were skipped due to explicit cherry-pick:
bddc51e RSN: Stop connection attempt on apparent PMK mismatch
3fdaaa8 Throttle control interface event message bursts
a530fe7 Add wpa_supplicant EVENT_TEST control interface command
ee1e3f5 hostapd: Global control interface notifications
2e95cfc Add debug prints for wpa_supplicant ctrl_iface socket send operations
ce7d0eb Update AP WPA/RSN IE on all associations if driver can select BSS
844dfeb QCA vendor command support to set band to driver
Change-Id: I909996c5afcd3b5d123ea1e23c0e1212021f7625
Signed-off-by: Dmitry Shmidt <dimitrysh@google.com>
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..2880870
--- /dev/null
+++ b/src/fst/fst.c
@@ -0,0 +1,225 @@
+/*
+ * 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);
+}
+
+
+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);
+
+ 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);
+
+ 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);
+ 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;
+ struct fst_ctrl_handle *h;
+
+ fst_session_global_deinit();
+ while ((group = fst_first_group()) != NULL)
+ fst_group_delete(group);
+ while ((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);
+ else
+ wpa_printf(MSG_DEBUG,
+ "FST: Ignore FST Action frame - no FST connection with "
+ MACSTR, MAC2STR(mgmt->sa));
+}
+
+
+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));
+#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));
+#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);
+}
+
+
+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;
+ }
+}
diff --git a/src/fst/fst.h b/src/fst/fst.h
new file mode 100644
index 0000000..0c0e435
--- /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 (owned by FST module)
+ */
+ void (*set_ies)(void *ctx, const 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
+ */
+ const 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..6735389
--- /dev/null
+++ b/src/fst/fst_ctrl_defs.h
@@ -0,0 +1,109 @@
+/*
+ * 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"
+
+#ifdef CONFIG_FST_TEST
+#define FST_CTR_PVAL_BAD_NEW_BAND "bad_new_band"
+
+#define FST_CMD_TEST_REQUEST "test_request"
+#define FST_CTR_IS_SUPPORTED "is_supported"
+#define FST_CTR_SEND_SETUP_REQUEST "send_setup_request"
+#define FST_CTR_SEND_SETUP_RESPONSE "send_setup_response"
+#define FST_CTR_SEND_ACK_REQUEST "send_ack_request"
+#define FST_CTR_SEND_ACK_RESPONSE "send_ack_response"
+#define FST_CTR_SEND_TEAR_DOWN "send_tear_down"
+#define FST_CTR_GET_FSTS_ID "get_fsts_id"
+#define FST_CTR_GET_LOCAL_MBIES "get_local_mbies"
+#endif /* CONFIG_FST_TEST */
+
+/* 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..d090718
--- /dev/null
+++ b/src/fst/fst_ctrl_iface.c
@@ -0,0 +1,948 @@
+/*
+ * 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 */
+static Boolean format_session_state_extra(const union fst_event_extra *extra,
+ char *buffer, size_t 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;
+
+ 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(struct fst_iface *f, u32 session_id,
+ enum fst_event_type event_type,
+ const union fst_event_extra *extra)
+{
+ struct fst_group *g;
+ 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.
+ */
+
+ if (!f) {
+ foreach_fst_group(g) {
+ f = fst_group_first_iface(g);
+ if (f)
+ break;
+ }
+ 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");
+}
+
+
+#ifdef CONFIG_FST_TEST
+/* fst test_request */
+static int test_request(const char *request, char *buf, size_t buflen)
+{
+ const char *p = request;
+ int ret;
+
+ if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_REQUEST,
+ os_strlen(FST_CTR_SEND_SETUP_REQUEST))) {
+ ret = fst_test_req_send_fst_request(
+ p + os_strlen(FST_CTR_SEND_SETUP_REQUEST));
+ } else if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_RESPONSE,
+ os_strlen(FST_CTR_SEND_SETUP_RESPONSE))) {
+ ret = fst_test_req_send_fst_response(
+ p + os_strlen(FST_CTR_SEND_SETUP_RESPONSE));
+ } else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_REQUEST,
+ os_strlen(FST_CTR_SEND_ACK_REQUEST))) {
+ ret = fst_test_req_send_ack_request(
+ p + os_strlen(FST_CTR_SEND_ACK_REQUEST));
+ } else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_RESPONSE,
+ os_strlen(FST_CTR_SEND_ACK_RESPONSE))) {
+ ret = fst_test_req_send_ack_response(
+ p + os_strlen(FST_CTR_SEND_ACK_RESPONSE));
+ } else if (!os_strncasecmp(p, FST_CTR_SEND_TEAR_DOWN,
+ os_strlen(FST_CTR_SEND_TEAR_DOWN))) {
+ ret = fst_test_req_send_tear_down(
+ p + os_strlen(FST_CTR_SEND_TEAR_DOWN));
+ } else if (!os_strncasecmp(p, FST_CTR_GET_FSTS_ID,
+ os_strlen(FST_CTR_GET_FSTS_ID))) {
+ u32 fsts_id = fst_test_req_get_fsts_id(
+ p + os_strlen(FST_CTR_GET_FSTS_ID));
+ if (fsts_id != FST_FSTS_ID_NOT_FOUND)
+ return os_snprintf(buf, buflen, "%u\n", fsts_id);
+ return os_snprintf(buf, buflen, "FAIL\n");
+ } else if (!os_strncasecmp(p, FST_CTR_GET_LOCAL_MBIES,
+ os_strlen(FST_CTR_GET_LOCAL_MBIES))) {
+ return fst_test_req_get_local_mbies(
+ p + os_strlen(FST_CTR_GET_LOCAL_MBIES), buf, buflen);
+ } else if (!os_strncasecmp(p, FST_CTR_IS_SUPPORTED,
+ os_strlen(FST_CTR_IS_SUPPORTED))) {
+ ret = 0;
+ } 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");
+}
+#endif /* CONFIG_FST_TEST */
+
+
+/* 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;
+ const struct wpabuf *mbies;
+
+ 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)
+ break;
+ }
+ if (!iface)
+ goto problem;
+
+ mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
+ if (!mbies)
+ goto problem;
+
+ return wpa_snprintf_hex(buf, buflen, wpabuf_head(mbies),
+ wpabuf_len(mbies));
+
+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;
+}
+
+
+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)
+{
+ const 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 += wpa_snprintf_hex(buf + ret, buflen - ret,
+ wpabuf_head(wpabuf),
+ wpabuf_len(wpabuf));
+ 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(i, 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(i, 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},
+#ifdef CONFIG_FST_TEST
+ { FST_CMD_TEST_REQUEST, 1, test_request },
+#endif /* CONFIG_FST_TEST */
+ { 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;
+
+ *endp = (char *) params;
+ while (isspace(**endp))
+ (*endp)++;
+ if (!**endp || buflen <= 1)
+ 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;
+
+ return fst_read_next_text_param(cmd, ifname, ifname_size, &endp);
+}
+
+
+int fst_iface_detach(const char *ifname)
+{
+ struct fst_group *g;
+
+ foreach_fst_group(g) {
+ struct fst_iface *f;
+
+ 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..4d0cd9f
--- /dev/null
+++ b/src/fst/fst_ctrl_iface.h
@@ -0,0 +1,45 @@
+/*
+ * 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 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..f6c7be9
--- /dev/null
+++ b/src/fst/fst_group.c
@@ -0,0 +1,462 @@
+/*
+ * 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 >= 2) {
+ const struct multi_band_ie *mbie =
+ (const struct multi_band_ie *) p;
+ WPA_ASSERT(mbie->eid == WLAN_EID_MULTI_BAND);
+ WPA_ASSERT(2 + 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 += 2 + mbie->len;
+ s -= 2 + 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 - 2;
+#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 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 &&
+ (size_t) 2 + mbie->len >= sizeof(*mbie) + ETH_ALEN)
+ peer_addr = (const u8 *) &mbie[1];
+ break;
+ default:
+ break;
+ }
+
+ return peer_addr;
+}
+
+
+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 >= 2) {
+ const struct multi_band_ie *mbie =
+ (const struct multi_band_ie *) mb_ies_buff;
+
+ if (mbie->eid != WLAN_EID_MULTI_BAND ||
+ (size_t) 2 + 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 += 2 + mbie->len;
+ mb_ies_size -= 2 + 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)) {
+ const struct wpabuf *mbies;
+ u8 other_iface_peer_addr[ETH_ALEN];
+ struct fst_iface *other_new_iface;
+
+ mbies = fst_iface_get_peer_mb_ie(other, addr);
+ if (!mbies)
+ continue;
+
+ 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) != 0) {
+ 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)
+{
+ struct fst_iface *i;
+
+ foreach_fst_group_iface(g, i) {
+ 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);
+ }
+}
diff --git a/src/fst/fst_group.h b/src/fst/fst_group.h
new file mode 100644
index 0000000..3a87c0b
--- /dev/null
+++ b/src/fst/fst_group.h
@@ -0,0 +1,75 @@
+/*
+ * 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;
+ u8 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);
+
+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)
+{
+ 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)
+{
+ 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..5a92d2c
--- /dev/null
+++ b/src/fst/fst_iface.c
@@ -0,0 +1,79 @@
+/*
+ * 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)
+{
+ fst_iface_set_ies(i, NULL);
+ 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..4670d89
--- /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,
+ const 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 const 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..9fe32b8
--- /dev/null
+++ b/src/fst/fst_internal.h
@@ -0,0 +1,49 @@
+/*
+ * 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__)
+
+enum mb_band_id fst_hw_mode_to_band(enum hostapd_hw_mode mode);
+
+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..55fa694
--- /dev/null
+++ b/src/fst/fst_session.c
@@ -0,0 +1,1620 @@
+/*
+ * 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"
+#ifdef CONFIG_FST_TEST
+#include "fst/fst_ctrl_defs.h"
+#endif /* CONFIG_FST_TEST */
+
+#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;
+ struct fst_iface *new_iface = NULL;
+ struct fst_group *g;
+ u8 new_iface_peer_addr[ETH_ALEN];
+ const struct wpabuf *peer_mbies;
+ size_t plen;
+
+ if (frame_len < IEEE80211_HDRLEN + 1 + sizeof(*req)) {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Request dropped: too short (%zu < %zu)",
+ frame_len,
+ IEEE80211_HDRLEN + 1 + sizeof(*req));
+ return;
+ }
+ plen = frame_len - IEEE80211_HDRLEN - 1;
+ req = (const struct fst_setup_req *)
+ (((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+ if (req->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
+ req->stie.length < 11) {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Request dropped: invalid STIE");
+ 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 (plen > sizeof(*req)) {
+ fst_iface_update_mb_ie(iface, mgmt->sa, (const u8 *) (req + 1),
+ plen - 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;
+ size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+ 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 (plen < sizeof(*res)) {
+ fst_printf_session(s, MSG_WARNING,
+ "Too short FST Response dropped");
+ return;
+ }
+ res = (const struct fst_setup_res *)
+ (((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+ if (res->stie.element_id != WLAN_EID_SESSION_TRANSITION ||
+ res->stie.length < 11) {
+ fst_printf_iface(iface, MSG_WARNING,
+ "FST Response dropped: invalid STIE");
+ 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;
+ size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+ union fst_session_state_switch_extra evext = {
+ .to_initial = {
+ .reason = REASON_TEARDOWN,
+ .initiator = FST_INITIATOR_REMOTE,
+ },
+ };
+
+ if (plen < sizeof(*td)) {
+ fst_printf_session(s, MSG_WARNING,
+ "Too short FST Tear Down dropped");
+ return;
+ }
+ td = (const struct fst_tear_down *)
+ (((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+
+ 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;
+ size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+ 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 (plen < sizeof(*req)) {
+ fst_printf_session(s, MSG_WARNING,
+ "Too short FST Ack Request dropped");
+ return;
+ }
+ req = (const struct fst_ack_req *)
+ (((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+
+ 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;
+ size_t plen = frame_len - IEEE80211_HDRLEN - 1;
+ 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 (plen < sizeof(*res)) {
+ fst_printf_session(s, MSG_WARNING,
+ "Too short FST Ack Response dropped");
+ return;
+ }
+ res = (const struct fst_ack_res *)
+ (((const u8 *) mgmt) + IEEE80211_HDRLEN + 1);
+
+ 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) - 2;
+ 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;
+
+ res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+ res.stie.length = sizeof(res.stie) - 2;
+
+ if (status_code == WLAN_STATUS_SUCCESS) {
+ 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 (len < IEEE80211_HDRLEN + 2 ||
+ mgmt->u.action.category != WLAN_ACTION_FST) {
+ fst_printf_iface(iface, MSG_ERROR,
+ "invalid Action frame received");
+ 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;
+}
+
+
+#ifdef CONFIG_FST_TEST
+
+static int get_group_fill_session(struct fst_group **g, struct fst_session *s)
+{
+ const u8 *old_addr, *new_addr;
+ struct fst_get_peer_ctx *ctx;
+
+ os_memset(s, 0, sizeof(*s));
+ foreach_fst_group(*g) {
+ s->data.new_iface = fst_group_first_iface(*g);
+ if (s->data.new_iface)
+ break;
+ }
+ if (!s->data.new_iface)
+ return -EINVAL;
+
+ s->data.old_iface = dl_list_entry(s->data.new_iface->group_lentry.next,
+ struct fst_iface, group_lentry);
+ if (!s->data.old_iface)
+ return -EINVAL;
+
+ old_addr = fst_iface_get_peer_first(s->data.old_iface, &ctx, TRUE);
+ if (!old_addr)
+ return -EINVAL;
+
+ new_addr = fst_iface_get_peer_first(s->data.new_iface, &ctx, TRUE);
+ if (!new_addr)
+ return -EINVAL;
+
+ os_memcpy(s->data.old_peer_addr, old_addr, ETH_ALEN);
+ os_memcpy(s->data.new_peer_addr, new_addr, ETH_ALEN);
+
+ return 0;
+}
+
+
+#define FST_MAX_COMMAND_WORD_NAME_LENGTH 16
+
+int fst_test_req_send_fst_request(const char *params)
+{
+ int fsts_id;
+ Boolean is_valid;
+ char *endp;
+ struct fst_setup_req req;
+ struct fst_session s;
+ struct fst_group *g;
+ enum hostapd_hw_mode hw_mode;
+ u8 channel;
+ char additional_param[FST_MAX_COMMAND_WORD_NAME_LENGTH];
+
+ if (params[0] != ' ')
+ return -EINVAL;
+ params++;
+ fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+ if (!is_valid)
+ return -EINVAL;
+
+ if (get_group_fill_session(&g, &s))
+ return -EINVAL;
+
+ req.action = FST_ACTION_SETUP_REQUEST;
+ req.dialog_token = g->dialog_token;
+ req.llt = host_to_le32(FST_LLT_MS_DEFAULT);
+ /* 8.4.2.147 Session Transition element */
+ req.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+ req.stie.length = sizeof(req.stie) - 2;
+ req.stie.fsts_id = host_to_le32(fsts_id);
+ req.stie.session_control = SESSION_CONTROL(SESSION_TYPE_BSS, 0);
+
+ fst_iface_get_channel_info(s.data.new_iface, &hw_mode, &channel);
+ req.stie.new_band_id = fst_hw_mode_to_band(hw_mode);
+ req.stie.new_band_op = 1;
+ req.stie.new_band_setup = 0;
+
+ fst_iface_get_channel_info(s.data.old_iface, &hw_mode, &channel);
+ req.stie.old_band_id = fst_hw_mode_to_band(hw_mode);
+ req.stie.old_band_op = 1;
+ req.stie.old_band_setup = 0;
+
+ if (!fst_read_next_text_param(endp, additional_param,
+ sizeof(additional_param), &endp)) {
+ if (!os_strcasecmp(additional_param, FST_CTR_PVAL_BAD_NEW_BAND))
+ req.stie.new_band_id = req.stie.old_band_id;
+ }
+
+ return fst_session_send_action(&s, TRUE, &req, sizeof(req),
+ s.data.old_iface->mb_ie);
+}
+
+
+int fst_test_req_send_fst_response(const char *params)
+{
+ int fsts_id;
+ Boolean is_valid;
+ char *endp;
+ struct fst_setup_res res;
+ struct fst_session s;
+ struct fst_group *g;
+ enum hostapd_hw_mode hw_mode;
+ u8 status_code;
+ u8 channel;
+ char response[FST_MAX_COMMAND_WORD_NAME_LENGTH];
+ struct fst_session *_s;
+
+ if (params[0] != ' ')
+ return -EINVAL;
+ params++;
+ fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+ if (!is_valid)
+ return -EINVAL;
+
+ if (get_group_fill_session(&g, &s))
+ return -EINVAL;
+
+ status_code = WLAN_STATUS_SUCCESS;
+ if (!fst_read_next_text_param(endp, response, sizeof(response),
+ &endp)) {
+ if (!os_strcasecmp(response, FST_CS_PVAL_RESPONSE_REJECT))
+ status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
+ }
+
+ os_memset(&res, 0, sizeof(res));
+
+ res.action = FST_ACTION_SETUP_RESPONSE;
+ /*
+ * If some session has just received an FST Setup Request, then
+ * use the correct dialog token copied from this request.
+ */
+ _s = fst_find_session_in_progress(fst_session_get_peer_addr(&s, TRUE),
+ g);
+ res.dialog_token = (_s && fst_session_is_ready_pending(_s)) ?
+ _s->data.pending_setup_req_dlgt : g->dialog_token;
+ res.status_code = status_code;
+
+ res.stie.element_id = WLAN_EID_SESSION_TRANSITION;
+ res.stie.length = sizeof(res.stie) - 2;
+
+ if (res.status_code == WLAN_STATUS_SUCCESS) {
+ res.stie.fsts_id = 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;
+ }
+
+ if (!fst_read_next_text_param(endp, response, sizeof(response),
+ &endp)) {
+ if (!os_strcasecmp(response, FST_CTR_PVAL_BAD_NEW_BAND))
+ res.stie.new_band_id = res.stie.old_band_id;
+ }
+
+ return fst_session_send_action(&s, TRUE, &res, sizeof(res),
+ s.data.old_iface->mb_ie);
+}
+
+
+int fst_test_req_send_ack_request(const char *params)
+{
+ int fsts_id;
+ Boolean is_valid;
+ char *endp;
+ struct fst_ack_req req;
+ struct fst_session s;
+ struct fst_group *g;
+
+ if (params[0] != ' ')
+ return -EINVAL;
+ params++;
+ fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+ if (!is_valid)
+ return -EINVAL;
+
+ if (get_group_fill_session(&g, &s))
+ return -EINVAL;
+
+ os_memset(&req, 0, sizeof(req));
+ req.action = FST_ACTION_ACK_REQUEST;
+ req.dialog_token = g->dialog_token;
+ req.fsts_id = fsts_id;
+
+ return fst_session_send_action(&s, FALSE, &req, sizeof(req), NULL);
+}
+
+
+int fst_test_req_send_ack_response(const char *params)
+{
+ int fsts_id;
+ Boolean is_valid;
+ char *endp;
+ struct fst_ack_res res;
+ struct fst_session s;
+ struct fst_group *g;
+
+ if (params[0] != ' ')
+ return -EINVAL;
+ params++;
+ fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+ if (!is_valid)
+ return -EINVAL;
+
+ if (get_group_fill_session(&g, &s))
+ return -EINVAL;
+
+ os_memset(&res, 0, sizeof(res));
+ res.action = FST_ACTION_ACK_RESPONSE;
+ res.dialog_token = g->dialog_token;
+ res.fsts_id = fsts_id;
+
+ return fst_session_send_action(&s, FALSE, &res, sizeof(res), NULL);
+}
+
+
+int fst_test_req_send_tear_down(const char *params)
+{
+ int fsts_id;
+ Boolean is_valid;
+ char *endp;
+ struct fst_tear_down td;
+ struct fst_session s;
+ struct fst_group *g;
+
+ if (params[0] != ' ')
+ return -EINVAL;
+ params++;
+ fsts_id = fst_read_next_int_param(params, &is_valid, &endp);
+ if (!is_valid)
+ return -EINVAL;
+
+ if (get_group_fill_session(&g, &s))
+ return -EINVAL;
+
+ os_memset(&td, 0, sizeof(td));
+ td.action = FST_ACTION_TEAR_DOWN;
+ td.fsts_id = fsts_id;
+
+ return fst_session_send_action(&s, TRUE, &td, sizeof(td), NULL);
+}
+
+
+u32 fst_test_req_get_fsts_id(const char *params)
+{
+ int sid;
+ Boolean is_valid;
+ char *endp;
+ struct fst_session *s;
+
+ if (params[0] != ' ')
+ return FST_FSTS_ID_NOT_FOUND;
+ params++;
+ sid = fst_read_next_int_param(params, &is_valid, &endp);
+ if (!is_valid)
+ return FST_FSTS_ID_NOT_FOUND;
+
+ s = fst_session_get_by_id(sid);
+ if (!s)
+ return FST_FSTS_ID_NOT_FOUND;
+
+ return s->data.fsts_id;
+}
+
+
+int fst_test_req_get_local_mbies(const char *request, char *buf, size_t buflen)
+{
+ char *endp;
+ char ifname[FST_MAX_COMMAND_WORD_NAME_LENGTH];
+ struct fst_group *g;
+ struct fst_iface *iface;
+
+ if (request[0] != ' ')
+ return -EINVAL;
+ request++;
+ if (fst_read_next_text_param(request, ifname, sizeof(ifname), &endp) ||
+ !*ifname)
+ goto problem;
+ g = dl_list_first(&fst_global_groups_list, struct fst_group,
+ global_groups_lentry);
+ if (!g)
+ goto problem;
+ iface = fst_group_get_iface_by_name(g, ifname);
+ if (!iface || !iface->mb_ie)
+ goto problem;
+ return wpa_snprintf_hex(buf, buflen, wpabuf_head(iface->mb_ie),
+ wpabuf_len(iface->mb_ie));
+
+problem:
+ return os_snprintf(buf, buflen, "FAIL\n");
+}
+
+#endif /* CONFIG_FST_TEST */
diff --git a/src/fst/fst_session.h b/src/fst/fst_session.h
new file mode 100644
index 0000000..1162de4
--- /dev/null
+++ b/src/fst/fst_session.h
@@ -0,0 +1,80 @@
+/*
+ * 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);
+
+#ifdef CONFIG_FST_TEST
+
+#define FST_FSTS_ID_NOT_FOUND ((u32) -1)
+
+int fst_test_req_send_fst_request(const char *params);
+int fst_test_req_send_fst_response(const char *params);
+int fst_test_req_send_ack_request(const char *params);
+int fst_test_req_send_ack_response(const char *params);
+int fst_test_req_send_tear_down(const char *params);
+u32 fst_test_req_get_fsts_id(const char *params);
+int fst_test_req_get_local_mbies(const char *request, char *buf,
+ size_t buflen);
+
+#endif /* CONFIG_FST_TEST */
+
+#endif /* FST_SESSION_H */