att_protocol.c 20 KB

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