1 /******************************************************************************
2  *
3  *  Copyright (C) 2008-2014 Broadcom Corporation
4  *
5  *  Licensed under the Apache License, Version 2.0 (the "License");
6  *  you may not use this file except in compliance with the License.
7  *  You may obtain a copy of the License at:
8  *
9  *  http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *  Unless required by applicable law or agreed to in writing, software
12  *  distributed under the License is distributed on an "AS IS" BASIS,
13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *  See the License for the specific language governing permissions and
15  *  limitations under the License.
16  *
17  ******************************************************************************/
18 
19 /******************************************************************************
20  *
21  *  this file contains ATT protocol functions
22  *
23  ******************************************************************************/
24 
25 #include "bt_target.h"
26 
27 #if BLE_INCLUDED == TRUE
28 
29 #include "gatt_int.h"
30 #include "l2c_api.h"
31 
32 #define GATT_HDR_FIND_TYPE_VALUE_LEN    21
33 #define GATT_OP_CODE_SIZE   1
34 /**********************************************************************
35 **   ATT protocl message building utility                              *
36 ***********************************************************************/
37 /*******************************************************************************
38 **
39 ** Function         attp_build_mtu_exec_cmd
40 **
41 ** Description      Build a exchange MTU request
42 **
43 ** Returns          None.
44 **
45 *******************************************************************************/
attp_build_mtu_cmd(UINT8 op_code,UINT16 rx_mtu)46 BT_HDR *attp_build_mtu_cmd(UINT8 op_code, UINT16 rx_mtu)
47 {
48     BT_HDR      *p_buf = NULL;
49     UINT8       *p;
50 
51     if ((p_buf = (BT_HDR *)GKI_getbuf(sizeof(BT_HDR) + GATT_HDR_SIZE + L2CAP_MIN_OFFSET)) != NULL)
52     {
53         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
54 
55         UINT8_TO_STREAM (p, op_code);
56         UINT16_TO_STREAM (p, rx_mtu);
57 
58         p_buf->offset = L2CAP_MIN_OFFSET;
59         p_buf->len = GATT_HDR_SIZE; /* opcode + 2 bytes mtu */
60     }
61     return p_buf;
62 }
63 /*******************************************************************************
64 **
65 ** Function         attp_build_exec_write_cmd
66 **
67 ** Description      Build a execute write request or response.
68 **
69 ** Returns          None.
70 **
71 *******************************************************************************/
attp_build_exec_write_cmd(UINT8 op_code,UINT8 flag)72 BT_HDR *attp_build_exec_write_cmd (UINT8 op_code, UINT8 flag)
73 {
74     BT_HDR      *p_buf = NULL;
75     UINT8       *p;
76 
77     if ((p_buf = (BT_HDR *)GKI_getpoolbuf(GATT_BUF_POOL_ID)) != NULL)
78     {
79         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
80 
81         p_buf->offset = L2CAP_MIN_OFFSET;
82         p_buf->len = GATT_OP_CODE_SIZE;
83 
84         UINT8_TO_STREAM (p, op_code);
85 
86         if (op_code == GATT_REQ_EXEC_WRITE)
87         {
88             flag &= GATT_PREP_WRITE_EXEC;
89             UINT8_TO_STREAM (p, flag);
90             p_buf->len += 1;
91         }
92 
93     }
94 
95     return p_buf;
96 }
97 
98 /*******************************************************************************
99 **
100 ** Function         attp_build_err_cmd
101 **
102 ** Description      Build a exchange MTU request
103 **
104 ** Returns          None.
105 **
106 *******************************************************************************/
attp_build_err_cmd(UINT8 cmd_code,UINT16 err_handle,UINT8 reason)107 BT_HDR *attp_build_err_cmd(UINT8 cmd_code, UINT16 err_handle, UINT8 reason)
108 {
109     BT_HDR      *p_buf = NULL;
110     UINT8       *p;
111 
112     if ((p_buf = (BT_HDR *)GKI_getbuf(sizeof(BT_HDR) + L2CAP_MIN_OFFSET + 5)) != NULL)
113     {
114         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
115 
116         UINT8_TO_STREAM (p, GATT_RSP_ERROR);
117         UINT8_TO_STREAM (p, cmd_code);
118         UINT16_TO_STREAM(p, err_handle);
119         UINT8_TO_STREAM (p, reason);
120 
121         p_buf->offset = L2CAP_MIN_OFFSET;
122         /* GATT_HDR_SIZE (1B ERR_RSP op code+ 2B handle) + 1B cmd_op_code  + 1B status */
123         p_buf->len = GATT_HDR_SIZE + 1 + 1;
124     }
125     return p_buf;
126 }
127 /*******************************************************************************
128 **
129 ** Function         attp_build_browse_cmd
130 **
131 ** Description      Build a read information request or read by type request
132 **
133 ** Returns          None.
134 **
135 *******************************************************************************/
attp_build_browse_cmd(UINT8 op_code,UINT16 s_hdl,UINT16 e_hdl,tBT_UUID uuid)136 BT_HDR *attp_build_browse_cmd(UINT8 op_code, UINT16 s_hdl, UINT16 e_hdl, tBT_UUID uuid)
137 {
138     BT_HDR      *p_buf = NULL;
139     UINT8       *p;
140 
141     if ((p_buf = (BT_HDR *)GKI_getbuf(sizeof(BT_HDR) + 8 + L2CAP_MIN_OFFSET)) != NULL)
142     {
143         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
144         /* Describe the built message location and size */
145         p_buf->offset = L2CAP_MIN_OFFSET;
146         p_buf->len = GATT_OP_CODE_SIZE + 4;
147 
148         UINT8_TO_STREAM (p, op_code);
149         UINT16_TO_STREAM (p, s_hdl);
150         UINT16_TO_STREAM (p, e_hdl);
151         p_buf->len += gatt_build_uuid_to_stream(&p, uuid);
152     }
153 
154     return p_buf;
155 }
156 /*******************************************************************************
157 **
158 ** Function         attp_build_read_handles_cmd
159 **
160 ** Description      Build a read by type and value request.
161 **
162 ** Returns          pointer to the command buffer.
163 **
164 *******************************************************************************/
attp_build_read_by_type_value_cmd(UINT16 payload_size,tGATT_FIND_TYPE_VALUE * p_value_type)165 BT_HDR *attp_build_read_by_type_value_cmd (UINT16 payload_size, tGATT_FIND_TYPE_VALUE *p_value_type)
166 {
167     BT_HDR      *p_buf = NULL;
168     UINT8       *p;
169     UINT16      len = p_value_type->value_len;
170 
171     if ((p_buf = (BT_HDR *)GKI_getbuf((UINT16)(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET))) != NULL)
172     {
173         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
174 
175         p_buf->offset = L2CAP_MIN_OFFSET;
176         p_buf->len = 5; /* opcode + s_handle + e_handle */
177 
178         UINT8_TO_STREAM  (p, GATT_REQ_FIND_TYPE_VALUE);
179         UINT16_TO_STREAM (p, p_value_type->s_handle);
180         UINT16_TO_STREAM (p, p_value_type->e_handle);
181 
182         p_buf->len += gatt_build_uuid_to_stream(&p, p_value_type->uuid);
183 
184         if (p_value_type->value_len +  p_buf->len > payload_size )
185             len = payload_size - p_buf->len;
186 
187         memcpy (p, p_value_type->value, len);
188         p_buf->len += len;
189     }
190 
191     return p_buf;
192 }
193 /*******************************************************************************
194 **
195 ** Function         attp_build_read_multi_cmd
196 **
197 ** Description      Build a read multiple request
198 **
199 ** Returns          None.
200 **
201 *******************************************************************************/
attp_build_read_multi_cmd(UINT16 payload_size,UINT16 num_handle,UINT16 * p_handle)202 BT_HDR *attp_build_read_multi_cmd(UINT16 payload_size, UINT16 num_handle, UINT16 *p_handle)
203 {
204     BT_HDR      *p_buf = NULL;
205     UINT8       *p, i = 0;
206 
207     if ((p_buf = (BT_HDR *)GKI_getbuf((UINT16)(sizeof(BT_HDR) + num_handle * 2 + 1 + L2CAP_MIN_OFFSET))) != NULL)
208     {
209         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
210 
211         p_buf->offset = L2CAP_MIN_OFFSET;
212         p_buf->len = 1;
213 
214         UINT8_TO_STREAM (p, GATT_REQ_READ_MULTI);
215 
216         for (i = 0; i < num_handle && p_buf->len + 2 <= payload_size; i ++)
217         {
218             UINT16_TO_STREAM (p, *(p_handle + i));
219             p_buf->len += 2;
220         }
221     }
222 
223     return p_buf;
224 }
225 /*******************************************************************************
226 **
227 ** Function         attp_build_handle_cmd
228 **
229 ** Description      Build a read /read blob request
230 **
231 ** Returns          None.
232 **
233 *******************************************************************************/
attp_build_handle_cmd(UINT8 op_code,UINT16 handle,UINT16 offset)234 BT_HDR *attp_build_handle_cmd(UINT8 op_code, UINT16 handle, UINT16 offset)
235 {
236     BT_HDR      *p_buf = NULL;
237     UINT8       *p;
238 
239     if ((p_buf = (BT_HDR *)GKI_getbuf(sizeof(BT_HDR) + 5 + L2CAP_MIN_OFFSET)) != NULL)
240     {
241         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
242 
243         p_buf->offset = L2CAP_MIN_OFFSET;
244 
245         UINT8_TO_STREAM (p, op_code);
246         p_buf->len  = 1;
247 
248         UINT16_TO_STREAM (p, handle);
249         p_buf->len += 2;
250 
251         if (op_code == GATT_REQ_READ_BLOB)
252         {
253             UINT16_TO_STREAM (p, offset);
254             p_buf->len += 2;
255         }
256 
257     }
258 
259     return p_buf;
260 }
261 /*******************************************************************************
262 **
263 ** Function         attp_build_opcode_cmd
264 **
265 ** Description      Build a  request/response with opcode only.
266 **
267 ** Returns          None.
268 **
269 *******************************************************************************/
attp_build_opcode_cmd(UINT8 op_code)270 BT_HDR *attp_build_opcode_cmd(UINT8 op_code)
271 {
272     BT_HDR      *p_buf = NULL;
273     UINT8       *p;
274 
275     if ((p_buf = (BT_HDR *)GKI_getbuf(sizeof(BT_HDR) + 1 + L2CAP_MIN_OFFSET)) != NULL)
276     {
277         p = (UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
278         p_buf->offset = L2CAP_MIN_OFFSET;
279 
280         UINT8_TO_STREAM (p, op_code);
281         p_buf->len  = 1;
282     }
283 
284     return p_buf;
285 }
286 /*******************************************************************************
287 **
288 ** Function         attp_build_value_cmd
289 **
290 ** Description      Build a attribute value request
291 **
292 ** Returns          None.
293 **
294 *******************************************************************************/
attp_build_value_cmd(UINT16 payload_size,UINT8 op_code,UINT16 handle,UINT16 offset,UINT16 len,UINT8 * p_data)295 BT_HDR *attp_build_value_cmd (UINT16 payload_size, UINT8 op_code, UINT16 handle,
296                               UINT16 offset, UINT16 len, UINT8 *p_data)
297 {
298     BT_HDR      *p_buf = NULL;
299     UINT8       *p, *pp, pair_len, *p_pair_len;
300 
301     if ((p_buf = (BT_HDR *)GKI_getbuf((UINT16)(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET))) != NULL)
302     {
303         p = pp =(UINT8 *)(p_buf + 1) + L2CAP_MIN_OFFSET;
304 
305         UINT8_TO_STREAM (p, op_code);
306         p_buf->offset = L2CAP_MIN_OFFSET;
307         p_buf->len = 1;
308 
309         if (op_code == GATT_RSP_READ_BY_TYPE)
310         {
311             p_pair_len = p;
312             pair_len = len + 2;
313             UINT8_TO_STREAM (p, pair_len);
314             p_buf->len += 1;
315         }
316         if (op_code != GATT_RSP_READ_BLOB && op_code != GATT_RSP_READ)
317         {
318             UINT16_TO_STREAM (p, handle);
319             p_buf->len += 2;
320         }
321 
322         if (op_code == GATT_REQ_PREPARE_WRITE ||op_code == GATT_RSP_PREPARE_WRITE )
323         {
324             UINT16_TO_STREAM (p, offset);
325             p_buf->len += 2;
326         }
327 
328         if (len > 0 && p_data != NULL)
329         {
330             /* ensure data not exceed MTU size */
331             if (payload_size - p_buf->len < len)
332             {
333                 len = payload_size - p_buf->len;
334                 /* update handle value pair length */
335                 if (op_code == GATT_RSP_READ_BY_TYPE)
336                     *p_pair_len = (len + 2);
337 
338                 GATT_TRACE_WARNING("attribute value too long, to be truncated to %d", len);
339             }
340 
341             ARRAY_TO_STREAM (p, p_data, len);
342             p_buf->len += len;
343         }
344     }
345     return p_buf;
346 }
347 
348 /*******************************************************************************
349 **
350 ** Function         attp_send_msg_to_l2cap
351 **
352 ** Description      Send message to L2CAP.
353 **
354 *******************************************************************************/
attp_send_msg_to_l2cap(tGATT_TCB * p_tcb,BT_HDR * p_toL2CAP)355 tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB *p_tcb, BT_HDR *p_toL2CAP)
356 {
357     UINT16      l2cap_ret;
358 
359 
360     if (p_tcb->att_lcid == L2CAP_ATT_CID)
361         l2cap_ret = L2CA_SendFixedChnlData (L2CAP_ATT_CID, p_tcb->peer_bda, p_toL2CAP);
362     else
363         l2cap_ret = (UINT16) L2CA_DataWrite (p_tcb->att_lcid, p_toL2CAP);
364 
365     if (l2cap_ret == L2CAP_DW_FAILED)
366     {
367         GATT_TRACE_ERROR("ATT   failed to pass msg:0x%0x to L2CAP",
368             *((UINT8 *)(p_toL2CAP + 1) + p_toL2CAP->offset));
369         return GATT_INTERNAL_ERROR;
370     }
371     else if (l2cap_ret == L2CAP_DW_CONGESTED)
372     {
373         GATT_TRACE_DEBUG("ATT congested, message accepted");
374         return GATT_CONGESTED;
375     }
376     return GATT_SUCCESS;
377 }
378 
379 /*******************************************************************************
380 **
381 ** Function         attp_build_sr_msg
382 **
383 ** Description      Build ATT Server PDUs.
384 **
385 *******************************************************************************/
attp_build_sr_msg(tGATT_TCB * p_tcb,UINT8 op_code,tGATT_SR_MSG * p_msg)386 BT_HDR *attp_build_sr_msg(tGATT_TCB *p_tcb, UINT8 op_code, tGATT_SR_MSG *p_msg)
387 {
388     BT_HDR          *p_cmd = NULL;
389     UINT16          offset = 0;
390 
391     switch (op_code)
392     {
393     case GATT_RSP_READ_BLOB:
394     case GATT_RSP_PREPARE_WRITE:
395         GATT_TRACE_EVENT ("ATT_RSP_READ_BLOB/GATT_RSP_PREPARE_WRITE: len = %d offset = %d",
396                     p_msg->attr_value.len, p_msg->attr_value.offset);
397         offset = p_msg->attr_value.offset;
398 /* Coverity: [FALSE-POSITIVE error] intended fall through */
399 /* Missing break statement between cases in switch statement */
400         /* fall through */
401     case GATT_RSP_READ_BY_TYPE:
402     case GATT_RSP_READ:
403     case GATT_HANDLE_VALUE_NOTIF:
404     case GATT_HANDLE_VALUE_IND:
405         p_cmd = attp_build_value_cmd(p_tcb->payload_size,
406                                      op_code,
407                                      p_msg->attr_value.handle,
408                                      offset,
409                                      p_msg->attr_value.len,
410                                      p_msg->attr_value.value);
411         break;
412 
413     case GATT_RSP_WRITE:
414         p_cmd = attp_build_opcode_cmd(op_code);
415         break;
416 
417     case GATT_RSP_ERROR:
418         p_cmd = attp_build_err_cmd(p_msg->error.cmd_code, p_msg->error.handle, p_msg->error.reason);
419         break;
420 
421     case GATT_RSP_EXEC_WRITE:
422         p_cmd = attp_build_exec_write_cmd(op_code, 0);
423         break;
424 
425     case GATT_RSP_MTU:
426         p_cmd = attp_build_mtu_cmd(op_code, p_msg->mtu);
427         break;
428 
429     default:
430         GATT_TRACE_DEBUG("attp_build_sr_msg: unknown op code = %d", op_code);
431         break;
432     }
433 
434     if (!p_cmd)
435         GATT_TRACE_ERROR("No resources");
436 
437     return p_cmd;
438 }
439 
440 /*******************************************************************************
441 **
442 ** Function         attp_send_sr_msg
443 **
444 ** Description      This function sends the server response or indication message
445 **                  to client.
446 **
447 ** Parameter        p_tcb: pointer to the connecton control block.
448 **                  p_msg: pointer to message parameters structure.
449 **
450 ** Returns          GATT_SUCCESS if sucessfully sent; otherwise error code.
451 **
452 **
453 *******************************************************************************/
attp_send_sr_msg(tGATT_TCB * p_tcb,BT_HDR * p_msg)454 tGATT_STATUS attp_send_sr_msg (tGATT_TCB *p_tcb, BT_HDR *p_msg)
455 {
456     tGATT_STATUS     cmd_sent = GATT_NO_RESOURCES;
457 
458     if (p_tcb != NULL)
459     {
460         if (p_msg != NULL)
461         {
462             p_msg->offset = L2CAP_MIN_OFFSET;
463             cmd_sent = attp_send_msg_to_l2cap (p_tcb, p_msg);
464         }
465     }
466     return cmd_sent;
467 }
468 
469 /*******************************************************************************
470 **
471 ** Function         attp_cl_send_cmd
472 **
473 ** Description      Send a ATT command or enqueue it.
474 **
475 ** Returns          GATT_SUCCESS if command sent
476 **                  GATT_CONGESTED if command sent but channel congested
477 **                  GATT_CMD_STARTED if command queue up in GATT
478 **                  GATT_ERROR if command sending failure
479 **
480 *******************************************************************************/
attp_cl_send_cmd(tGATT_TCB * p_tcb,UINT16 clcb_idx,UINT8 cmd_code,BT_HDR * p_cmd)481 tGATT_STATUS attp_cl_send_cmd(tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 cmd_code, BT_HDR *p_cmd)
482 {
483     tGATT_STATUS att_ret = GATT_SUCCESS;
484 
485     if (p_tcb != NULL)
486     {
487         cmd_code &= ~GATT_AUTH_SIGN_MASK;
488 
489         /* no pending request or value confirmation */
490         if (p_tcb->pending_cl_req == p_tcb->next_slot_inq ||
491             cmd_code == GATT_HANDLE_VALUE_CONF)
492         {
493             att_ret = attp_send_msg_to_l2cap(p_tcb, p_cmd);
494             if (att_ret == GATT_CONGESTED || att_ret == GATT_SUCCESS)
495             {
496                 /* do not enq cmd if handle value confirmation or set request */
497                 if (cmd_code != GATT_HANDLE_VALUE_CONF && cmd_code != GATT_CMD_WRITE)
498                 {
499                     gatt_start_rsp_timer (clcb_idx);
500                     gatt_cmd_enq(p_tcb, clcb_idx, FALSE, cmd_code, NULL);
501                 }
502             }
503             else
504                 att_ret = GATT_INTERNAL_ERROR;
505         }
506         else
507         {
508             att_ret = GATT_CMD_STARTED;
509             gatt_cmd_enq(p_tcb, clcb_idx, TRUE, cmd_code, p_cmd);
510         }
511     }
512     else
513         att_ret = GATT_ERROR;
514 
515     return att_ret;
516 }
517 /*******************************************************************************
518 **
519 ** Function         attp_send_cl_msg
520 **
521 ** Description      This function sends the client request or confirmation message
522 **                  to server.
523 **
524 ** Parameter        p_tcb: pointer to the connectino control block.
525 **                  clcb_idx: clcb index
526 **                  op_code: message op code.
527 **                  p_msg: pointer to message parameters structure.
528 **
529 ** Returns          GATT_SUCCESS if sucessfully sent; otherwise error code.
530 **
531 **
532 *******************************************************************************/
attp_send_cl_msg(tGATT_TCB * p_tcb,UINT16 clcb_idx,UINT8 op_code,tGATT_CL_MSG * p_msg)533 tGATT_STATUS attp_send_cl_msg (tGATT_TCB *p_tcb, UINT16 clcb_idx, UINT8 op_code, tGATT_CL_MSG *p_msg)
534 {
535     tGATT_STATUS     status = GATT_NO_RESOURCES;
536     BT_HDR          *p_cmd = NULL;
537     UINT16          offset = 0, handle;
538 
539     if (p_tcb != NULL)
540     {
541         switch (op_code)
542         {
543         case GATT_REQ_MTU:
544             if (p_msg->mtu <= GATT_MAX_MTU_SIZE)
545             {
546                 p_tcb->payload_size = p_msg->mtu;
547                 p_cmd = attp_build_mtu_cmd(GATT_REQ_MTU, p_msg->mtu);
548             }
549             else
550                 status = GATT_ILLEGAL_PARAMETER;
551             break;
552 
553         case GATT_REQ_FIND_INFO:
554         case GATT_REQ_READ_BY_TYPE:
555         case GATT_REQ_READ_BY_GRP_TYPE:
556             if (GATT_HANDLE_IS_VALID (p_msg->browse.s_handle) &&
557                 GATT_HANDLE_IS_VALID (p_msg->browse.e_handle)  &&
558                 p_msg->browse.s_handle <= p_msg->browse.e_handle)
559             {
560                 p_cmd = attp_build_browse_cmd(op_code,
561                                             p_msg->browse.s_handle,
562                                             p_msg->browse.e_handle,
563                                             p_msg->browse.uuid);
564             }
565             else
566                 status = GATT_ILLEGAL_PARAMETER;
567             break;
568 
569         case GATT_REQ_READ_BLOB:
570             offset = p_msg->read_blob.offset;
571             /* fall through */
572         case GATT_REQ_READ:
573             handle = (op_code == GATT_REQ_READ) ? p_msg->handle: p_msg->read_blob.handle;
574             /*  handle checking */
575             if (GATT_HANDLE_IS_VALID (handle))
576             {
577                 p_cmd = attp_build_handle_cmd(op_code, handle, offset);
578             }
579             else
580                 status = GATT_ILLEGAL_PARAMETER;
581             break;
582 
583         case GATT_HANDLE_VALUE_CONF:
584             p_cmd = attp_build_opcode_cmd(op_code);
585             break;
586 
587         case GATT_REQ_PREPARE_WRITE:
588             offset = p_msg->attr_value.offset;
589             /* fall through */
590         case GATT_REQ_WRITE:
591         case GATT_CMD_WRITE:
592         case GATT_SIGN_CMD_WRITE:
593             if (GATT_HANDLE_IS_VALID (p_msg->attr_value.handle))
594             {
595                 p_cmd = attp_build_value_cmd (p_tcb->payload_size,
596                                               op_code, p_msg->attr_value.handle,
597                                               offset,
598                                               p_msg->attr_value.len,
599                                               p_msg->attr_value.value);
600             }
601             else
602                 status = GATT_ILLEGAL_PARAMETER;
603             break;
604 
605         case GATT_REQ_EXEC_WRITE:
606             p_cmd = attp_build_exec_write_cmd(op_code, p_msg->exec_write);
607             break;
608 
609         case GATT_REQ_FIND_TYPE_VALUE:
610             p_cmd = attp_build_read_by_type_value_cmd(p_tcb->payload_size, &p_msg->find_type_value);
611             break;
612 
613         case GATT_REQ_READ_MULTI:
614             p_cmd = attp_build_read_multi_cmd(p_tcb->payload_size,
615                                               p_msg->read_multi.num_handles,
616                                               p_msg->read_multi.handles);
617             break;
618 
619         default:
620             break;
621         }
622 
623         if (p_cmd != NULL)
624             status = attp_cl_send_cmd(p_tcb, clcb_idx, op_code, p_cmd);
625 
626     }
627     else
628     {
629         GATT_TRACE_ERROR("Peer device not connected");
630     }
631 
632     return status;
633 }
634 #endif
635