1 /*
2  * ng_l2cap_cmds.c
3  */
4 
5 /*-
6  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
7  *
8  * Copyright (c) Maksim Yevmenkin <m_evmenkin@yahoo.com>
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  * 1. Redistributions of source code must retain the above copyright
15  *    notice, this list of conditions and the following disclaimer.
16  * 2. Redistributions in binary form must reproduce the above copyright
17  *    notice, this list of conditions and the following disclaimer in the
18  *    documentation and/or other materials provided with the distribution.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  *
32  * $Id: ng_l2cap_cmds.c,v 1.2 2003/09/08 19:11:45 max Exp $
33  * $FreeBSD$
34  */
35 
36 #include <sys/param.h>
37 #include <sys/systm.h>
38 #include <sys/kernel.h>
39 #include <sys/endian.h>
40 #include <sys/malloc.h>
41 #include <sys/mbuf.h>
42 #include <sys/queue.h>
43 #include <netgraph/ng_message.h>
44 #include <netgraph/netgraph.h>
45 #include <netgraph/bluetooth/include/ng_bluetooth.h>
46 #include <netgraph/bluetooth/include/ng_hci.h>
47 #include <netgraph/bluetooth/include/ng_l2cap.h>
48 #include <netgraph/bluetooth/l2cap/ng_l2cap_var.h>
49 #include <netgraph/bluetooth/l2cap/ng_l2cap_cmds.h>
50 #include <netgraph/bluetooth/l2cap/ng_l2cap_evnt.h>
51 #include <netgraph/bluetooth/l2cap/ng_l2cap_llpi.h>
52 #include <netgraph/bluetooth/l2cap/ng_l2cap_ulpi.h>
53 #include <netgraph/bluetooth/l2cap/ng_l2cap_misc.h>
54 
55 /******************************************************************************
56  ******************************************************************************
57  **                    L2CAP commands processing module
58  ******************************************************************************
59  ******************************************************************************/
60 
61 /*
62  * Process L2CAP command queue on connection
63  */
64 
65 void
66 ng_l2cap_con_wakeup(ng_l2cap_con_p con)
67 {
68 	ng_l2cap_cmd_p	 cmd = NULL;
69 	struct mbuf	*m = NULL;
70 	int		 error = 0;
71 
72 	/* Find first non-pending command in the queue */
73 	TAILQ_FOREACH(cmd, &con->cmd_list, next) {
74 		KASSERT((cmd->con == con),
75 ("%s: %s - invalid connection pointer!\n",
76 			__func__, NG_NODE_NAME(con->l2cap->node)));
77 
78 		if (!(cmd->flags & NG_L2CAP_CMD_PENDING))
79 			break;
80 	}
81 
82 	if (cmd == NULL)
83 		return;
84 
85 	/* Detach command packet */
86 	m = cmd->aux;
87 	cmd->aux = NULL;
88 
89 	/* Process command */
90 	switch (cmd->code) {
91 	case NG_L2CAP_DISCON_RSP:
92 	case NG_L2CAP_ECHO_RSP:
93 	case NG_L2CAP_INFO_RSP:
94 		/*
95 		 * Do not check return ng_l2cap_lp_send() value, because
96 		 * in these cases we do not really have a graceful way out.
97 		 * ECHO and INFO responses are internal to the stack and not
98 		 * visible to user. REJect is just being nice to remote end
99 		 * (otherwise remote end will timeout anyway). DISCON is
100 		 * probably most interesting here, however, if it fails
101 		 * there is nothing we can do anyway.
102 		 */
103 
104 		(void) ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
105 		ng_l2cap_unlink_cmd(cmd);
106 		ng_l2cap_free_cmd(cmd);
107 		break;
108 	case NG_L2CAP_CMD_REJ:
109 		(void) ng_l2cap_lp_send(con,
110 					(con->linktype == NG_HCI_LINK_ACL)?
111 					NG_L2CAP_SIGNAL_CID:
112 					NG_L2CAP_LESIGNAL_CID
113 					, m);
114 		ng_l2cap_unlink_cmd(cmd);
115 		ng_l2cap_free_cmd(cmd);
116 		break;
117 
118 	case NG_L2CAP_CON_REQ:
119 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
120 		if (error != 0) {
121 			ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token,
122 				NG_L2CAP_NO_RESOURCES, 0);
123 			ng_l2cap_free_chan(cmd->ch); /* will free commands */
124 		} else
125 			ng_l2cap_command_timeout(cmd,
126 				bluetooth_l2cap_rtx_timeout());
127 		break;
128 	case NG_L2CAP_CON_RSP:
129 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
130 		ng_l2cap_unlink_cmd(cmd);
131 		if (cmd->ch != NULL) {
132 			ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token,
133 				(error == 0)? NG_L2CAP_SUCCESS :
134 					NG_L2CAP_NO_RESOURCES);
135 			if (error != 0)
136 				ng_l2cap_free_chan(cmd->ch);
137 		}
138 		ng_l2cap_free_cmd(cmd);
139 		break;
140 
141 	case NG_L2CAP_CFG_REQ:
142 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
143 		if (error != 0) {
144 			ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token,
145 				NG_L2CAP_NO_RESOURCES);
146 			ng_l2cap_unlink_cmd(cmd);
147 			ng_l2cap_free_cmd(cmd);
148 		} else
149 			ng_l2cap_command_timeout(cmd,
150 				bluetooth_l2cap_rtx_timeout());
151 		break;
152 
153 	case NG_L2CAP_CFG_RSP:
154 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
155 		ng_l2cap_unlink_cmd(cmd);
156 		if (cmd->ch != NULL)
157 			ng_l2cap_l2ca_cfg_rsp_rsp(cmd->ch, cmd->token,
158 				(error == 0)? NG_L2CAP_SUCCESS :
159 					NG_L2CAP_NO_RESOURCES);
160 		ng_l2cap_free_cmd(cmd);
161 		break;
162 
163 	case NG_L2CAP_DISCON_REQ:
164 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
165 		ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token,
166 			(error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES);
167 		if (error != 0)
168 			ng_l2cap_free_chan(cmd->ch); /* XXX free channel */
169 		else
170 			ng_l2cap_command_timeout(cmd,
171 				bluetooth_l2cap_rtx_timeout());
172 		break;
173 
174 	case NG_L2CAP_ECHO_REQ:
175 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
176 		if (error != 0) {
177 			ng_l2cap_l2ca_ping_rsp(con, cmd->token,
178 					NG_L2CAP_NO_RESOURCES, NULL);
179 			ng_l2cap_unlink_cmd(cmd);
180 			ng_l2cap_free_cmd(cmd);
181 		} else
182 			ng_l2cap_command_timeout(cmd,
183 				bluetooth_l2cap_rtx_timeout());
184 		break;
185 
186 	case NG_L2CAP_INFO_REQ:
187 		error = ng_l2cap_lp_send(con, NG_L2CAP_SIGNAL_CID, m);
188 		if (error != 0) {
189 			ng_l2cap_l2ca_get_info_rsp(con, cmd->token,
190 				NG_L2CAP_NO_RESOURCES, NULL);
191 			ng_l2cap_unlink_cmd(cmd);
192 			ng_l2cap_free_cmd(cmd);
193 		} else
194 			ng_l2cap_command_timeout(cmd,
195 				bluetooth_l2cap_rtx_timeout());
196 		break;
197 
198 	case NGM_L2CAP_L2CA_WRITE: {
199 		int	length = m->m_pkthdr.len;
200 
201 		if (cmd->ch->dcid == NG_L2CAP_CLT_CID) {
202 			m = ng_l2cap_prepend(m, sizeof(ng_l2cap_clt_hdr_t));
203 			if (m == NULL)
204 				error = ENOBUFS;
205 			else
206                 		mtod(m, ng_l2cap_clt_hdr_t *)->psm =
207 							htole16(cmd->ch->psm);
208 		}
209 
210 		if (error == 0)
211 			error = ng_l2cap_lp_send(con, cmd->ch->dcid, m);
212 
213 		ng_l2cap_l2ca_write_rsp(cmd->ch, cmd->token,
214 			(error == 0)? NG_L2CAP_SUCCESS : NG_L2CAP_NO_RESOURCES,
215 			length);
216 
217 		ng_l2cap_unlink_cmd(cmd);
218 		ng_l2cap_free_cmd(cmd);
219 		} break;
220 	case NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE:
221 		error = ng_l2cap_lp_send(con, NG_L2CAP_LESIGNAL_CID, m);
222 		ng_l2cap_unlink_cmd(cmd);
223 		ng_l2cap_free_cmd(cmd);
224 		break;
225 	case NG_L2CAP_CMD_PARAM_UPDATE_REQUEST:
226 		  /*TBD.*/
227 	/* XXX FIXME add other commands */
228 	default:
229 		panic(
230 "%s: %s - unknown command code=%d\n",
231 			__func__, NG_NODE_NAME(con->l2cap->node), cmd->code);
232 		break;
233 	}
234 } /* ng_l2cap_con_wakeup */
235 
236 /*
237  * We have failed to open ACL connection to the remote unit. Could be negative
238  * confirmation or timeout. So fail any "delayed" commands, notify upper layer,
239  * remove all channels and remove connection descriptor.
240  */
241 
242 void
243 ng_l2cap_con_fail(ng_l2cap_con_p con, u_int16_t result)
244 {
245 	ng_l2cap_p	l2cap = con->l2cap;
246 	ng_l2cap_cmd_p	cmd = NULL;
247 	ng_l2cap_chan_p	ch = NULL;
248 
249 	NG_L2CAP_INFO(
250 "%s: %s - ACL connection failed, result=%d\n",
251 		__func__, NG_NODE_NAME(l2cap->node), result);
252 
253 	/* Connection is dying */
254 	con->flags |= NG_L2CAP_CON_DYING;
255 
256 	/* Clean command queue */
257 	while (!TAILQ_EMPTY(&con->cmd_list)) {
258 		cmd = TAILQ_FIRST(&con->cmd_list);
259 
260 		ng_l2cap_unlink_cmd(cmd);
261 		if(cmd->flags & NG_L2CAP_CMD_PENDING)
262 			ng_l2cap_command_untimeout(cmd);
263 
264 		KASSERT((cmd->con == con),
265 ("%s: %s - invalid connection pointer!\n",
266 			__func__, NG_NODE_NAME(l2cap->node)));
267 
268 		switch (cmd->code) {
269 		case NG_L2CAP_CMD_REJ:
270 		case NG_L2CAP_DISCON_RSP:
271 		case NG_L2CAP_ECHO_RSP:
272 		case NG_L2CAP_INFO_RSP:
273 		case NG_L2CAP_CMD_PARAM_UPDATE_RESPONSE:
274 			break;
275 
276 		case NG_L2CAP_CON_REQ:
277 			ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, result, 0);
278 			break;
279 
280 		case NG_L2CAP_CON_RSP:
281 			if (cmd->ch != NULL)
282 				ng_l2cap_l2ca_con_rsp_rsp(cmd->ch, cmd->token,
283 					result);
284 			break;
285 
286 		case NG_L2CAP_CFG_REQ:
287 		case NG_L2CAP_CFG_RSP:
288 		case NGM_L2CAP_L2CA_WRITE:
289 			ng_l2cap_l2ca_discon_ind(cmd->ch);
290 			break;
291 
292 		case NG_L2CAP_DISCON_REQ:
293 			ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token,
294 				NG_L2CAP_SUCCESS);
295 			break;
296 
297 		case NG_L2CAP_ECHO_REQ:
298 			ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token,
299 				result, NULL);
300 			break;
301 
302 		case NG_L2CAP_INFO_REQ:
303 			ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token,
304 				result, NULL);
305 			break;
306 
307 		/* XXX FIXME add other commands */
308 
309 		default:
310 			panic(
311 "%s: %s - unexpected command code=%d\n",
312 				__func__, NG_NODE_NAME(l2cap->node), cmd->code);
313 			break;
314 		}
315 
316 		if (cmd->ch != NULL)
317 			ng_l2cap_free_chan(cmd->ch);
318 
319 		ng_l2cap_free_cmd(cmd);
320 	}
321 
322 	/*
323 	 * There still might be channels (in OPEN state?) that
324 	 * did not submit any commands, so disconnect them
325 	 */
326 
327 	LIST_FOREACH(ch, &l2cap->chan_list, next)
328 		if (ch->con == con)
329 			ng_l2cap_l2ca_discon_ind(ch);
330 
331 	/* Free connection descriptor */
332 	ng_l2cap_free_con(con);
333 } /* ng_l2cap_con_fail */
334 
335 /*
336  * Process L2CAP command timeout. In general - notify upper layer and destroy
337  * channel. Do not pay much attention to return code, just do our best.
338  */
339 
340 void
341 ng_l2cap_process_command_timeout(node_p node, hook_p hook, void *arg1, int arg2)
342 {
343 	ng_l2cap_p	l2cap = NULL;
344 	ng_l2cap_con_p	con = NULL;
345 	ng_l2cap_cmd_p	cmd = NULL;
346 	u_int16_t	con_handle = (arg2 & 0x0ffff);
347 	u_int8_t	ident = ((arg2 >> 16) & 0xff);
348 
349 	if (NG_NODE_NOT_VALID(node)) {
350 		printf("%s: Netgraph node is not valid\n", __func__);
351 		return;
352 	}
353 
354 	l2cap = (ng_l2cap_p) NG_NODE_PRIVATE(node);
355 
356 	con = ng_l2cap_con_by_handle(l2cap, con_handle);
357 	if (con == NULL) {
358 		NG_L2CAP_ALERT(
359 "%s: %s - could not find connection, con_handle=%d\n",
360 			__func__, NG_NODE_NAME(node), con_handle);
361 		return;
362 	}
363 
364 	cmd = ng_l2cap_cmd_by_ident(con, ident);
365 	if (cmd == NULL) {
366 		NG_L2CAP_ALERT(
367 "%s: %s - could not find command, con_handle=%d, ident=%d\n",
368 			__func__, NG_NODE_NAME(node), con_handle, ident);
369 		return;
370 	}
371 
372 	cmd->flags &= ~NG_L2CAP_CMD_PENDING;
373 	ng_l2cap_unlink_cmd(cmd);
374 
375 	switch (cmd->code) {
376  	case NG_L2CAP_CON_REQ:
377 		ng_l2cap_l2ca_con_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT, 0);
378 		ng_l2cap_free_chan(cmd->ch);
379 		break;
380 
381 	case NG_L2CAP_CFG_REQ:
382 		ng_l2cap_l2ca_cfg_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT);
383 		break;
384 
385  	case NG_L2CAP_DISCON_REQ:
386 		ng_l2cap_l2ca_discon_rsp(cmd->ch, cmd->token, NG_L2CAP_TIMEOUT);
387 		ng_l2cap_free_chan(cmd->ch); /* XXX free channel */
388 		break;
389 
390 	case NG_L2CAP_ECHO_REQ:
391 		/* Echo request timed out. Let the upper layer know */
392 		ng_l2cap_l2ca_ping_rsp(cmd->con, cmd->token,
393 			NG_L2CAP_TIMEOUT, NULL);
394 		break;
395 
396 	case NG_L2CAP_INFO_REQ:
397 		/* Info request timed out. Let the upper layer know */
398 		ng_l2cap_l2ca_get_info_rsp(cmd->con, cmd->token,
399 			NG_L2CAP_TIMEOUT, NULL);
400 		break;
401 
402 	/* XXX FIXME add other commands */
403 
404 	default:
405 		panic(
406 "%s: %s - unexpected command code=%d\n",
407 			__func__, NG_NODE_NAME(l2cap->node), cmd->code);
408 		break;
409 	}
410 
411 	ng_l2cap_free_cmd(cmd);
412 } /* ng_l2cap_process_command_timeout */
413