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
19static 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 */
35static Boolean 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		/* no break */
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
81static 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 */
171static 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 */
210static 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 */
249static 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
273static 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 */
297static 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 */
320static 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 */
363static 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 */
387static 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 */
412static 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 */
462struct list_sessions_cb_ctx {
463	char *buf;
464	size_t buflen;
465	size_t reply_len;
466};
467
468
469static 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
483static 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 */
508static 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
557static 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
590problem:
591	return os_snprintf(buf, buflen, "FAIL\n");
592}
593
594
595/* fst list_ifaces */
596static 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 */
629static 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
648static 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
660static 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
694static void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i,
695						  Boolean 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
711static 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
718static 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
724static 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
734static 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
740const struct fst_ctrl *fst_ctrl_cli = &ctrl_cli;
741
742
743int 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 */
764int 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	Boolean 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
821int fst_read_next_int_param(const char *params, Boolean *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
839int 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
866int 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
885int 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	Boolean 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
925int 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
933int 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