text: /*
   * 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;
  }
  