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