xref: /freebsd/contrib/wpa/src/fst/fst_ctrl_iface.c (revision ce6a89e27cd190313be39bb479880aeda4778436)
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 
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 */
35 static 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 		/* 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 
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 */
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 */
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 */
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 
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 */
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 */
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 */
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 */
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 */
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 
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 
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 */
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 
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 */
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 */
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 
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 
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 
694 static 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 
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 
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 
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 
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 */
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 	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 
821 int 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 
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 
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 
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 	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 
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 
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