1  /*
2   * FST module - Control Interface implementation
3   * Copyright (c) 2014, Qualcomm Atheros, Inc.
4   *
5   * This software may be distributed under the terms of the BSD license.
6   * See README for more details.
7   */
8  
9  #include "utils/includes.h"
10  #include "utils/common.h"
11  #include "common/defs.h"
12  #include "list.h"
13  #include "fst/fst.h"
14  #include "fst/fst_internal.h"
15  #include "fst_ctrl_defs.h"
16  #include "fst_ctrl_iface.h"
17  
18  
get_fst_group_by_id(const char * id)19  static struct fst_group * get_fst_group_by_id(const char *id)
20  {
21  	struct fst_group *g;
22  
23  	foreach_fst_group(g) {
24  		const char *group_id = fst_group_get_id(g);
25  
26  		if (os_strncmp(group_id, id, os_strlen(group_id)) == 0)
27  			return g;
28  	}
29  
30  	return NULL;
31  }
32  
33  
34  /* notifications */
format_session_state_extra(const union fst_event_extra * extra,char * buffer,size_t size)35  static bool format_session_state_extra(const union fst_event_extra *extra,
36  				       char *buffer, size_t size)
37  {
38  	int len;
39  	char reject_str[32] = FST_CTRL_PVAL_NONE;
40  	const char *initiator = FST_CTRL_PVAL_NONE;
41  	const struct fst_event_extra_session_state *ss;
42  
43  	ss = &extra->session_state;
44  	if (ss->new_state != FST_SESSION_STATE_INITIAL)
45  		return true;
46  
47  	switch (ss->extra.to_initial.reason) {
48  	case REASON_REJECT:
49  		if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS)
50  			os_snprintf(reject_str, sizeof(reject_str), "%u",
51  				    ss->extra.to_initial.reject_code);
52  		/* fall through */
53  	case REASON_TEARDOWN:
54  	case REASON_SWITCH:
55  		switch (ss->extra.to_initial.initiator) {
56  		case FST_INITIATOR_LOCAL:
57  			initiator = FST_CS_PVAL_INITIATOR_LOCAL;
58  			break;
59  		case FST_INITIATOR_REMOTE:
60  			initiator = FST_CS_PVAL_INITIATOR_REMOTE;
61  			break;
62  		default:
63  			break;
64  		}
65  		break;
66  	default:
67  		break;
68  	}
69  
70  	len = os_snprintf(buffer, size,
71  			  FST_CES_PNAME_REASON "=%s "
72  			  FST_CES_PNAME_REJECT_CODE "=%s "
73  			  FST_CES_PNAME_INITIATOR "=%s",
74  			  fst_reason_name(ss->extra.to_initial.reason),
75  			  reject_str, initiator);
76  
77  	return !os_snprintf_error(size, len);
78  }
79  
80  
fst_ctrl_iface_notify(struct fst_iface * f,u32 session_id,enum fst_event_type event_type,const union fst_event_extra * extra)81  static void fst_ctrl_iface_notify(struct fst_iface *f, u32 session_id,
82  				  enum fst_event_type event_type,
83  				  const union fst_event_extra *extra)
84  {
85  	struct fst_group *g;
86  	char extra_str[128] = "";
87  	const struct fst_event_extra_session_state *ss;
88  	const struct fst_event_extra_iface_state *is;
89  	const struct fst_event_extra_peer_state *ps;
90  
91  	/*
92  	 * FST can use any of interface objects as it only sends messages
93  	 * on global Control Interface, so we just pick the 1st one.
94  	 */
95  
96  	if (!f) {
97  		foreach_fst_group(g) {
98  			f = fst_group_first_iface(g);
99  			if (f)
100  				break;
101  		}
102  		if (!f)
103  			return;
104  	}
105  
106  	WPA_ASSERT(f->iface_obj.ctx);
107  
108  	switch (event_type) {
109  	case EVENT_FST_IFACE_STATE_CHANGED:
110  		if (!extra)
111  			return;
112  		is = &extra->iface_state;
113  		wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO,
114  				    FST_CTRL_EVENT_IFACE " %s "
115  				    FST_CEI_PNAME_IFNAME "=%s "
116  				    FST_CEI_PNAME_GROUP "=%s",
117  				    is->attached ? FST_CEI_PNAME_ATTACHED :
118  				    FST_CEI_PNAME_DETACHED,
119  				    is->ifname, is->group_id);
120  		break;
121  	case EVENT_PEER_STATE_CHANGED:
122  		if (!extra)
123  			return;
124  		ps = &extra->peer_state;
125  		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
126  				    FST_CTRL_EVENT_PEER " %s "
127  				    FST_CEP_PNAME_IFNAME "=%s "
128  				    FST_CEP_PNAME_ADDR "=" MACSTR,
129  				    ps->connected ? FST_CEP_PNAME_CONNECTED :
130  				    FST_CEP_PNAME_DISCONNECTED,
131  				    ps->ifname, MAC2STR(ps->addr));
132  		break;
133  	case EVENT_FST_SESSION_STATE_CHANGED:
134  		if (!extra)
135  			return;
136  		if (!format_session_state_extra(extra, extra_str,
137  						sizeof(extra_str))) {
138  			fst_printf(MSG_ERROR,
139  				   "CTRL: Cannot format STATE_CHANGE extra");
140  			extra_str[0] = 0;
141  		}
142  		ss = &extra->session_state;
143  		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
144  				    FST_CTRL_EVENT_SESSION " "
145  				    FST_CES_PNAME_SESSION_ID "=%u "
146  				    FST_CES_PNAME_EVT_TYPE "=%s "
147  				    FST_CES_PNAME_OLD_STATE "=%s "
148  				    FST_CES_PNAME_NEW_STATE "=%s %s",
149  				    session_id,
150  				    fst_session_event_type_name(event_type),
151  				    fst_session_state_name(ss->old_state),
152  				    fst_session_state_name(ss->new_state),
153  				    extra_str);
154  		break;
155  	case EVENT_FST_ESTABLISHED:
156  	case EVENT_FST_SETUP:
157  		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
158  				    FST_CTRL_EVENT_SESSION " "
159  				    FST_CES_PNAME_SESSION_ID "=%u "
160  				    FST_CES_PNAME_EVT_TYPE "=%s",
161  				    session_id,
162  				    fst_session_event_type_name(event_type));
163  		break;
164  	}
165  }
166  
167  
168  /* command processors */
169  
170  /* fst session_get */
session_get(const char * session_id,char * buf,size_t buflen)171  static int session_get(const char *session_id, char *buf, size_t buflen)
172  {
173  	struct fst_session *s;
174  	struct fst_iface *new_iface, *old_iface;
175  	const u8 *old_peer_addr, *new_peer_addr;
176  	u32 id;
177  
178  	id = strtoul(session_id, NULL, 0);
179  
180  	s = fst_session_get_by_id(id);
181  	if (!s) {
182  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
183  		return os_snprintf(buf, buflen, "FAIL\n");
184  	}
185  
186  	old_peer_addr = fst_session_get_peer_addr(s, true);
187  	new_peer_addr = fst_session_get_peer_addr(s, false);
188  	new_iface = fst_session_get_iface(s, false);
189  	old_iface = fst_session_get_iface(s, true);
190  
191  	return os_snprintf(buf, buflen,
192  			   FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n"
193  			   FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n"
194  			   FST_CSG_PNAME_NEW_IFNAME "=%s\n"
195  			   FST_CSG_PNAME_OLD_IFNAME "=%s\n"
196  			   FST_CSG_PNAME_LLT "=%u\n"
197  			   FST_CSG_PNAME_STATE "=%s\n",
198  			   MAC2STR(old_peer_addr),
199  			   MAC2STR(new_peer_addr),
200  			   new_iface ? fst_iface_get_name(new_iface) :
201  			   FST_CTRL_PVAL_NONE,
202  			   old_iface ? fst_iface_get_name(old_iface) :
203  			   FST_CTRL_PVAL_NONE,
204  			   fst_session_get_llt(s),
205  			   fst_session_state_name(fst_session_get_state(s)));
206  }
207  
208  
209  /* fst session_set */
session_set(const char * session_id,char * buf,size_t buflen)210  static int session_set(const char *session_id, char *buf, size_t buflen)
211  {
212  	struct fst_session *s;
213  	char *p, *q;
214  	u32 id;
215  	int ret;
216  
217  	id = strtoul(session_id, &p, 0);
218  
219  	s = fst_session_get_by_id(id);
220  	if (!s) {
221  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
222  		return os_snprintf(buf, buflen, "FAIL\n");
223  	}
224  
225  	if (*p != ' ' || !(q = os_strchr(p + 1, '=')))
226  		return os_snprintf(buf, buflen, "FAIL\n");
227  	p++;
228  
229  	if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) {
230  		ret = fst_session_set_str_ifname(s, q + 1, true);
231  	} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) {
232  		ret = fst_session_set_str_ifname(s, q + 1, false);
233  	} else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) {
234  		ret = fst_session_set_str_peer_addr(s, q + 1, true);
235  	} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) {
236  		ret = fst_session_set_str_peer_addr(s, q + 1, false);
237  	} else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) {
238  		ret = fst_session_set_str_llt(s, q + 1);
239  	} else {
240  		fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
241  		return os_snprintf(buf, buflen, "FAIL\n");
242  	}
243  
244  	return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
245  }
246  
247  
248  /* fst session_add/remove */
session_add(const char * group_id,char * buf,size_t buflen)249  static int session_add(const char *group_id, char *buf, size_t buflen)
250  {
251  	struct fst_group *g;
252  	struct fst_session *s;
253  
254  	g = get_fst_group_by_id(group_id);
255  	if (!g) {
256  		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
257  			   group_id);
258  		return os_snprintf(buf, buflen, "FAIL\n");
259  	}
260  
261  	s = fst_session_create(g);
262  	if (!s) {
263  		fst_printf(MSG_ERROR,
264  			   "CTRL: Cannot create session for group '%s'",
265  			   group_id);
266  		return os_snprintf(buf, buflen, "FAIL\n");
267  	}
268  
269  	return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s));
270  }
271  
272  
session_remove(const char * session_id,char * buf,size_t buflen)273  static int session_remove(const char *session_id, char *buf, size_t buflen)
274  {
275  	struct fst_session *s;
276  	struct fst_group *g;
277  	u32 id;
278  
279  	id = strtoul(session_id, NULL, 0);
280  
281  	s = fst_session_get_by_id(id);
282  	if (!s) {
283  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
284  		return os_snprintf(buf, buflen, "FAIL\n");
285  	}
286  
287  	g = fst_session_get_group(s);
288  	fst_session_reset(s);
289  	fst_session_delete(s);
290  	fst_group_delete_if_empty(g);
291  
292  	return os_snprintf(buf, buflen, "OK\n");
293  }
294  
295  
296  /* fst session_initiate */
session_initiate(const char * session_id,char * buf,size_t buflen)297  static int session_initiate(const char *session_id, char *buf, size_t buflen)
298  {
299  	struct fst_session *s;
300  	u32 id;
301  
302  	id = strtoul(session_id, NULL, 0);
303  
304  	s = fst_session_get_by_id(id);
305  	if (!s) {
306  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
307  		return os_snprintf(buf, buflen, "FAIL\n");
308  	}
309  
310  	if (fst_session_initiate_setup(s)) {
311  		fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id);
312  		return os_snprintf(buf, buflen, "FAIL\n");
313  	}
314  
315  	return os_snprintf(buf, buflen, "OK\n");
316  }
317  
318  
319  /* fst session_respond */
session_respond(const char * session_id,char * buf,size_t buflen)320  static int session_respond(const char *session_id, char *buf, size_t buflen)
321  {
322  	struct fst_session *s;
323  	char *p;
324  	u32 id;
325  	u8 status_code;
326  
327  	id = strtoul(session_id, &p, 0);
328  
329  	s = fst_session_get_by_id(id);
330  	if (!s) {
331  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
332  		return os_snprintf(buf, buflen, "FAIL\n");
333  	}
334  
335  	if (*p != ' ')
336  		return os_snprintf(buf, buflen, "FAIL\n");
337  	p++;
338  
339  	if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) {
340  		status_code = WLAN_STATUS_SUCCESS;
341  	} else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) {
342  		status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
343  	} else {
344  		fst_printf(MSG_WARNING,
345  			   "CTRL: session %u: unknown response status: %s",
346  			   id, p);
347  		return os_snprintf(buf, buflen, "FAIL\n");
348  	}
349  
350  	if (fst_session_respond(s, status_code)) {
351  		fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u",
352  			   id);
353  		return os_snprintf(buf, buflen, "FAIL\n");
354  	}
355  
356  	fst_printf(MSG_INFO, "CTRL: session %u responded", id);
357  
358  	return os_snprintf(buf, buflen, "OK\n");
359  }
360  
361  
362  /* fst session_transfer */
session_transfer(const char * session_id,char * buf,size_t buflen)363  static int session_transfer(const char *session_id, char *buf, size_t buflen)
364  {
365  	struct fst_session *s;
366  	u32 id;
367  
368  	id = strtoul(session_id, NULL, 0);
369  
370  	s = fst_session_get_by_id(id);
371  	if (!s) {
372  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
373  		return os_snprintf(buf, buflen, "FAIL\n");
374  	}
375  
376  	if (fst_session_initiate_switch(s)) {
377  		fst_printf(MSG_WARNING,
378  			   "CTRL: Cannot initiate ST for session %u", id);
379  		return os_snprintf(buf, buflen, "FAIL\n");
380  	}
381  
382  	return os_snprintf(buf, buflen, "OK\n");
383  }
384  
385  
386  /* fst session_teardown */
session_teardown(const char * session_id,char * buf,size_t buflen)387  static int session_teardown(const char *session_id, char *buf, size_t buflen)
388  {
389  	struct fst_session *s;
390  	u32 id;
391  
392  	id = strtoul(session_id, NULL, 0);
393  
394  	s = fst_session_get_by_id(id);
395  	if (!s) {
396  		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
397  		return os_snprintf(buf, buflen, "FAIL\n");
398  	}
399  
400  	if (fst_session_tear_down_setup(s)) {
401  		fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u",
402  			   id);
403  		return os_snprintf(buf, buflen, "FAIL\n");
404  	}
405  
406  	return os_snprintf(buf, buflen, "OK\n");
407  }
408  
409  
410  #ifdef CONFIG_FST_TEST
411  /* fst test_request */
test_request(const char * request,char * buf,size_t buflen)412  static int test_request(const char *request, char *buf, size_t buflen)
413  {
414  	const char *p = request;
415  	int ret;
416  
417  	if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_REQUEST,
418  			    os_strlen(FST_CTR_SEND_SETUP_REQUEST))) {
419  		ret = fst_test_req_send_fst_request(
420  			p + os_strlen(FST_CTR_SEND_SETUP_REQUEST));
421  	} else if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_RESPONSE,
422  				   os_strlen(FST_CTR_SEND_SETUP_RESPONSE))) {
423  		ret = fst_test_req_send_fst_response(
424  			p + os_strlen(FST_CTR_SEND_SETUP_RESPONSE));
425  	} else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_REQUEST,
426  				   os_strlen(FST_CTR_SEND_ACK_REQUEST))) {
427  		ret = fst_test_req_send_ack_request(
428  			p + os_strlen(FST_CTR_SEND_ACK_REQUEST));
429  	} else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_RESPONSE,
430  				   os_strlen(FST_CTR_SEND_ACK_RESPONSE))) {
431  		ret = fst_test_req_send_ack_response(
432  			p + os_strlen(FST_CTR_SEND_ACK_RESPONSE));
433  	} else if (!os_strncasecmp(p, FST_CTR_SEND_TEAR_DOWN,
434  				   os_strlen(FST_CTR_SEND_TEAR_DOWN))) {
435  		ret = fst_test_req_send_tear_down(
436  			p + os_strlen(FST_CTR_SEND_TEAR_DOWN));
437  	} else if (!os_strncasecmp(p, FST_CTR_GET_FSTS_ID,
438  				   os_strlen(FST_CTR_GET_FSTS_ID))) {
439  		u32 fsts_id = fst_test_req_get_fsts_id(
440  			p + os_strlen(FST_CTR_GET_FSTS_ID));
441  		if (fsts_id != FST_FSTS_ID_NOT_FOUND)
442  			return os_snprintf(buf, buflen, "%u\n", fsts_id);
443  		return os_snprintf(buf, buflen, "FAIL\n");
444  	} else if (!os_strncasecmp(p, FST_CTR_GET_LOCAL_MBIES,
445  				   os_strlen(FST_CTR_GET_LOCAL_MBIES))) {
446  		return fst_test_req_get_local_mbies(
447  			p + os_strlen(FST_CTR_GET_LOCAL_MBIES), buf, buflen);
448  	} else if (!os_strncasecmp(p, FST_CTR_IS_SUPPORTED,
449  				   os_strlen(FST_CTR_IS_SUPPORTED))) {
450  		ret = 0;
451  	} else {
452  		fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
453  		return os_snprintf(buf, buflen, "FAIL\n");
454  	}
455  
456  	return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
457  }
458  #endif /* CONFIG_FST_TEST */
459  
460  
461  /* fst list_sessions */
462  struct list_sessions_cb_ctx {
463  	char *buf;
464  	size_t buflen;
465  	size_t reply_len;
466  };
467  
468  
list_session_enum_cb(struct fst_group * g,struct fst_session * s,void * ctx)469  static void list_session_enum_cb(struct fst_group *g, struct fst_session *s,
470  				 void *ctx)
471  {
472  	struct list_sessions_cb_ctx *c = ctx;
473  	int ret;
474  
475  	ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s));
476  
477  	c->buf += ret;
478  	c->buflen -= ret;
479  	c->reply_len += ret;
480  }
481  
482  
list_sessions(const char * group_id,char * buf,size_t buflen)483  static int list_sessions(const char *group_id, char *buf, size_t buflen)
484  {
485  	struct list_sessions_cb_ctx ctx;
486  	struct fst_group *g;
487  
488  	g = get_fst_group_by_id(group_id);
489  	if (!g) {
490  		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
491  			   group_id);
492  		return os_snprintf(buf, buflen, "FAIL\n");
493  	}
494  
495  	ctx.buf = buf;
496  	ctx.buflen = buflen;
497  	ctx.reply_len = 0;
498  
499  	fst_session_enum(g, list_session_enum_cb, &ctx);
500  
501  	ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n");
502  
503  	return ctx.reply_len;
504  }
505  
506  
507  /* fst iface_peers */
iface_peers(const char * group_id,char * buf,size_t buflen)508  static int iface_peers(const char *group_id, char *buf, size_t buflen)
509  {
510  	const char *ifname;
511  	struct fst_group *g;
512  	struct fst_iface *f;
513  	struct fst_get_peer_ctx *ctx;
514  	const u8 *addr;
515  	unsigned found = 0;
516  	int ret = 0;
517  
518  	g = get_fst_group_by_id(group_id);
519  	if (!g) {
520  		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
521  			   group_id);
522  		return os_snprintf(buf, buflen, "FAIL\n");
523  	}
524  
525  	ifname = os_strchr(group_id, ' ');
526  	if (!ifname)
527  		return os_snprintf(buf, buflen, "FAIL\n");
528  	ifname++;
529  
530  	foreach_fst_group_iface(g, f) {
531  		const char *in = fst_iface_get_name(f);
532  
533  		if (os_strncmp(ifname, in, os_strlen(in)) == 0) {
534  			found = 1;
535  			break;
536  		}
537  	}
538  
539  	if (!found)
540  		return os_snprintf(buf, buflen, "FAIL\n");
541  
542  	addr = fst_iface_get_peer_first(f, &ctx, false);
543  	for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, false)) {
544  		int res;
545  
546  		res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n",
547  				  MAC2STR(addr));
548  		if (os_snprintf_error(buflen - ret, res))
549  			break;
550  		ret += res;
551  	}
552  
553  	return ret;
554  }
555  
556  
get_peer_mbies(const char * params,char * buf,size_t buflen)557  static int get_peer_mbies(const char *params, char *buf, size_t buflen)
558  {
559  	char *endp;
560  	char ifname[FST_MAX_INTERFACE_SIZE];
561  	u8 peer_addr[ETH_ALEN];
562  	struct fst_group *g;
563  	struct fst_iface *iface = NULL;
564  	const struct wpabuf *mbies;
565  
566  	if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) ||
567  	    !*ifname)
568  		goto problem;
569  
570  	while (isspace(*endp))
571  		endp++;
572  	if (fst_read_peer_addr(endp, peer_addr))
573  		goto problem;
574  
575  	foreach_fst_group(g) {
576  		iface = fst_group_get_iface_by_name(g, ifname);
577  		if (iface)
578  			break;
579  	}
580  	if (!iface)
581  		goto problem;
582  
583  	mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
584  	if (!mbies)
585  		goto problem;
586  
587  	return wpa_snprintf_hex(buf, buflen, wpabuf_head(mbies),
588  				wpabuf_len(mbies));
589  
590  problem:
591  	return os_snprintf(buf, buflen, "FAIL\n");
592  }
593  
594  
595  /* fst list_ifaces */
list_ifaces(const char * group_id,char * buf,size_t buflen)596  static int list_ifaces(const char *group_id, char *buf, size_t buflen)
597  {
598  	struct fst_group *g;
599  	struct fst_iface *f;
600  	int ret = 0;
601  
602  	g = get_fst_group_by_id(group_id);
603  	if (!g) {
604  		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
605  			   group_id);
606  		return os_snprintf(buf, buflen, "FAIL\n");
607  	}
608  
609  	foreach_fst_group_iface(g, f) {
610  		int res;
611  		const u8 *iface_addr = fst_iface_get_addr(f);
612  
613  		res = os_snprintf(buf + ret, buflen - ret,
614  				  "%s|" MACSTR "|%u|%u\n",
615  				  fst_iface_get_name(f),
616  				  MAC2STR(iface_addr),
617  				  fst_iface_get_priority(f),
618  				  fst_iface_get_llt(f));
619  		if (os_snprintf_error(buflen - ret, res))
620  			break;
621  		ret += res;
622  	}
623  
624  	return ret;
625  }
626  
627  
628  /* fst list_groups */
list_groups(const char * cmd,char * buf,size_t buflen)629  static int list_groups(const char *cmd, char *buf, size_t buflen)
630  {
631  	struct fst_group *g;
632  	int ret = 0;
633  
634  	foreach_fst_group(g) {
635  		int res;
636  
637  		res = os_snprintf(buf + ret, buflen - ret, "%s\n",
638  				  fst_group_get_id(g));
639  		if (os_snprintf_error(buflen - ret, res))
640  			break;
641  		ret += res;
642  	}
643  
644  	return ret;
645  }
646  
647  
band_freq(enum mb_band_id band)648  static const char * band_freq(enum mb_band_id band)
649  {
650  	static const char *band_names[] = {
651  		[MB_BAND_ID_WIFI_2_4GHZ] = "2.4GHZ",
652  		[MB_BAND_ID_WIFI_5GHZ] = "5GHZ",
653  		[MB_BAND_ID_WIFI_60GHZ] = "60GHZ",
654  	};
655  
656  	return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names));
657  }
658  
659  
print_band(unsigned num,struct fst_iface * iface,const u8 * addr,char * buf,size_t buflen)660  static int print_band(unsigned num, struct fst_iface *iface, const u8 *addr,
661  		      char *buf, size_t buflen)
662  {
663  	const struct wpabuf *wpabuf;
664  	enum hostapd_hw_mode hw_mode;
665  	u8 channel;
666  	int ret = 0;
667  
668  	fst_iface_get_channel_info(iface, &hw_mode, &channel);
669  
670  	ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n",
671  			   num, band_freq(fst_hw_mode_to_band(hw_mode)));
672  	ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n",
673  			   num, fst_iface_get_name(iface));
674  	wpabuf = fst_iface_get_peer_mb_ie(iface, addr);
675  	if (wpabuf) {
676  		ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=",
677  				   num);
678  		ret += wpa_snprintf_hex(buf + ret, buflen - ret,
679  					wpabuf_head(wpabuf),
680  					wpabuf_len(wpabuf));
681  		ret += os_snprintf(buf + ret, buflen - ret, "\n");
682  	}
683  	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n",
684  			   num, fst_iface_get_group_id(iface));
685  	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n",
686  			   num, fst_iface_get_priority(iface));
687  	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n",
688  			   num, fst_iface_get_llt(iface));
689  
690  	return ret;
691  }
692  
693  
fst_ctrl_iface_on_iface_state_changed(struct fst_iface * i,bool attached)694  static void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i,
695  						  bool attached)
696  {
697  	union fst_event_extra extra;
698  
699  	os_memset(&extra, 0, sizeof(extra));
700  	extra.iface_state.attached = attached;
701  	os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i),
702  		   sizeof(extra.iface_state.ifname));
703  	os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i),
704  		   sizeof(extra.iface_state.group_id));
705  
706  	fst_ctrl_iface_notify(i, FST_INVALID_SESSION_ID,
707  			      EVENT_FST_IFACE_STATE_CHANGED, &extra);
708  }
709  
710  
fst_ctrl_iface_on_iface_added(struct fst_iface * i)711  static int fst_ctrl_iface_on_iface_added(struct fst_iface *i)
712  {
713  	fst_ctrl_iface_on_iface_state_changed(i, true);
714  	return 0;
715  }
716  
717  
fst_ctrl_iface_on_iface_removed(struct fst_iface * i)718  static void fst_ctrl_iface_on_iface_removed(struct fst_iface *i)
719  {
720  	fst_ctrl_iface_on_iface_state_changed(i, false);
721  }
722  
723  
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)724  static void fst_ctrl_iface_on_event(enum fst_event_type event_type,
725  				    struct fst_iface *i, struct fst_session *s,
726  				    const union fst_event_extra *extra)
727  {
728  	u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID;
729  
730  	fst_ctrl_iface_notify(i, session_id, event_type, extra);
731  }
732  
733  
734  static const struct fst_ctrl ctrl_cli = {
735  	.on_iface_added = fst_ctrl_iface_on_iface_added,
736  	.on_iface_removed =  fst_ctrl_iface_on_iface_removed,
737  	.on_event = fst_ctrl_iface_on_event,
738  };
739  
740  const struct fst_ctrl *fst_ctrl_cli = &ctrl_cli;
741  
742  
fst_ctrl_iface_mb_info(const u8 * addr,char * buf,size_t buflen)743  int fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
744  {
745  	struct fst_group *g;
746  	struct fst_iface *f;
747  	unsigned num = 0;
748  	int ret = 0;
749  
750  	foreach_fst_group(g) {
751  		foreach_fst_group_iface(g, f) {
752  			if (fst_iface_is_connected(f, addr, true)) {
753  				ret += print_band(num++, f, addr,
754  						  buf + ret, buflen - ret);
755  			}
756  		}
757  	}
758  
759  	return ret;
760  }
761  
762  
763  /* fst ctrl processor */
fst_ctrl_iface_receive(const char * cmd,char * reply,size_t reply_size)764  int fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size)
765  {
766  	static const struct fst_command {
767  		const char *name;
768  		unsigned has_param;
769  		int (*process)(const char *group_id, char *buf, size_t buflen);
770  	} commands[] = {
771  		{ FST_CMD_LIST_GROUPS, 0, list_groups},
772  		{ FST_CMD_LIST_IFACES, 1, list_ifaces},
773  		{ FST_CMD_IFACE_PEERS, 1, iface_peers},
774  		{ FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies},
775  		{ FST_CMD_LIST_SESSIONS, 1, list_sessions},
776  		{ FST_CMD_SESSION_ADD, 1, session_add},
777  		{ FST_CMD_SESSION_REMOVE, 1, session_remove},
778  		{ FST_CMD_SESSION_GET, 1, session_get},
779  		{ FST_CMD_SESSION_SET, 1, session_set},
780  		{ FST_CMD_SESSION_INITIATE, 1, session_initiate},
781  		{ FST_CMD_SESSION_RESPOND, 1, session_respond},
782  		{ FST_CMD_SESSION_TRANSFER, 1, session_transfer},
783  		{ FST_CMD_SESSION_TEARDOWN, 1, session_teardown},
784  #ifdef CONFIG_FST_TEST
785  		{ FST_CMD_TEST_REQUEST, 1, test_request },
786  #endif /* CONFIG_FST_TEST */
787  		{ NULL, 0, NULL }
788  	};
789  	const struct fst_command *c;
790  	const char *p;
791  	const char *temp;
792  	bool non_spaces_found;
793  
794  	for (c = commands; c->name; c++) {
795  		if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0)
796  			continue;
797  		p = cmd + os_strlen(c->name);
798  		if (c->has_param) {
799  			if (!isspace(p[0]))
800  				return os_snprintf(reply, reply_size, "FAIL\n");
801  			p++;
802  			temp = p;
803  			non_spaces_found = false;
804  			while (*temp) {
805  				if (!isspace(*temp)) {
806  					non_spaces_found = true;
807  					break;
808  				}
809  				temp++;
810  			}
811  			if (!non_spaces_found)
812  				return os_snprintf(reply, reply_size, "FAIL\n");
813  		}
814  		return c->process(p, reply, reply_size);
815  	}
816  
817  	return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n");
818  }
819  
820  
fst_read_next_int_param(const char * params,bool * valid,char ** endp)821  int fst_read_next_int_param(const char *params, bool *valid, char **endp)
822  {
823  	int ret = -1;
824  	const char *curp;
825  
826  	*valid = false;
827  	*endp = (char *) params;
828  	curp = params;
829  	if (*curp) {
830  		ret = (int) strtol(curp, endp, 0);
831  		if (!**endp || isspace(**endp))
832  			*valid = true;
833  	}
834  
835  	return ret;
836  }
837  
838  
fst_read_next_text_param(const char * params,char * buf,size_t buflen,char ** endp)839  int fst_read_next_text_param(const char *params, char *buf, size_t buflen,
840  			     char **endp)
841  {
842  	size_t max_chars_to_copy;
843  	char *cur_dest;
844  
845  	*endp = (char *) params;
846  	while (isspace(**endp))
847  		(*endp)++;
848  	if (!**endp || buflen <= 1)
849  		return -EINVAL;
850  
851  	max_chars_to_copy = buflen - 1;
852  	/* We need 1 byte for the terminating zero */
853  	cur_dest = buf;
854  	while (**endp && !isspace(**endp) && max_chars_to_copy > 0) {
855  		*cur_dest = **endp;
856  		(*endp)++;
857  		cur_dest++;
858  		max_chars_to_copy--;
859  	}
860  	*cur_dest = 0;
861  
862  	return 0;
863  }
864  
865  
fst_read_peer_addr(const char * mac,u8 * peer_addr)866  int fst_read_peer_addr(const char *mac, u8 *peer_addr)
867  {
868  	if (hwaddr_aton(mac, peer_addr)) {
869  		fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string",
870  			   mac);
871  		return -1;
872  	}
873  
874  	if (is_zero_ether_addr(peer_addr) ||
875  	    is_multicast_ether_addr(peer_addr)) {
876  		fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr",
877  			   mac);
878  		return -1;
879  	}
880  
881  	return 0;
882  }
883  
884  
fst_parse_attach_command(const char * cmd,char * ifname,size_t ifname_size,struct fst_iface_cfg * cfg)885  int fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
886  			     struct fst_iface_cfg *cfg)
887  {
888  	char *pos;
889  	char *endp;
890  	bool is_valid;
891  	int val;
892  
893  	if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) ||
894  	    fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id),
895  				     &endp))
896  		return -EINVAL;
897  
898  	cfg->llt = FST_DEFAULT_LLT_CFG_VALUE;
899  	cfg->priority = 0;
900  	pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT);
901  	if (pos) {
902  		pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT);
903  		if (*pos == '=') {
904  			val = fst_read_next_int_param(pos + 1, &is_valid,
905  						      &endp);
906  			if (is_valid)
907  				cfg->llt = val;
908  		}
909  	}
910  	pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY);
911  	if (pos) {
912  		pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY);
913  		if (*pos == '=') {
914  			val = fst_read_next_int_param(pos + 1, &is_valid,
915  						      &endp);
916  			if (is_valid)
917  				cfg->priority = (u8) val;
918  		}
919  	}
920  
921  	return 0;
922  }
923  
924  
fst_parse_detach_command(const char * cmd,char * ifname,size_t ifname_size)925  int fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size)
926  {
927  	char *endp;
928  
929  	return fst_read_next_text_param(cmd, ifname, ifname_size, &endp);
930  }
931  
932  
fst_iface_detach(const char * ifname)933  int fst_iface_detach(const char *ifname)
934  {
935  	struct fst_group *g;
936  
937  	foreach_fst_group(g) {
938  		struct fst_iface *f;
939  
940  		f = fst_group_get_iface_by_name(g, ifname);
941  		if (f) {
942  			fst_detach(f);
943  			return 0;
944  		}
945  	}
946  
947  	return -EINVAL;
948  }
949