1289284Srpaulo/*
2289284Srpaulo * FST module - Control Interface implementation
3289284Srpaulo * Copyright (c) 2014, Qualcomm Atheros, Inc.
4289284Srpaulo *
5289284Srpaulo * This software may be distributed under the terms of the BSD license.
6289284Srpaulo * See README for more details.
7289284Srpaulo */
8289284Srpaulo
9289284Srpaulo#include "utils/includes.h"
10289284Srpaulo#include "utils/common.h"
11289284Srpaulo#include "common/defs.h"
12289284Srpaulo#include "list.h"
13289284Srpaulo#include "fst/fst.h"
14289284Srpaulo#include "fst/fst_internal.h"
15289284Srpaulo#include "fst_ctrl_defs.h"
16289284Srpaulo#include "fst_ctrl_iface.h"
17289284Srpaulo
18289284Srpaulo
19289284Srpaulostatic struct fst_group * get_fst_group_by_id(const char *id)
20289284Srpaulo{
21289284Srpaulo	struct fst_group *g;
22289284Srpaulo
23289284Srpaulo	foreach_fst_group(g) {
24289284Srpaulo		const char *group_id = fst_group_get_id(g);
25289284Srpaulo
26289284Srpaulo		if (os_strncmp(group_id, id, os_strlen(group_id)) == 0)
27289284Srpaulo			return g;
28289284Srpaulo	}
29289284Srpaulo
30289284Srpaulo	return NULL;
31289284Srpaulo}
32289284Srpaulo
33289284Srpaulo
34289284Srpaulo/* notifications */
35289284Srpaulostatic Boolean format_session_state_extra(const union fst_event_extra *extra,
36289284Srpaulo					  char *buffer, size_t size)
37289284Srpaulo{
38289284Srpaulo	int len;
39289284Srpaulo	char reject_str[32] = FST_CTRL_PVAL_NONE;
40289284Srpaulo	const char *initiator = FST_CTRL_PVAL_NONE;
41289284Srpaulo	const struct fst_event_extra_session_state *ss;
42289284Srpaulo
43289284Srpaulo	ss = &extra->session_state;
44289284Srpaulo	if (ss->new_state != FST_SESSION_STATE_INITIAL)
45289284Srpaulo		return TRUE;
46289284Srpaulo
47289284Srpaulo	switch (ss->extra.to_initial.reason) {
48289284Srpaulo	case REASON_REJECT:
49289284Srpaulo		if (ss->extra.to_initial.reject_code != WLAN_STATUS_SUCCESS)
50289284Srpaulo			os_snprintf(reject_str, sizeof(reject_str), "%u",
51289284Srpaulo				    ss->extra.to_initial.reject_code);
52346981Scy		/* fall through */
53289284Srpaulo	case REASON_TEARDOWN:
54289284Srpaulo	case REASON_SWITCH:
55289284Srpaulo		switch (ss->extra.to_initial.initiator) {
56289284Srpaulo		case FST_INITIATOR_LOCAL:
57289284Srpaulo			initiator = FST_CS_PVAL_INITIATOR_LOCAL;
58289284Srpaulo			break;
59289284Srpaulo		case FST_INITIATOR_REMOTE:
60289284Srpaulo			initiator = FST_CS_PVAL_INITIATOR_REMOTE;
61289284Srpaulo			break;
62289284Srpaulo		default:
63289284Srpaulo			break;
64289284Srpaulo		}
65289284Srpaulo		break;
66289284Srpaulo	default:
67289284Srpaulo		break;
68289284Srpaulo	}
69289284Srpaulo
70289284Srpaulo	len = os_snprintf(buffer, size,
71289284Srpaulo			  FST_CES_PNAME_REASON "=%s "
72289284Srpaulo			  FST_CES_PNAME_REJECT_CODE "=%s "
73289284Srpaulo			  FST_CES_PNAME_INITIATOR "=%s",
74289284Srpaulo			  fst_reason_name(ss->extra.to_initial.reason),
75289284Srpaulo			  reject_str, initiator);
76289284Srpaulo
77289284Srpaulo	return !os_snprintf_error(size, len);
78289284Srpaulo}
79289284Srpaulo
80289284Srpaulo
81289284Srpaulostatic void fst_ctrl_iface_notify(struct fst_iface *f, u32 session_id,
82289284Srpaulo				  enum fst_event_type event_type,
83289284Srpaulo				  const union fst_event_extra *extra)
84289284Srpaulo{
85289284Srpaulo	struct fst_group *g;
86289284Srpaulo	char extra_str[128] = "";
87289284Srpaulo	const struct fst_event_extra_session_state *ss;
88289284Srpaulo	const struct fst_event_extra_iface_state *is;
89289284Srpaulo	const struct fst_event_extra_peer_state *ps;
90289284Srpaulo
91289284Srpaulo	/*
92289284Srpaulo	 * FST can use any of interface objects as it only sends messages
93289284Srpaulo	 * on global Control Interface, so we just pick the 1st one.
94289284Srpaulo	 */
95289284Srpaulo
96289284Srpaulo	if (!f) {
97289284Srpaulo		foreach_fst_group(g) {
98289284Srpaulo			f = fst_group_first_iface(g);
99289284Srpaulo			if (f)
100289284Srpaulo				break;
101289284Srpaulo		}
102289284Srpaulo		if (!f)
103289284Srpaulo			return;
104289284Srpaulo	}
105289284Srpaulo
106289284Srpaulo	WPA_ASSERT(f->iface_obj.ctx);
107289284Srpaulo
108289284Srpaulo	switch (event_type) {
109289284Srpaulo	case EVENT_FST_IFACE_STATE_CHANGED:
110289284Srpaulo		if (!extra)
111289284Srpaulo			return;
112289284Srpaulo		is = &extra->iface_state;
113289284Srpaulo		wpa_msg_global_only(f->iface_obj.ctx, MSG_INFO,
114289284Srpaulo				    FST_CTRL_EVENT_IFACE " %s "
115289284Srpaulo				    FST_CEI_PNAME_IFNAME "=%s "
116289284Srpaulo				    FST_CEI_PNAME_GROUP "=%s",
117289284Srpaulo				    is->attached ? FST_CEI_PNAME_ATTACHED :
118289284Srpaulo				    FST_CEI_PNAME_DETACHED,
119289284Srpaulo				    is->ifname, is->group_id);
120289284Srpaulo		break;
121289284Srpaulo	case EVENT_PEER_STATE_CHANGED:
122289284Srpaulo		if (!extra)
123289284Srpaulo			return;
124289284Srpaulo		ps = &extra->peer_state;
125289284Srpaulo		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
126289284Srpaulo				    FST_CTRL_EVENT_PEER " %s "
127289284Srpaulo				    FST_CEP_PNAME_IFNAME "=%s "
128289284Srpaulo				    FST_CEP_PNAME_ADDR "=" MACSTR,
129289284Srpaulo				    ps->connected ? FST_CEP_PNAME_CONNECTED :
130289284Srpaulo				    FST_CEP_PNAME_DISCONNECTED,
131289284Srpaulo				    ps->ifname, MAC2STR(ps->addr));
132289284Srpaulo		break;
133289284Srpaulo	case EVENT_FST_SESSION_STATE_CHANGED:
134289284Srpaulo		if (!extra)
135289284Srpaulo			return;
136289284Srpaulo		if (!format_session_state_extra(extra, extra_str,
137289284Srpaulo						sizeof(extra_str))) {
138289284Srpaulo			fst_printf(MSG_ERROR,
139289284Srpaulo				   "CTRL: Cannot format STATE_CHANGE extra");
140289284Srpaulo			extra_str[0] = 0;
141289284Srpaulo		}
142289284Srpaulo		ss = &extra->session_state;
143289284Srpaulo		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
144289284Srpaulo				    FST_CTRL_EVENT_SESSION " "
145289284Srpaulo				    FST_CES_PNAME_SESSION_ID "=%u "
146289284Srpaulo				    FST_CES_PNAME_EVT_TYPE "=%s "
147289284Srpaulo				    FST_CES_PNAME_OLD_STATE "=%s "
148289284Srpaulo				    FST_CES_PNAME_NEW_STATE "=%s %s",
149289284Srpaulo				    session_id,
150289284Srpaulo				    fst_session_event_type_name(event_type),
151289284Srpaulo				    fst_session_state_name(ss->old_state),
152289284Srpaulo				    fst_session_state_name(ss->new_state),
153289284Srpaulo				    extra_str);
154289284Srpaulo		break;
155289284Srpaulo	case EVENT_FST_ESTABLISHED:
156289284Srpaulo	case EVENT_FST_SETUP:
157289284Srpaulo		wpa_msg_global_only(fst_iface_get_wpa_obj_ctx(f), MSG_INFO,
158289284Srpaulo				    FST_CTRL_EVENT_SESSION " "
159289284Srpaulo				    FST_CES_PNAME_SESSION_ID "=%u "
160289284Srpaulo				    FST_CES_PNAME_EVT_TYPE "=%s",
161289284Srpaulo				    session_id,
162289284Srpaulo				    fst_session_event_type_name(event_type));
163289284Srpaulo		break;
164289284Srpaulo	}
165289284Srpaulo}
166289284Srpaulo
167289284Srpaulo
168289284Srpaulo/* command processors */
169289284Srpaulo
170289284Srpaulo/* fst session_get */
171289284Srpaulostatic int session_get(const char *session_id, char *buf, size_t buflen)
172289284Srpaulo{
173289284Srpaulo	struct fst_session *s;
174289284Srpaulo	struct fst_iface *new_iface, *old_iface;
175289284Srpaulo	const u8 *old_peer_addr, *new_peer_addr;
176289284Srpaulo	u32 id;
177289284Srpaulo
178289284Srpaulo	id = strtoul(session_id, NULL, 0);
179289284Srpaulo
180289284Srpaulo	s = fst_session_get_by_id(id);
181289284Srpaulo	if (!s) {
182289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
183289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
184289284Srpaulo	}
185289284Srpaulo
186289284Srpaulo	old_peer_addr = fst_session_get_peer_addr(s, TRUE);
187289284Srpaulo	new_peer_addr = fst_session_get_peer_addr(s, FALSE);
188289284Srpaulo	new_iface = fst_session_get_iface(s, FALSE);
189289284Srpaulo	old_iface = fst_session_get_iface(s, TRUE);
190289284Srpaulo
191289284Srpaulo	return os_snprintf(buf, buflen,
192289284Srpaulo			   FST_CSG_PNAME_OLD_PEER_ADDR "=" MACSTR "\n"
193289284Srpaulo			   FST_CSG_PNAME_NEW_PEER_ADDR "=" MACSTR "\n"
194289284Srpaulo			   FST_CSG_PNAME_NEW_IFNAME "=%s\n"
195289284Srpaulo			   FST_CSG_PNAME_OLD_IFNAME "=%s\n"
196289284Srpaulo			   FST_CSG_PNAME_LLT "=%u\n"
197289284Srpaulo			   FST_CSG_PNAME_STATE "=%s\n",
198289284Srpaulo			   MAC2STR(old_peer_addr),
199289284Srpaulo			   MAC2STR(new_peer_addr),
200289284Srpaulo			   new_iface ? fst_iface_get_name(new_iface) :
201289284Srpaulo			   FST_CTRL_PVAL_NONE,
202289284Srpaulo			   old_iface ? fst_iface_get_name(old_iface) :
203289284Srpaulo			   FST_CTRL_PVAL_NONE,
204289284Srpaulo			   fst_session_get_llt(s),
205289284Srpaulo			   fst_session_state_name(fst_session_get_state(s)));
206289284Srpaulo}
207289284Srpaulo
208289284Srpaulo
209289284Srpaulo/* fst session_set */
210289284Srpaulostatic int session_set(const char *session_id, char *buf, size_t buflen)
211289284Srpaulo{
212289284Srpaulo	struct fst_session *s;
213289284Srpaulo	char *p, *q;
214289284Srpaulo	u32 id;
215289284Srpaulo	int ret;
216289284Srpaulo
217289284Srpaulo	id = strtoul(session_id, &p, 0);
218289284Srpaulo
219289284Srpaulo	s = fst_session_get_by_id(id);
220289284Srpaulo	if (!s) {
221289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
222289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
223289284Srpaulo	}
224289284Srpaulo
225289284Srpaulo	if (*p != ' ' || !(q = os_strchr(p + 1, '=')))
226289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
227289284Srpaulo	p++;
228289284Srpaulo
229289284Srpaulo	if (os_strncasecmp(p, FST_CSS_PNAME_OLD_IFNAME, q - p) == 0) {
230289284Srpaulo		ret = fst_session_set_str_ifname(s, q + 1, TRUE);
231289284Srpaulo	} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_IFNAME, q - p) == 0) {
232289284Srpaulo		ret = fst_session_set_str_ifname(s, q + 1, FALSE);
233289284Srpaulo	} else if (os_strncasecmp(p, FST_CSS_PNAME_OLD_PEER_ADDR, q - p) == 0) {
234289284Srpaulo		ret = fst_session_set_str_peer_addr(s, q + 1, TRUE);
235289284Srpaulo	} else if (os_strncasecmp(p, FST_CSS_PNAME_NEW_PEER_ADDR, q - p) == 0) {
236289284Srpaulo		ret = fst_session_set_str_peer_addr(s, q + 1, FALSE);
237289284Srpaulo	} else if (os_strncasecmp(p, FST_CSS_PNAME_LLT, q - p) == 0) {
238289284Srpaulo		ret = fst_session_set_str_llt(s, q + 1);
239289284Srpaulo	} else {
240289284Srpaulo		fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
241289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
242289284Srpaulo	}
243289284Srpaulo
244289284Srpaulo	return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
245289284Srpaulo}
246289284Srpaulo
247289284Srpaulo
248289284Srpaulo/* fst session_add/remove */
249289284Srpaulostatic int session_add(const char *group_id, char *buf, size_t buflen)
250289284Srpaulo{
251289284Srpaulo	struct fst_group *g;
252289284Srpaulo	struct fst_session *s;
253289284Srpaulo
254289284Srpaulo	g = get_fst_group_by_id(group_id);
255289284Srpaulo	if (!g) {
256289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
257289284Srpaulo			   group_id);
258289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
259289284Srpaulo	}
260289284Srpaulo
261289284Srpaulo	s = fst_session_create(g);
262289284Srpaulo	if (!s) {
263289284Srpaulo		fst_printf(MSG_ERROR,
264289284Srpaulo			   "CTRL: Cannot create session for group '%s'",
265289284Srpaulo			   group_id);
266289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
267289284Srpaulo	}
268289284Srpaulo
269289284Srpaulo	return os_snprintf(buf, buflen, "%u\n", fst_session_get_id(s));
270289284Srpaulo}
271289284Srpaulo
272289284Srpaulo
273289284Srpaulostatic int session_remove(const char *session_id, char *buf, size_t buflen)
274289284Srpaulo{
275289284Srpaulo	struct fst_session *s;
276289284Srpaulo	struct fst_group *g;
277289284Srpaulo	u32 id;
278289284Srpaulo
279289284Srpaulo	id = strtoul(session_id, NULL, 0);
280289284Srpaulo
281289284Srpaulo	s = fst_session_get_by_id(id);
282289284Srpaulo	if (!s) {
283289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
284289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
285289284Srpaulo	}
286289284Srpaulo
287289284Srpaulo	g = fst_session_get_group(s);
288289284Srpaulo	fst_session_reset(s);
289289284Srpaulo	fst_session_delete(s);
290289284Srpaulo	fst_group_delete_if_empty(g);
291289284Srpaulo
292289284Srpaulo	return os_snprintf(buf, buflen, "OK\n");
293289284Srpaulo}
294289284Srpaulo
295289284Srpaulo
296289284Srpaulo/* fst session_initiate */
297289284Srpaulostatic int session_initiate(const char *session_id, char *buf, size_t buflen)
298289284Srpaulo{
299289284Srpaulo	struct fst_session *s;
300289284Srpaulo	u32 id;
301289284Srpaulo
302289284Srpaulo	id = strtoul(session_id, NULL, 0);
303289284Srpaulo
304289284Srpaulo	s = fst_session_get_by_id(id);
305289284Srpaulo	if (!s) {
306289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
307289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
308289284Srpaulo	}
309289284Srpaulo
310289284Srpaulo	if (fst_session_initiate_setup(s)) {
311289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot initiate session %u", id);
312289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
313289284Srpaulo	}
314289284Srpaulo
315289284Srpaulo	return os_snprintf(buf, buflen, "OK\n");
316289284Srpaulo}
317289284Srpaulo
318289284Srpaulo
319289284Srpaulo/* fst session_respond */
320289284Srpaulostatic int session_respond(const char *session_id, char *buf, size_t buflen)
321289284Srpaulo{
322289284Srpaulo	struct fst_session *s;
323289284Srpaulo	char *p;
324289284Srpaulo	u32 id;
325289284Srpaulo	u8 status_code;
326289284Srpaulo
327289284Srpaulo	id = strtoul(session_id, &p, 0);
328289284Srpaulo
329289284Srpaulo	s = fst_session_get_by_id(id);
330289284Srpaulo	if (!s) {
331289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
332289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
333289284Srpaulo	}
334289284Srpaulo
335289284Srpaulo	if (*p != ' ')
336289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
337289284Srpaulo	p++;
338289284Srpaulo
339289284Srpaulo	if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_ACCEPT)) {
340289284Srpaulo		status_code = WLAN_STATUS_SUCCESS;
341289284Srpaulo	} else if (!os_strcasecmp(p, FST_CS_PVAL_RESPONSE_REJECT)) {
342289284Srpaulo		status_code = WLAN_STATUS_PENDING_ADMITTING_FST_SESSION;
343289284Srpaulo	} else {
344289284Srpaulo		fst_printf(MSG_WARNING,
345289284Srpaulo			   "CTRL: session %u: unknown response status: %s",
346289284Srpaulo			   id, p);
347289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
348289284Srpaulo	}
349289284Srpaulo
350289284Srpaulo	if (fst_session_respond(s, status_code)) {
351289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot respond to session %u",
352289284Srpaulo			   id);
353289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
354289284Srpaulo	}
355289284Srpaulo
356289284Srpaulo	fst_printf(MSG_INFO, "CTRL: session %u responded", id);
357289284Srpaulo
358289284Srpaulo	return os_snprintf(buf, buflen, "OK\n");
359289284Srpaulo}
360289284Srpaulo
361289284Srpaulo
362289284Srpaulo/* fst session_transfer */
363289284Srpaulostatic int session_transfer(const char *session_id, char *buf, size_t buflen)
364289284Srpaulo{
365289284Srpaulo	struct fst_session *s;
366289284Srpaulo	u32 id;
367289284Srpaulo
368289284Srpaulo	id = strtoul(session_id, NULL, 0);
369289284Srpaulo
370289284Srpaulo	s = fst_session_get_by_id(id);
371289284Srpaulo	if (!s) {
372289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
373289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
374289284Srpaulo	}
375289284Srpaulo
376289284Srpaulo	if (fst_session_initiate_switch(s)) {
377289284Srpaulo		fst_printf(MSG_WARNING,
378289284Srpaulo			   "CTRL: Cannot initiate ST for session %u", id);
379289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
380289284Srpaulo	}
381289284Srpaulo
382289284Srpaulo	return os_snprintf(buf, buflen, "OK\n");
383289284Srpaulo}
384289284Srpaulo
385289284Srpaulo
386289284Srpaulo/* fst session_teardown */
387289284Srpaulostatic int session_teardown(const char *session_id, char *buf, size_t buflen)
388289284Srpaulo{
389289284Srpaulo	struct fst_session *s;
390289284Srpaulo	u32 id;
391289284Srpaulo
392289284Srpaulo	id = strtoul(session_id, NULL, 0);
393289284Srpaulo
394289284Srpaulo	s = fst_session_get_by_id(id);
395289284Srpaulo	if (!s) {
396289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find session %u", id);
397289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
398289284Srpaulo	}
399289284Srpaulo
400289284Srpaulo	if (fst_session_tear_down_setup(s)) {
401289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot tear down session %u",
402289284Srpaulo			   id);
403289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
404289284Srpaulo	}
405289284Srpaulo
406289284Srpaulo	return os_snprintf(buf, buflen, "OK\n");
407289284Srpaulo}
408289284Srpaulo
409289284Srpaulo
410289284Srpaulo#ifdef CONFIG_FST_TEST
411289284Srpaulo/* fst test_request */
412289284Srpaulostatic int test_request(const char *request, char *buf, size_t buflen)
413289284Srpaulo{
414289284Srpaulo	const char *p = request;
415289284Srpaulo	int ret;
416289284Srpaulo
417289284Srpaulo	if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_REQUEST,
418289284Srpaulo			    os_strlen(FST_CTR_SEND_SETUP_REQUEST))) {
419289284Srpaulo		ret = fst_test_req_send_fst_request(
420289284Srpaulo			p + os_strlen(FST_CTR_SEND_SETUP_REQUEST));
421289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_SEND_SETUP_RESPONSE,
422289284Srpaulo				   os_strlen(FST_CTR_SEND_SETUP_RESPONSE))) {
423289284Srpaulo		ret = fst_test_req_send_fst_response(
424289284Srpaulo			p + os_strlen(FST_CTR_SEND_SETUP_RESPONSE));
425289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_REQUEST,
426289284Srpaulo				   os_strlen(FST_CTR_SEND_ACK_REQUEST))) {
427289284Srpaulo		ret = fst_test_req_send_ack_request(
428289284Srpaulo			p + os_strlen(FST_CTR_SEND_ACK_REQUEST));
429289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_SEND_ACK_RESPONSE,
430289284Srpaulo				   os_strlen(FST_CTR_SEND_ACK_RESPONSE))) {
431289284Srpaulo		ret = fst_test_req_send_ack_response(
432289284Srpaulo			p + os_strlen(FST_CTR_SEND_ACK_RESPONSE));
433289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_SEND_TEAR_DOWN,
434289284Srpaulo				   os_strlen(FST_CTR_SEND_TEAR_DOWN))) {
435289284Srpaulo		ret = fst_test_req_send_tear_down(
436289284Srpaulo			p + os_strlen(FST_CTR_SEND_TEAR_DOWN));
437289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_GET_FSTS_ID,
438289284Srpaulo				   os_strlen(FST_CTR_GET_FSTS_ID))) {
439289284Srpaulo		u32 fsts_id = fst_test_req_get_fsts_id(
440289284Srpaulo			p + os_strlen(FST_CTR_GET_FSTS_ID));
441289284Srpaulo		if (fsts_id != FST_FSTS_ID_NOT_FOUND)
442289284Srpaulo			return os_snprintf(buf, buflen, "%u\n", fsts_id);
443289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
444289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_GET_LOCAL_MBIES,
445289284Srpaulo				   os_strlen(FST_CTR_GET_LOCAL_MBIES))) {
446289284Srpaulo		return fst_test_req_get_local_mbies(
447289284Srpaulo			p + os_strlen(FST_CTR_GET_LOCAL_MBIES), buf, buflen);
448289284Srpaulo	} else if (!os_strncasecmp(p, FST_CTR_IS_SUPPORTED,
449289284Srpaulo				   os_strlen(FST_CTR_IS_SUPPORTED))) {
450289284Srpaulo		ret = 0;
451289284Srpaulo	} else {
452289284Srpaulo		fst_printf(MSG_ERROR, "CTRL: Unknown parameter: %s", p);
453289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
454289284Srpaulo	}
455289284Srpaulo
456289284Srpaulo	return os_snprintf(buf, buflen, "%s\n", ret ? "FAIL" : "OK");
457289284Srpaulo}
458289284Srpaulo#endif /* CONFIG_FST_TEST */
459289284Srpaulo
460289284Srpaulo
461289284Srpaulo/* fst list_sessions */
462289284Srpaulostruct list_sessions_cb_ctx {
463289284Srpaulo	char *buf;
464289284Srpaulo	size_t buflen;
465289284Srpaulo	size_t reply_len;
466289284Srpaulo};
467289284Srpaulo
468289284Srpaulo
469289284Srpaulostatic void list_session_enum_cb(struct fst_group *g, struct fst_session *s,
470289284Srpaulo				 void *ctx)
471289284Srpaulo{
472289284Srpaulo	struct list_sessions_cb_ctx *c = ctx;
473289284Srpaulo	int ret;
474289284Srpaulo
475289284Srpaulo	ret = os_snprintf(c->buf, c->buflen, " %u", fst_session_get_id(s));
476289284Srpaulo
477289284Srpaulo	c->buf += ret;
478289284Srpaulo	c->buflen -= ret;
479289284Srpaulo	c->reply_len += ret;
480289284Srpaulo}
481289284Srpaulo
482289284Srpaulo
483289284Srpaulostatic int list_sessions(const char *group_id, char *buf, size_t buflen)
484289284Srpaulo{
485289284Srpaulo	struct list_sessions_cb_ctx ctx;
486289284Srpaulo	struct fst_group *g;
487289284Srpaulo
488289284Srpaulo	g = get_fst_group_by_id(group_id);
489289284Srpaulo	if (!g) {
490289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
491289284Srpaulo			   group_id);
492289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
493289284Srpaulo	}
494289284Srpaulo
495289284Srpaulo	ctx.buf = buf;
496289284Srpaulo	ctx.buflen = buflen;
497289284Srpaulo	ctx.reply_len = 0;
498289284Srpaulo
499289284Srpaulo	fst_session_enum(g, list_session_enum_cb, &ctx);
500289284Srpaulo
501289284Srpaulo	ctx.reply_len += os_snprintf(buf + ctx.reply_len, ctx.buflen, "\n");
502289284Srpaulo
503289284Srpaulo	return ctx.reply_len;
504289284Srpaulo}
505289284Srpaulo
506289284Srpaulo
507289284Srpaulo/* fst iface_peers */
508289284Srpaulostatic int iface_peers(const char *group_id, char *buf, size_t buflen)
509289284Srpaulo{
510289284Srpaulo	const char *ifname;
511289284Srpaulo	struct fst_group *g;
512289284Srpaulo	struct fst_iface *f;
513289284Srpaulo	struct fst_get_peer_ctx *ctx;
514289284Srpaulo	const u8 *addr;
515289284Srpaulo	unsigned found = 0;
516289284Srpaulo	int ret = 0;
517289284Srpaulo
518289284Srpaulo	g = get_fst_group_by_id(group_id);
519289284Srpaulo	if (!g) {
520289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
521289284Srpaulo			   group_id);
522289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
523289284Srpaulo	}
524289284Srpaulo
525289284Srpaulo	ifname = os_strchr(group_id, ' ');
526289284Srpaulo	if (!ifname)
527289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
528289284Srpaulo	ifname++;
529289284Srpaulo
530289284Srpaulo	foreach_fst_group_iface(g, f) {
531289284Srpaulo		const char *in = fst_iface_get_name(f);
532289284Srpaulo
533289284Srpaulo		if (os_strncmp(ifname, in, os_strlen(in)) == 0) {
534289284Srpaulo			found = 1;
535289284Srpaulo			break;
536289284Srpaulo		}
537289284Srpaulo	}
538289284Srpaulo
539289284Srpaulo	if (!found)
540289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
541289284Srpaulo
542289284Srpaulo	addr = fst_iface_get_peer_first(f, &ctx, FALSE);
543289284Srpaulo	for (; addr != NULL; addr = fst_iface_get_peer_next(f, &ctx, FALSE)) {
544289284Srpaulo		int res;
545289284Srpaulo
546289284Srpaulo		res = os_snprintf(buf + ret, buflen - ret, MACSTR "\n",
547289284Srpaulo				  MAC2STR(addr));
548289284Srpaulo		if (os_snprintf_error(buflen - ret, res))
549289284Srpaulo			break;
550289284Srpaulo		ret += res;
551289284Srpaulo	}
552289284Srpaulo
553289284Srpaulo	return ret;
554289284Srpaulo}
555289284Srpaulo
556289284Srpaulo
557289284Srpaulostatic int get_peer_mbies(const char *params, char *buf, size_t buflen)
558289284Srpaulo{
559289284Srpaulo	char *endp;
560289284Srpaulo	char ifname[FST_MAX_INTERFACE_SIZE];
561289284Srpaulo	u8 peer_addr[ETH_ALEN];
562289284Srpaulo	struct fst_group *g;
563289284Srpaulo	struct fst_iface *iface = NULL;
564289284Srpaulo	const struct wpabuf *mbies;
565289284Srpaulo
566289284Srpaulo	if (fst_read_next_text_param(params, ifname, sizeof(ifname), &endp) ||
567289284Srpaulo	    !*ifname)
568289284Srpaulo		goto problem;
569289284Srpaulo
570289284Srpaulo	while (isspace(*endp))
571289284Srpaulo		endp++;
572289284Srpaulo	if (fst_read_peer_addr(endp, peer_addr))
573289284Srpaulo		goto problem;
574289284Srpaulo
575289284Srpaulo	foreach_fst_group(g) {
576289284Srpaulo		iface = fst_group_get_iface_by_name(g, ifname);
577289284Srpaulo		if (iface)
578289284Srpaulo			break;
579289284Srpaulo	}
580289284Srpaulo	if (!iface)
581289284Srpaulo		goto problem;
582289284Srpaulo
583289284Srpaulo	mbies = fst_iface_get_peer_mb_ie(iface, peer_addr);
584289284Srpaulo	if (!mbies)
585289284Srpaulo		goto problem;
586289284Srpaulo
587289284Srpaulo	return wpa_snprintf_hex(buf, buflen, wpabuf_head(mbies),
588289284Srpaulo				wpabuf_len(mbies));
589289284Srpaulo
590289284Srpauloproblem:
591289284Srpaulo	return os_snprintf(buf, buflen, "FAIL\n");
592289284Srpaulo}
593289284Srpaulo
594289284Srpaulo
595289284Srpaulo/* fst list_ifaces */
596289284Srpaulostatic int list_ifaces(const char *group_id, char *buf, size_t buflen)
597289284Srpaulo{
598289284Srpaulo	struct fst_group *g;
599289284Srpaulo	struct fst_iface *f;
600289284Srpaulo	int ret = 0;
601289284Srpaulo
602289284Srpaulo	g = get_fst_group_by_id(group_id);
603289284Srpaulo	if (!g) {
604289284Srpaulo		fst_printf(MSG_WARNING, "CTRL: Cannot find group '%s'",
605289284Srpaulo			   group_id);
606289284Srpaulo		return os_snprintf(buf, buflen, "FAIL\n");
607289284Srpaulo	}
608289284Srpaulo
609289284Srpaulo	foreach_fst_group_iface(g, f) {
610289284Srpaulo		int res;
611289284Srpaulo		const u8 *iface_addr = fst_iface_get_addr(f);
612289284Srpaulo
613289284Srpaulo		res = os_snprintf(buf + ret, buflen - ret,
614289284Srpaulo				  "%s|" MACSTR "|%u|%u\n",
615289284Srpaulo				  fst_iface_get_name(f),
616289284Srpaulo				  MAC2STR(iface_addr),
617289284Srpaulo				  fst_iface_get_priority(f),
618289284Srpaulo				  fst_iface_get_llt(f));
619289284Srpaulo		if (os_snprintf_error(buflen - ret, res))
620289284Srpaulo			break;
621289284Srpaulo		ret += res;
622289284Srpaulo	}
623289284Srpaulo
624289284Srpaulo	return ret;
625289284Srpaulo}
626289284Srpaulo
627289284Srpaulo
628289284Srpaulo/* fst list_groups */
629289284Srpaulostatic int list_groups(const char *cmd, char *buf, size_t buflen)
630289284Srpaulo{
631289284Srpaulo	struct fst_group *g;
632289284Srpaulo	int ret = 0;
633289284Srpaulo
634289284Srpaulo	foreach_fst_group(g) {
635289284Srpaulo		int res;
636289284Srpaulo
637289284Srpaulo		res = os_snprintf(buf + ret, buflen - ret, "%s\n",
638289284Srpaulo				  fst_group_get_id(g));
639289284Srpaulo		if (os_snprintf_error(buflen - ret, res))
640289284Srpaulo			break;
641289284Srpaulo		ret += res;
642289284Srpaulo	}
643289284Srpaulo
644289284Srpaulo	return ret;
645289284Srpaulo}
646289284Srpaulo
647289284Srpaulo
648289284Srpaulostatic const char * band_freq(enum mb_band_id band)
649289284Srpaulo{
650289284Srpaulo	static const char *band_names[] = {
651337817Scy		[MB_BAND_ID_WIFI_2_4GHZ] = "2.4GHZ",
652337817Scy		[MB_BAND_ID_WIFI_5GHZ] = "5GHZ",
653337817Scy		[MB_BAND_ID_WIFI_60GHZ] = "60GHZ",
654289284Srpaulo	};
655289284Srpaulo
656289284Srpaulo	return fst_get_str_name(band, band_names, ARRAY_SIZE(band_names));
657289284Srpaulo}
658289284Srpaulo
659289284Srpaulo
660289284Srpaulostatic int print_band(unsigned num, struct fst_iface *iface, const u8 *addr,
661289284Srpaulo		      char *buf, size_t buflen)
662289284Srpaulo{
663289284Srpaulo	const struct wpabuf *wpabuf;
664289284Srpaulo	enum hostapd_hw_mode hw_mode;
665289284Srpaulo	u8 channel;
666289284Srpaulo	int ret = 0;
667289284Srpaulo
668289284Srpaulo	fst_iface_get_channel_info(iface, &hw_mode, &channel);
669289284Srpaulo
670289284Srpaulo	ret += os_snprintf(buf + ret, buflen - ret, "band%u_frequency=%s\n",
671289284Srpaulo			   num, band_freq(fst_hw_mode_to_band(hw_mode)));
672289284Srpaulo	ret += os_snprintf(buf + ret, buflen - ret, "band%u_iface=%s\n",
673289284Srpaulo			   num, fst_iface_get_name(iface));
674289284Srpaulo	wpabuf = fst_iface_get_peer_mb_ie(iface, addr);
675289284Srpaulo	if (wpabuf) {
676289284Srpaulo		ret += os_snprintf(buf + ret, buflen - ret, "band%u_mb_ies=",
677289284Srpaulo				   num);
678289284Srpaulo		ret += wpa_snprintf_hex(buf + ret, buflen - ret,
679289284Srpaulo					wpabuf_head(wpabuf),
680289284Srpaulo					wpabuf_len(wpabuf));
681289284Srpaulo		ret += os_snprintf(buf + ret, buflen - ret, "\n");
682289284Srpaulo	}
683289284Srpaulo	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_group_id=%s\n",
684289284Srpaulo			   num, fst_iface_get_group_id(iface));
685289284Srpaulo	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_priority=%u\n",
686289284Srpaulo			   num, fst_iface_get_priority(iface));
687289284Srpaulo	ret += os_snprintf(buf + ret, buflen - ret, "band%u_fst_llt=%u\n",
688289284Srpaulo			   num, fst_iface_get_llt(iface));
689289284Srpaulo
690289284Srpaulo	return ret;
691289284Srpaulo}
692289284Srpaulo
693289284Srpaulo
694289284Srpaulostatic void fst_ctrl_iface_on_iface_state_changed(struct fst_iface *i,
695289284Srpaulo						  Boolean attached)
696289284Srpaulo{
697289284Srpaulo	union fst_event_extra extra;
698289284Srpaulo
699289284Srpaulo	os_memset(&extra, 0, sizeof(extra));
700289284Srpaulo	extra.iface_state.attached = attached;
701289284Srpaulo	os_strlcpy(extra.iface_state.ifname, fst_iface_get_name(i),
702289284Srpaulo		   sizeof(extra.iface_state.ifname));
703289284Srpaulo	os_strlcpy(extra.iface_state.group_id, fst_iface_get_group_id(i),
704289284Srpaulo		   sizeof(extra.iface_state.group_id));
705289284Srpaulo
706289284Srpaulo	fst_ctrl_iface_notify(i, FST_INVALID_SESSION_ID,
707289284Srpaulo			      EVENT_FST_IFACE_STATE_CHANGED, &extra);
708289284Srpaulo}
709289284Srpaulo
710289284Srpaulo
711289284Srpaulostatic int fst_ctrl_iface_on_iface_added(struct fst_iface *i)
712289284Srpaulo{
713289284Srpaulo	fst_ctrl_iface_on_iface_state_changed(i, TRUE);
714289284Srpaulo	return 0;
715289284Srpaulo}
716289284Srpaulo
717289284Srpaulo
718289284Srpaulostatic void fst_ctrl_iface_on_iface_removed(struct fst_iface *i)
719289284Srpaulo{
720289284Srpaulo	fst_ctrl_iface_on_iface_state_changed(i, FALSE);
721289284Srpaulo}
722289284Srpaulo
723289284Srpaulo
724289284Srpaulostatic void fst_ctrl_iface_on_event(enum fst_event_type event_type,
725289284Srpaulo				    struct fst_iface *i, struct fst_session *s,
726289284Srpaulo				    const union fst_event_extra *extra)
727289284Srpaulo{
728289284Srpaulo	u32 session_id = s ? fst_session_get_id(s) : FST_INVALID_SESSION_ID;
729289284Srpaulo
730289284Srpaulo	fst_ctrl_iface_notify(i, session_id, event_type, extra);
731289284Srpaulo}
732289284Srpaulo
733289284Srpaulo
734289284Srpaulostatic const struct fst_ctrl ctrl_cli = {
735289284Srpaulo	.on_iface_added = fst_ctrl_iface_on_iface_added,
736289284Srpaulo	.on_iface_removed =  fst_ctrl_iface_on_iface_removed,
737289284Srpaulo	.on_event = fst_ctrl_iface_on_event,
738289284Srpaulo};
739289284Srpaulo
740289284Srpauloconst struct fst_ctrl *fst_ctrl_cli = &ctrl_cli;
741289284Srpaulo
742289284Srpaulo
743289284Srpauloint fst_ctrl_iface_mb_info(const u8 *addr, char *buf, size_t buflen)
744289284Srpaulo{
745289284Srpaulo	struct fst_group *g;
746289284Srpaulo	struct fst_iface *f;
747289284Srpaulo	unsigned num = 0;
748289284Srpaulo	int ret = 0;
749289284Srpaulo
750289284Srpaulo	foreach_fst_group(g) {
751289284Srpaulo		foreach_fst_group_iface(g, f) {
752337817Scy			if (fst_iface_is_connected(f, addr, TRUE)) {
753289284Srpaulo				ret += print_band(num++, f, addr,
754289284Srpaulo						  buf + ret, buflen - ret);
755289284Srpaulo			}
756289284Srpaulo		}
757289284Srpaulo	}
758289284Srpaulo
759289284Srpaulo	return ret;
760289284Srpaulo}
761289284Srpaulo
762289284Srpaulo
763289284Srpaulo/* fst ctrl processor */
764289284Srpauloint fst_ctrl_iface_receive(const char *cmd, char *reply, size_t reply_size)
765289284Srpaulo{
766289284Srpaulo	static const struct fst_command {
767289284Srpaulo		const char *name;
768289284Srpaulo		unsigned has_param;
769289284Srpaulo		int (*process)(const char *group_id, char *buf, size_t buflen);
770289284Srpaulo	} commands[] = {
771289284Srpaulo		{ FST_CMD_LIST_GROUPS, 0, list_groups},
772289284Srpaulo		{ FST_CMD_LIST_IFACES, 1, list_ifaces},
773289284Srpaulo		{ FST_CMD_IFACE_PEERS, 1, iface_peers},
774289284Srpaulo		{ FST_CMD_GET_PEER_MBIES, 1, get_peer_mbies},
775289284Srpaulo		{ FST_CMD_LIST_SESSIONS, 1, list_sessions},
776289284Srpaulo		{ FST_CMD_SESSION_ADD, 1, session_add},
777289284Srpaulo		{ FST_CMD_SESSION_REMOVE, 1, session_remove},
778289284Srpaulo		{ FST_CMD_SESSION_GET, 1, session_get},
779289284Srpaulo		{ FST_CMD_SESSION_SET, 1, session_set},
780289284Srpaulo		{ FST_CMD_SESSION_INITIATE, 1, session_initiate},
781289284Srpaulo		{ FST_CMD_SESSION_RESPOND, 1, session_respond},
782289284Srpaulo		{ FST_CMD_SESSION_TRANSFER, 1, session_transfer},
783289284Srpaulo		{ FST_CMD_SESSION_TEARDOWN, 1, session_teardown},
784289284Srpaulo#ifdef CONFIG_FST_TEST
785289284Srpaulo		{ FST_CMD_TEST_REQUEST, 1, test_request },
786289284Srpaulo#endif /* CONFIG_FST_TEST */
787289284Srpaulo		{ NULL, 0, NULL }
788289284Srpaulo	};
789289284Srpaulo	const struct fst_command *c;
790289284Srpaulo	const char *p;
791289284Srpaulo	const char *temp;
792289284Srpaulo	Boolean non_spaces_found;
793289284Srpaulo
794289284Srpaulo	for (c = commands; c->name; c++) {
795289284Srpaulo		if (os_strncasecmp(cmd, c->name, os_strlen(c->name)) != 0)
796289284Srpaulo			continue;
797289284Srpaulo		p = cmd + os_strlen(c->name);
798289284Srpaulo		if (c->has_param) {
799289284Srpaulo			if (!isspace(p[0]))
800289284Srpaulo				return os_snprintf(reply, reply_size, "FAIL\n");
801289284Srpaulo			p++;
802289284Srpaulo			temp = p;
803289284Srpaulo			non_spaces_found = FALSE;
804289284Srpaulo			while (*temp) {
805289284Srpaulo				if (!isspace(*temp)) {
806289284Srpaulo					non_spaces_found = TRUE;
807289284Srpaulo					break;
808289284Srpaulo				}
809289284Srpaulo				temp++;
810289284Srpaulo			}
811289284Srpaulo			if (!non_spaces_found)
812289284Srpaulo				return os_snprintf(reply, reply_size, "FAIL\n");
813289284Srpaulo		}
814289284Srpaulo		return c->process(p, reply, reply_size);
815289284Srpaulo	}
816289284Srpaulo
817289284Srpaulo	return os_snprintf(reply, reply_size, "UNKNOWN FST COMMAND\n");
818289284Srpaulo}
819289284Srpaulo
820289284Srpaulo
821289284Srpauloint fst_read_next_int_param(const char *params, Boolean *valid, char **endp)
822289284Srpaulo{
823289284Srpaulo	int ret = -1;
824289284Srpaulo	const char *curp;
825289284Srpaulo
826289284Srpaulo	*valid = FALSE;
827289284Srpaulo	*endp = (char *) params;
828289284Srpaulo	curp = params;
829289284Srpaulo	if (*curp) {
830289284Srpaulo		ret = (int) strtol(curp, endp, 0);
831289284Srpaulo		if (!**endp || isspace(**endp))
832289284Srpaulo			*valid = TRUE;
833289284Srpaulo	}
834289284Srpaulo
835289284Srpaulo	return ret;
836289284Srpaulo}
837289284Srpaulo
838289284Srpaulo
839289284Srpauloint fst_read_next_text_param(const char *params, char *buf, size_t buflen,
840289284Srpaulo			     char **endp)
841289284Srpaulo{
842289284Srpaulo	size_t max_chars_to_copy;
843289284Srpaulo	char *cur_dest;
844289284Srpaulo
845289284Srpaulo	*endp = (char *) params;
846289284Srpaulo	while (isspace(**endp))
847289284Srpaulo		(*endp)++;
848289284Srpaulo	if (!**endp || buflen <= 1)
849289284Srpaulo		return -EINVAL;
850289284Srpaulo
851289284Srpaulo	max_chars_to_copy = buflen - 1;
852289284Srpaulo	/* We need 1 byte for the terminating zero */
853289284Srpaulo	cur_dest = buf;
854289284Srpaulo	while (**endp && !isspace(**endp) && max_chars_to_copy > 0) {
855289284Srpaulo		*cur_dest = **endp;
856289284Srpaulo		(*endp)++;
857289284Srpaulo		cur_dest++;
858289284Srpaulo		max_chars_to_copy--;
859289284Srpaulo	}
860289284Srpaulo	*cur_dest = 0;
861289284Srpaulo
862289284Srpaulo	return 0;
863289284Srpaulo}
864289284Srpaulo
865289284Srpaulo
866289284Srpauloint fst_read_peer_addr(const char *mac, u8 *peer_addr)
867289284Srpaulo{
868289284Srpaulo	if (hwaddr_aton(mac, peer_addr)) {
869289284Srpaulo		fst_printf(MSG_WARNING, "Bad peer_mac %s: invalid addr string",
870289284Srpaulo			   mac);
871289284Srpaulo		return -1;
872289284Srpaulo	}
873289284Srpaulo
874289284Srpaulo	if (is_zero_ether_addr(peer_addr) ||
875289284Srpaulo	    is_multicast_ether_addr(peer_addr)) {
876289284Srpaulo		fst_printf(MSG_WARNING, "Bad peer_mac %s: not a unicast addr",
877289284Srpaulo			   mac);
878289284Srpaulo		return -1;
879289284Srpaulo	}
880289284Srpaulo
881289284Srpaulo	return 0;
882289284Srpaulo}
883289284Srpaulo
884289284Srpaulo
885289284Srpauloint fst_parse_attach_command(const char *cmd, char *ifname, size_t ifname_size,
886289284Srpaulo			     struct fst_iface_cfg *cfg)
887289284Srpaulo{
888289284Srpaulo	char *pos;
889289284Srpaulo	char *endp;
890289284Srpaulo	Boolean is_valid;
891289284Srpaulo	int val;
892289284Srpaulo
893289284Srpaulo	if (fst_read_next_text_param(cmd, ifname, ifname_size, &endp) ||
894289284Srpaulo	    fst_read_next_text_param(endp, cfg->group_id, sizeof(cfg->group_id),
895289284Srpaulo				     &endp))
896289284Srpaulo		return -EINVAL;
897289284Srpaulo
898289284Srpaulo	cfg->llt = FST_DEFAULT_LLT_CFG_VALUE;
899289284Srpaulo	cfg->priority = 0;
900289284Srpaulo	pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_LLT);
901289284Srpaulo	if (pos) {
902289284Srpaulo		pos += os_strlen(FST_ATTACH_CMD_PNAME_LLT);
903289284Srpaulo		if (*pos == '=') {
904289284Srpaulo			val = fst_read_next_int_param(pos + 1, &is_valid,
905289284Srpaulo						      &endp);
906289284Srpaulo			if (is_valid)
907289284Srpaulo				cfg->llt = val;
908289284Srpaulo		}
909289284Srpaulo	}
910289284Srpaulo	pos = os_strstr(endp, FST_ATTACH_CMD_PNAME_PRIORITY);
911289284Srpaulo	if (pos) {
912289284Srpaulo		pos += os_strlen(FST_ATTACH_CMD_PNAME_PRIORITY);
913289284Srpaulo		if (*pos == '=') {
914289284Srpaulo			val = fst_read_next_int_param(pos + 1, &is_valid,
915289284Srpaulo						      &endp);
916289284Srpaulo			if (is_valid)
917289284Srpaulo				cfg->priority = (u8) val;
918289284Srpaulo		}
919289284Srpaulo	}
920289284Srpaulo
921289284Srpaulo	return 0;
922289284Srpaulo}
923289284Srpaulo
924289284Srpaulo
925289284Srpauloint fst_parse_detach_command(const char *cmd, char *ifname, size_t ifname_size)
926289284Srpaulo{
927289284Srpaulo	char *endp;
928289284Srpaulo
929289284Srpaulo	return fst_read_next_text_param(cmd, ifname, ifname_size, &endp);
930289284Srpaulo}
931289284Srpaulo
932289284Srpaulo
933289284Srpauloint fst_iface_detach(const char *ifname)
934289284Srpaulo{
935289284Srpaulo	struct fst_group *g;
936289284Srpaulo
937289284Srpaulo	foreach_fst_group(g) {
938289284Srpaulo		struct fst_iface *f;
939289284Srpaulo
940289284Srpaulo		f = fst_group_get_iface_by_name(g, ifname);
941289284Srpaulo		if (f) {
942289284Srpaulo			fst_detach(f);
943289284Srpaulo			return 0;
944289284Srpaulo		}
945289284Srpaulo	}
946289284Srpaulo
947289284Srpaulo	return -EINVAL;
948289284Srpaulo}
949