Merged the hostap_2.6 updates, and the Leap of Faith work, from the hostap_update...
[mech_eap.git] / libeap / src / fst / fst_ctrl_iface.c
diff --git a/libeap/src/fst/fst_ctrl_iface.c b/libeap/src/fst/fst_ctrl_iface.c
new file mode 100644 (file)
index 0000000..7820e58
--- /dev/null
@@ -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, TRUE)) {
+                               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;
+}