1 /******************************************************************************
2 *
3 * Copyright 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 #include "gatt_int.h"
28 #include "l2c_api.h"
29 #include "osi/include/log.h"
30
31 #define GATT_HDR_FIND_TYPE_VALUE_LEN 21
32 #define GATT_OP_CODE_SIZE 1
33 #define GATT_START_END_HANDLE_SIZE 4
34
35 using base::StringPrintf;
36 using bluetooth::Uuid;
37 /**********************************************************************
38 * ATT protocl message building utility *
39 **********************************************************************/
40 /*******************************************************************************
41 *
42 * Function attp_build_mtu_exec_cmd
43 *
44 * Description Build a exchange MTU request
45 *
46 * Returns None.
47 *
48 ******************************************************************************/
attp_build_mtu_cmd(uint8_t op_code,uint16_t rx_mtu)49 static BT_HDR* attp_build_mtu_cmd(uint8_t op_code, uint16_t rx_mtu) {
50 uint8_t* p;
51 BT_HDR* p_buf =
52 (BT_HDR*)osi_malloc(sizeof(BT_HDR) + GATT_HDR_SIZE + L2CAP_MIN_OFFSET);
53
54 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
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_t op_code,uint8_t flag)72 static BT_HDR* attp_build_exec_write_cmd(uint8_t op_code, uint8_t flag) {
73 BT_HDR* p_buf = (BT_HDR*)osi_malloc(GATT_DATA_BUF_SIZE);
74 uint8_t* p;
75
76 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
77
78 p_buf->offset = L2CAP_MIN_OFFSET;
79 p_buf->len = GATT_OP_CODE_SIZE;
80
81 UINT8_TO_STREAM(p, op_code);
82
83 if (op_code == GATT_REQ_EXEC_WRITE) {
84 flag &= GATT_PREP_WRITE_EXEC;
85 UINT8_TO_STREAM(p, flag);
86 p_buf->len += 1;
87 }
88
89 return p_buf;
90 }
91
92 /*******************************************************************************
93 *
94 * Function attp_build_err_cmd
95 *
96 * Description Build a exchange MTU request
97 *
98 * Returns None.
99 *
100 ******************************************************************************/
attp_build_err_cmd(uint8_t cmd_code,uint16_t err_handle,uint8_t reason)101 static BT_HDR* attp_build_err_cmd(uint8_t cmd_code, uint16_t err_handle,
102 uint8_t reason) {
103 uint8_t* p;
104 BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR) + L2CAP_MIN_OFFSET + 5);
105
106 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
107 UINT8_TO_STREAM(p, GATT_RSP_ERROR);
108 UINT8_TO_STREAM(p, cmd_code);
109 UINT16_TO_STREAM(p, err_handle);
110 UINT8_TO_STREAM(p, reason);
111
112 p_buf->offset = L2CAP_MIN_OFFSET;
113 /* GATT_HDR_SIZE (1B ERR_RSP op code+ 2B handle) + 1B cmd_op_code + 1B status
114 */
115 p_buf->len = GATT_HDR_SIZE + 1 + 1;
116
117 return p_buf;
118 }
119 /*******************************************************************************
120 *
121 * Function attp_build_browse_cmd
122 *
123 * Description Build a read information request or read by type request
124 *
125 * Returns None.
126 *
127 ******************************************************************************/
attp_build_browse_cmd(uint8_t op_code,uint16_t s_hdl,uint16_t e_hdl,const bluetooth::Uuid & uuid)128 static BT_HDR* attp_build_browse_cmd(uint8_t op_code, uint16_t s_hdl,
129 uint16_t e_hdl,
130 const bluetooth::Uuid& uuid) {
131 const size_t payload_size =
132 (GATT_OP_CODE_SIZE) + (GATT_START_END_HANDLE_SIZE) + (Uuid::kNumBytes128);
133 BT_HDR* p_buf =
134 (BT_HDR*)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);
135
136 uint8_t* p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
137 /* Describe the built message location and size */
138 p_buf->offset = L2CAP_MIN_OFFSET;
139 p_buf->len = GATT_OP_CODE_SIZE + 4;
140
141 UINT8_TO_STREAM(p, op_code);
142 UINT16_TO_STREAM(p, s_hdl);
143 UINT16_TO_STREAM(p, e_hdl);
144 p_buf->len += gatt_build_uuid_to_stream(&p, uuid);
145
146 return p_buf;
147 }
148
149 /*******************************************************************************
150 *
151 * Function attp_build_read_handles_cmd
152 *
153 * Description Build a read by type and value request.
154 *
155 * Returns pointer to the command buffer.
156 *
157 ******************************************************************************/
attp_build_read_by_type_value_cmd(uint16_t payload_size,tGATT_FIND_TYPE_VALUE * p_value_type)158 static BT_HDR* attp_build_read_by_type_value_cmd(
159 uint16_t payload_size, tGATT_FIND_TYPE_VALUE* p_value_type) {
160 uint8_t* p;
161 uint16_t len = p_value_type->value_len;
162 BT_HDR* p_buf =
163 (BT_HDR*)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);
164
165 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
166 p_buf->offset = L2CAP_MIN_OFFSET;
167 p_buf->len = 5; /* opcode + s_handle + e_handle */
168
169 UINT8_TO_STREAM(p, GATT_REQ_FIND_TYPE_VALUE);
170 UINT16_TO_STREAM(p, p_value_type->s_handle);
171 UINT16_TO_STREAM(p, p_value_type->e_handle);
172
173 p_buf->len += gatt_build_uuid_to_stream(&p, p_value_type->uuid);
174
175 if (p_value_type->value_len + p_buf->len > payload_size)
176 len = payload_size - p_buf->len;
177
178 memcpy(p, p_value_type->value, len);
179 p_buf->len += len;
180
181 return p_buf;
182 }
183
184 /*******************************************************************************
185 *
186 * Function attp_build_read_multi_cmd
187 *
188 * Description Build a read multiple request
189 *
190 * Returns None.
191 *
192 ******************************************************************************/
attp_build_read_multi_cmd(uint8_t op_code,uint16_t payload_size,uint16_t num_handle,uint16_t * p_handle)193 static BT_HDR* attp_build_read_multi_cmd(uint8_t op_code, uint16_t payload_size,
194 uint16_t num_handle,
195 uint16_t* p_handle) {
196 uint8_t *p, i = 0;
197 BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR) + num_handle * 2 + 1 +
198 L2CAP_MIN_OFFSET);
199
200 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
201 p_buf->offset = L2CAP_MIN_OFFSET;
202 p_buf->len = 1;
203
204 UINT8_TO_STREAM(p, op_code);
205
206 for (i = 0; i < num_handle && p_buf->len + 2 <= payload_size; i++) {
207 UINT16_TO_STREAM(p, *(p_handle + i));
208 p_buf->len += 2;
209 }
210
211 return p_buf;
212 }
213 /*******************************************************************************
214 *
215 * Function attp_build_handle_cmd
216 *
217 * Description Build a read /read blob request
218 *
219 * Returns None.
220 *
221 ******************************************************************************/
attp_build_handle_cmd(uint8_t op_code,uint16_t handle,uint16_t offset)222 static BT_HDR* attp_build_handle_cmd(uint8_t op_code, uint16_t handle,
223 uint16_t offset) {
224 uint8_t* p;
225 BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR) + 5 + L2CAP_MIN_OFFSET);
226
227 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
228 p_buf->offset = L2CAP_MIN_OFFSET;
229
230 UINT8_TO_STREAM(p, op_code);
231 p_buf->len = 1;
232
233 UINT16_TO_STREAM(p, handle);
234 p_buf->len += 2;
235
236 if (op_code == GATT_REQ_READ_BLOB) {
237 UINT16_TO_STREAM(p, offset);
238 p_buf->len += 2;
239 }
240
241 return p_buf;
242 }
243
244 /*******************************************************************************
245 *
246 * Function attp_build_opcode_cmd
247 *
248 * Description Build a request/response with opcode only.
249 *
250 * Returns None.
251 *
252 ******************************************************************************/
attp_build_opcode_cmd(uint8_t op_code)253 static BT_HDR* attp_build_opcode_cmd(uint8_t op_code) {
254 uint8_t* p;
255 BT_HDR* p_buf = (BT_HDR*)osi_malloc(sizeof(BT_HDR) + 1 + L2CAP_MIN_OFFSET);
256
257 p = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
258 p_buf->offset = L2CAP_MIN_OFFSET;
259
260 UINT8_TO_STREAM(p, op_code);
261 p_buf->len = 1;
262
263 return p_buf;
264 }
265
266 /*******************************************************************************
267 *
268 * Function attp_build_value_cmd
269 *
270 * Description Build a attribute value request
271 *
272 * Returns None.
273 *
274 ******************************************************************************/
attp_build_value_cmd(uint16_t payload_size,uint8_t op_code,uint16_t handle,uint16_t offset,uint16_t len,uint8_t * p_data)275 static BT_HDR* attp_build_value_cmd(uint16_t payload_size, uint8_t op_code,
276 uint16_t handle, uint16_t offset,
277 uint16_t len, uint8_t* p_data) {
278 uint8_t *p, *pp, pair_len, *p_pair_len;
279 BT_HDR* p_buf =
280 (BT_HDR*)osi_malloc(sizeof(BT_HDR) + payload_size + L2CAP_MIN_OFFSET);
281
282 p = pp = (uint8_t*)(p_buf + 1) + L2CAP_MIN_OFFSET;
283 UINT8_TO_STREAM(p, op_code);
284 p_buf->offset = L2CAP_MIN_OFFSET;
285 p_buf->len = 1;
286
287 if (op_code == GATT_RSP_READ_BY_TYPE) {
288 p_pair_len = p;
289 pair_len = len + 2;
290 UINT8_TO_STREAM(p, pair_len);
291 p_buf->len += 1;
292 }
293 if (op_code != GATT_RSP_READ_BLOB && op_code != GATT_RSP_READ) {
294 UINT16_TO_STREAM(p, handle);
295 p_buf->len += 2;
296 }
297
298 if (op_code == GATT_REQ_PREPARE_WRITE || op_code == GATT_RSP_PREPARE_WRITE) {
299 UINT16_TO_STREAM(p, offset);
300 p_buf->len += 2;
301 }
302
303 if (len > 0 && p_data != NULL) {
304 /* ensure data not exceed MTU size */
305 if (payload_size - p_buf->len < len) {
306 len = payload_size - p_buf->len;
307 /* update handle value pair length */
308 if (op_code == GATT_RSP_READ_BY_TYPE) *p_pair_len = (len + 2);
309
310 LOG(WARNING) << StringPrintf(
311 "attribute value too long, to be truncated to %d", len);
312 }
313
314 ARRAY_TO_STREAM(p, p_data, len);
315 p_buf->len += len;
316 }
317
318 return p_buf;
319 }
320
321 /*******************************************************************************
322 *
323 * Function attp_send_msg_to_l2cap
324 *
325 * Description Send message to L2CAP.
326 *
327 ******************************************************************************/
attp_send_msg_to_l2cap(tGATT_TCB & tcb,uint16_t lcid,BT_HDR * p_toL2CAP)328 tGATT_STATUS attp_send_msg_to_l2cap(tGATT_TCB& tcb, uint16_t lcid,
329 BT_HDR* p_toL2CAP) {
330 uint16_t l2cap_ret;
331
332 if (lcid == L2CAP_ATT_CID) {
333 LOG_DEBUG("Sending ATT message on att fixed channel");
334 l2cap_ret = L2CA_SendFixedChnlData(lcid, tcb.peer_bda, p_toL2CAP);
335 } else {
336 LOG_DEBUG("Sending ATT message on lcid:%hu", lcid);
337 l2cap_ret = (uint16_t)L2CA_DataWrite(lcid, p_toL2CAP);
338 }
339
340 if (l2cap_ret == L2CAP_DW_FAILED) {
341 LOG(ERROR) << __func__ << ": failed to write data to L2CAP";
342 return GATT_INTERNAL_ERROR;
343 } else if (l2cap_ret == L2CAP_DW_CONGESTED) {
344 VLOG(1) << StringPrintf("ATT congested, message accepted");
345 return GATT_CONGESTED;
346 }
347 return GATT_SUCCESS;
348 }
349
350 /** Build ATT Server PDUs */
attp_build_sr_msg(tGATT_TCB & tcb,uint8_t op_code,tGATT_SR_MSG * p_msg)351 BT_HDR* attp_build_sr_msg(tGATT_TCB& tcb, uint8_t op_code,
352 tGATT_SR_MSG* p_msg) {
353 uint16_t offset = 0;
354
355 switch (op_code) {
356 case GATT_RSP_READ_BLOB:
357 case GATT_RSP_PREPARE_WRITE:
358 VLOG(1) << StringPrintf(
359 "ATT_RSP_READ_BLOB/GATT_RSP_PREPARE_WRITE: len = %d offset = %d",
360 p_msg->attr_value.len, p_msg->attr_value.offset);
361 offset = p_msg->attr_value.offset;
362 FALLTHROUGH_INTENDED; /* FALLTHROUGH */
363 case GATT_RSP_READ_BY_TYPE:
364 case GATT_RSP_READ:
365 case GATT_HANDLE_VALUE_NOTIF:
366 case GATT_HANDLE_VALUE_IND:
367 return attp_build_value_cmd(
368 tcb.payload_size, op_code, p_msg->attr_value.handle, offset,
369 p_msg->attr_value.len, p_msg->attr_value.value);
370
371 case GATT_RSP_WRITE:
372 return attp_build_opcode_cmd(op_code);
373
374 case GATT_RSP_ERROR:
375 return attp_build_err_cmd(p_msg->error.cmd_code, p_msg->error.handle,
376 p_msg->error.reason);
377
378 case GATT_RSP_EXEC_WRITE:
379 return attp_build_exec_write_cmd(op_code, 0);
380
381 case GATT_RSP_MTU:
382 return attp_build_mtu_cmd(op_code, p_msg->mtu);
383
384 default:
385 LOG(FATAL) << "attp_build_sr_msg: unknown op code = " << +op_code;
386 return nullptr;
387 }
388 }
389
390 /*******************************************************************************
391 *
392 * Function attp_send_sr_msg
393 *
394 * Description This function sends the server response or indication
395 * message to client.
396 *
397 * Parameter p_tcb: pointer to the connecton control block.
398 * p_msg: pointer to message parameters structure.
399 *
400 * Returns GATT_SUCCESS if sucessfully sent; otherwise error code.
401 *
402 *
403 ******************************************************************************/
attp_send_sr_msg(tGATT_TCB & tcb,uint16_t cid,BT_HDR * p_msg)404 tGATT_STATUS attp_send_sr_msg(tGATT_TCB& tcb, uint16_t cid, BT_HDR* p_msg) {
405 if (p_msg == NULL) {
406 LOG_WARN("Unable to send empty message");
407 return GATT_NO_RESOURCES;
408 }
409
410 LOG_DEBUG("Sending server response or indication message to client");
411 p_msg->offset = L2CAP_MIN_OFFSET;
412 return attp_send_msg_to_l2cap(tcb, cid, p_msg);
413 }
414
415 /*******************************************************************************
416 *
417 * Function attp_cl_send_cmd
418 *
419 * Description Send a ATT command or enqueue it.
420 *
421 * Returns GATT_SUCCESS if command sent
422 * GATT_CONGESTED if command sent but channel congested
423 * GATT_CMD_STARTED if command queue up in GATT
424 * GATT_ERROR if command sending failure
425 *
426 ******************************************************************************/
attp_cl_send_cmd(tGATT_TCB & tcb,tGATT_CLCB * p_clcb,uint8_t cmd_code,BT_HDR * p_cmd)427 static tGATT_STATUS attp_cl_send_cmd(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
428 uint8_t cmd_code, BT_HDR* p_cmd) {
429 cmd_code &= ~GATT_AUTH_SIGN_MASK;
430
431 if (gatt_tcb_is_cid_busy(tcb, p_clcb->cid) &&
432 cmd_code != GATT_HANDLE_VALUE_CONF) {
433 gatt_cmd_enq(tcb, p_clcb, true, cmd_code, p_cmd);
434 LOG_DEBUG("Enqueued ATT command");
435 return GATT_CMD_STARTED;
436 }
437
438 LOG_DEBUG(
439 "Sending ATT command to l2cap cid:0x%04x eatt_channels:%u transport:%s",
440 p_clcb->cid, tcb.eatt, bt_transport_text(tcb.transport).c_str());
441 tGATT_STATUS att_ret = attp_send_msg_to_l2cap(tcb, p_clcb->cid, p_cmd);
442 if (att_ret != GATT_CONGESTED && att_ret != GATT_SUCCESS) {
443 LOG_WARN("Unable to send ATT command to l2cap layer");
444 return GATT_INTERNAL_ERROR;
445 }
446
447 if (cmd_code == GATT_HANDLE_VALUE_CONF || cmd_code == GATT_CMD_WRITE) {
448 return att_ret;
449 }
450
451 LOG_DEBUG("Starting ATT response timer");
452 gatt_start_rsp_timer(p_clcb);
453 gatt_cmd_enq(tcb, p_clcb, false, cmd_code, NULL);
454 return att_ret;
455 }
456
457 /*******************************************************************************
458 *
459 * Function attp_send_cl_confirmation_msg
460 *
461 * Description This function sends the client confirmation
462 * message to server.
463 *
464 * Parameter p_tcb: pointer to the connection control block.
465 * cid: channel id
466 *
467 * Returns GATT_SUCCESS if successfully sent; otherwise error code.
468 *
469 *
470 ******************************************************************************/
attp_send_cl_confirmation_msg(tGATT_TCB & tcb,uint16_t cid)471 tGATT_STATUS attp_send_cl_confirmation_msg(tGATT_TCB& tcb, uint16_t cid) {
472 BT_HDR* p_cmd = NULL;
473 p_cmd = attp_build_opcode_cmd(GATT_HANDLE_VALUE_CONF);
474
475 if (p_cmd == NULL) return GATT_NO_RESOURCES;
476
477 /* no pending request or value confirmation */
478 tGATT_STATUS att_ret = attp_send_msg_to_l2cap(tcb, cid, p_cmd);
479 if (att_ret != GATT_CONGESTED && att_ret != GATT_SUCCESS) {
480 return GATT_INTERNAL_ERROR;
481 }
482
483 return att_ret;
484 }
485
486 /*******************************************************************************
487 *
488 * Function attp_send_cl_msg
489 *
490 * Description This function sends the client request or confirmation
491 * message to server.
492 *
493 * Parameter p_tcb: pointer to the connectino control block.
494 * p_clcb: clcb
495 * op_code: message op code.
496 * p_msg: pointer to message parameters structure.
497 *
498 * Returns GATT_SUCCESS if sucessfully sent; otherwise error code.
499 *
500 *
501 ******************************************************************************/
attp_send_cl_msg(tGATT_TCB & tcb,tGATT_CLCB * p_clcb,uint8_t op_code,tGATT_CL_MSG * p_msg)502 tGATT_STATUS attp_send_cl_msg(tGATT_TCB& tcb, tGATT_CLCB* p_clcb,
503 uint8_t op_code, tGATT_CL_MSG* p_msg) {
504 BT_HDR* p_cmd = NULL;
505 uint16_t offset = 0, handle;
506
507 if (!p_clcb) {
508 LOG_ERROR("Missing p_clcb");
509 return GATT_ILLEGAL_PARAMETER;
510 }
511
512 uint16_t payload_size = gatt_tcb_get_payload_size_tx(tcb, p_clcb->cid);
513
514 switch (op_code) {
515 case GATT_REQ_MTU:
516 if (p_msg->mtu > GATT_MAX_MTU_SIZE) {
517 LOG_WARN(
518 "GATT message MTU is larger than max GATT MTU size op_code:%hhu",
519 op_code);
520 return GATT_ILLEGAL_PARAMETER;
521 }
522
523 tcb.payload_size = p_msg->mtu;
524 p_cmd = attp_build_mtu_cmd(GATT_REQ_MTU, p_msg->mtu);
525 break;
526
527 case GATT_REQ_FIND_INFO:
528 case GATT_REQ_READ_BY_TYPE:
529 case GATT_REQ_READ_BY_GRP_TYPE:
530 if (!GATT_HANDLE_IS_VALID(p_msg->browse.s_handle) ||
531 !GATT_HANDLE_IS_VALID(p_msg->browse.e_handle) ||
532 p_msg->browse.s_handle > p_msg->browse.e_handle) {
533 LOG_WARN("GATT message has invalid handle op_code:%hhu", op_code);
534 return GATT_ILLEGAL_PARAMETER;
535 }
536
537 p_cmd = attp_build_browse_cmd(op_code, p_msg->browse.s_handle,
538 p_msg->browse.e_handle, p_msg->browse.uuid);
539 break;
540
541 case GATT_REQ_READ_BLOB:
542 offset = p_msg->read_blob.offset;
543 FALLTHROUGH_INTENDED; /* FALLTHROUGH */
544 case GATT_REQ_READ:
545 handle =
546 (op_code == GATT_REQ_READ) ? p_msg->handle : p_msg->read_blob.handle;
547 /* handle checking */
548 if (!GATT_HANDLE_IS_VALID(handle)) {
549 LOG_WARN("GATT message has invalid handle op_code:%hhu", op_code);
550 return GATT_ILLEGAL_PARAMETER;
551 }
552
553 p_cmd = attp_build_handle_cmd(op_code, handle, offset);
554 break;
555
556 case GATT_REQ_PREPARE_WRITE:
557 offset = p_msg->attr_value.offset;
558 FALLTHROUGH_INTENDED; /* FALLTHROUGH */
559 case GATT_REQ_WRITE:
560 case GATT_CMD_WRITE:
561 case GATT_SIGN_CMD_WRITE:
562 if (!GATT_HANDLE_IS_VALID(p_msg->attr_value.handle)) {
563 LOG_WARN("GATT message has invalid handle op_code:%hhu", op_code);
564 return GATT_ILLEGAL_PARAMETER;
565 }
566
567 p_cmd = attp_build_value_cmd(
568 payload_size, op_code, p_msg->attr_value.handle, offset,
569 p_msg->attr_value.len, p_msg->attr_value.value);
570 break;
571
572 case GATT_REQ_EXEC_WRITE:
573 p_cmd = attp_build_exec_write_cmd(op_code, p_msg->exec_write);
574 break;
575
576 case GATT_REQ_FIND_TYPE_VALUE:
577 p_cmd = attp_build_read_by_type_value_cmd(payload_size,
578 &p_msg->find_type_value);
579 break;
580
581 case GATT_REQ_READ_MULTI:
582 case GATT_REQ_READ_MULTI_VAR:
583 p_cmd = attp_build_read_multi_cmd(op_code, payload_size,
584 p_msg->read_multi.num_handles,
585 p_msg->read_multi.handles);
586 break;
587
588 default:
589 break;
590 }
591
592 if (p_cmd == NULL) {
593 LOG_WARN(
594 "Unable to build proper GATT message to send to peer device "
595 "op_code:%hhu",
596 op_code);
597 return GATT_NO_RESOURCES;
598 }
599
600 return attp_cl_send_cmd(tcb, p_clcb, op_code, p_cmd);
601 }
602