1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20 #include <stddef.h>
21 #include <errno.h>
22 #include "ble_hs_priv.h"
23
24 static uint16_t ble_att_preferred_mtu_val;
25
26 /** Dispatch table for incoming ATT requests. Sorted by op code. */
27 typedef int ble_att_rx_fn(uint16_t conn_handle, struct os_mbuf **om);
28 struct ble_att_rx_dispatch_entry {
29 uint8_t bde_op;
30 ble_att_rx_fn *bde_fn;
31 };
32
33 /** Dispatch table for incoming ATT commands. Must be ordered by op code. */
34 static const struct ble_att_rx_dispatch_entry ble_att_rx_dispatch[] = {
35 { BLE_ATT_OP_ERROR_RSP, ble_att_clt_rx_error },
36 { BLE_ATT_OP_MTU_REQ, ble_att_svr_rx_mtu },
37 { BLE_ATT_OP_MTU_RSP, ble_att_clt_rx_mtu },
38 { BLE_ATT_OP_FIND_INFO_REQ, ble_att_svr_rx_find_info },
39 { BLE_ATT_OP_FIND_INFO_RSP, ble_att_clt_rx_find_info },
40 { BLE_ATT_OP_FIND_TYPE_VALUE_REQ, ble_att_svr_rx_find_type_value },
41 { BLE_ATT_OP_FIND_TYPE_VALUE_RSP, ble_att_clt_rx_find_type_value },
42 { BLE_ATT_OP_READ_TYPE_REQ, ble_att_svr_rx_read_type },
43 { BLE_ATT_OP_READ_TYPE_RSP, ble_att_clt_rx_read_type },
44 { BLE_ATT_OP_READ_REQ, ble_att_svr_rx_read },
45 { BLE_ATT_OP_READ_RSP, ble_att_clt_rx_read },
46 { BLE_ATT_OP_READ_BLOB_REQ, ble_att_svr_rx_read_blob },
47 { BLE_ATT_OP_READ_BLOB_RSP, ble_att_clt_rx_read_blob },
48 { BLE_ATT_OP_READ_MULT_REQ, ble_att_svr_rx_read_mult },
49 { BLE_ATT_OP_READ_MULT_RSP, ble_att_clt_rx_read_mult },
50 { BLE_ATT_OP_READ_GROUP_TYPE_REQ, ble_att_svr_rx_read_group_type },
51 { BLE_ATT_OP_READ_GROUP_TYPE_RSP, ble_att_clt_rx_read_group_type },
52 { BLE_ATT_OP_WRITE_REQ, ble_att_svr_rx_write },
53 { BLE_ATT_OP_WRITE_RSP, ble_att_clt_rx_write },
54 { BLE_ATT_OP_PREP_WRITE_REQ, ble_att_svr_rx_prep_write },
55 { BLE_ATT_OP_PREP_WRITE_RSP, ble_att_clt_rx_prep_write },
56 { BLE_ATT_OP_EXEC_WRITE_REQ, ble_att_svr_rx_exec_write },
57 { BLE_ATT_OP_EXEC_WRITE_RSP, ble_att_clt_rx_exec_write },
58 { BLE_ATT_OP_NOTIFY_REQ, ble_att_svr_rx_notify },
59 { BLE_ATT_OP_INDICATE_REQ, ble_att_svr_rx_indicate },
60 { BLE_ATT_OP_INDICATE_RSP, ble_att_clt_rx_indicate },
61 { BLE_ATT_OP_WRITE_CMD, ble_att_svr_rx_write_no_rsp },
62 };
63
64 #define BLE_ATT_RX_DISPATCH_SZ \
65 (sizeof ble_att_rx_dispatch / sizeof ble_att_rx_dispatch[0])
66
67 STATS_SECT_DECL(ble_att_stats) ble_att_stats;
68 STATS_NAME_START(ble_att_stats)
STATS_NAME(ble_att_stats,error_rsp_rx)69 STATS_NAME(ble_att_stats, error_rsp_rx)
70 STATS_NAME(ble_att_stats, error_rsp_tx)
71 STATS_NAME(ble_att_stats, mtu_req_rx)
72 STATS_NAME(ble_att_stats, mtu_req_tx)
73 STATS_NAME(ble_att_stats, mtu_rsp_rx)
74 STATS_NAME(ble_att_stats, mtu_rsp_tx)
75 STATS_NAME(ble_att_stats, find_info_req_rx)
76 STATS_NAME(ble_att_stats, find_info_req_tx)
77 STATS_NAME(ble_att_stats, find_info_rsp_rx)
78 STATS_NAME(ble_att_stats, find_info_rsp_tx)
79 STATS_NAME(ble_att_stats, find_type_value_req_rx)
80 STATS_NAME(ble_att_stats, find_type_value_req_tx)
81 STATS_NAME(ble_att_stats, find_type_value_rsp_rx)
82 STATS_NAME(ble_att_stats, find_type_value_rsp_tx)
83 STATS_NAME(ble_att_stats, read_type_req_rx)
84 STATS_NAME(ble_att_stats, read_type_req_tx)
85 STATS_NAME(ble_att_stats, read_type_rsp_rx)
86 STATS_NAME(ble_att_stats, read_type_rsp_tx)
87 STATS_NAME(ble_att_stats, read_req_rx)
88 STATS_NAME(ble_att_stats, read_req_tx)
89 STATS_NAME(ble_att_stats, read_rsp_rx)
90 STATS_NAME(ble_att_stats, read_rsp_tx)
91 STATS_NAME(ble_att_stats, read_blob_req_rx)
92 STATS_NAME(ble_att_stats, read_blob_req_tx)
93 STATS_NAME(ble_att_stats, read_blob_rsp_rx)
94 STATS_NAME(ble_att_stats, read_blob_rsp_tx)
95 STATS_NAME(ble_att_stats, read_mult_req_rx)
96 STATS_NAME(ble_att_stats, read_mult_req_tx)
97 STATS_NAME(ble_att_stats, read_mult_rsp_rx)
98 STATS_NAME(ble_att_stats, read_mult_rsp_tx)
99 STATS_NAME(ble_att_stats, read_group_type_req_rx)
100 STATS_NAME(ble_att_stats, read_group_type_req_tx)
101 STATS_NAME(ble_att_stats, read_group_type_rsp_rx)
102 STATS_NAME(ble_att_stats, read_group_type_rsp_tx)
103 STATS_NAME(ble_att_stats, write_req_rx)
104 STATS_NAME(ble_att_stats, write_req_tx)
105 STATS_NAME(ble_att_stats, write_rsp_rx)
106 STATS_NAME(ble_att_stats, write_rsp_tx)
107 STATS_NAME(ble_att_stats, prep_write_req_rx)
108 STATS_NAME(ble_att_stats, prep_write_req_tx)
109 STATS_NAME(ble_att_stats, prep_write_rsp_rx)
110 STATS_NAME(ble_att_stats, prep_write_rsp_tx)
111 STATS_NAME(ble_att_stats, exec_write_req_rx)
112 STATS_NAME(ble_att_stats, exec_write_req_tx)
113 STATS_NAME(ble_att_stats, exec_write_rsp_rx)
114 STATS_NAME(ble_att_stats, exec_write_rsp_tx)
115 STATS_NAME(ble_att_stats, notify_req_rx)
116 STATS_NAME(ble_att_stats, notify_req_tx)
117 STATS_NAME(ble_att_stats, indicate_req_rx)
118 STATS_NAME(ble_att_stats, indicate_req_tx)
119 STATS_NAME(ble_att_stats, indicate_rsp_rx)
120 STATS_NAME(ble_att_stats, indicate_rsp_tx)
121 STATS_NAME(ble_att_stats, write_cmd_rx)
122 STATS_NAME(ble_att_stats, write_cmd_tx)
123 STATS_NAME_END(ble_att_stats)
124
125 static const struct ble_att_rx_dispatch_entry *
126 ble_att_rx_dispatch_entry_find(uint8_t op)
127 {
128 const struct ble_att_rx_dispatch_entry *entry;
129 int i;
130
131 for (i = 0; i < BLE_ATT_RX_DISPATCH_SZ; i++) {
132 entry = ble_att_rx_dispatch + i;
133 if (entry->bde_op == op) {
134 return entry;
135 }
136
137 if (entry->bde_op > op) {
138 break;
139 }
140 }
141
142 return NULL;
143 }
144
145 int
ble_att_conn_chan_find(uint16_t conn_handle,struct ble_hs_conn ** out_conn,struct ble_l2cap_chan ** out_chan)146 ble_att_conn_chan_find(uint16_t conn_handle, struct ble_hs_conn **out_conn,
147 struct ble_l2cap_chan **out_chan)
148 {
149 return ble_hs_misc_conn_chan_find(conn_handle, BLE_L2CAP_CID_ATT,
150 out_conn, out_chan);
151 }
152
153 void
ble_att_inc_tx_stat(uint8_t att_op)154 ble_att_inc_tx_stat(uint8_t att_op)
155 {
156 switch (att_op) {
157 case BLE_ATT_OP_ERROR_RSP:
158 STATS_INC(ble_att_stats, error_rsp_tx);
159 break;
160
161 case BLE_ATT_OP_MTU_REQ:
162 STATS_INC(ble_att_stats, mtu_req_tx);
163 break;
164
165 case BLE_ATT_OP_MTU_RSP:
166 STATS_INC(ble_att_stats, mtu_rsp_tx);
167 break;
168
169 case BLE_ATT_OP_FIND_INFO_REQ:
170 STATS_INC(ble_att_stats, find_info_req_tx);
171 break;
172
173 case BLE_ATT_OP_FIND_INFO_RSP:
174 STATS_INC(ble_att_stats, find_info_rsp_tx);
175 break;
176
177 case BLE_ATT_OP_FIND_TYPE_VALUE_REQ:
178 STATS_INC(ble_att_stats, find_type_value_req_tx);
179 break;
180
181 case BLE_ATT_OP_FIND_TYPE_VALUE_RSP:
182 STATS_INC(ble_att_stats, find_type_value_rsp_tx);
183 break;
184
185 case BLE_ATT_OP_READ_TYPE_REQ:
186 STATS_INC(ble_att_stats, read_type_req_tx);
187 break;
188
189 case BLE_ATT_OP_READ_TYPE_RSP:
190 STATS_INC(ble_att_stats, read_type_rsp_tx);
191 break;
192
193 case BLE_ATT_OP_READ_REQ:
194 STATS_INC(ble_att_stats, read_req_tx);
195 break;
196
197 case BLE_ATT_OP_READ_RSP:
198 STATS_INC(ble_att_stats, read_rsp_tx);
199 break;
200
201 case BLE_ATT_OP_READ_BLOB_REQ:
202 STATS_INC(ble_att_stats, read_blob_req_tx);
203 break;
204
205 case BLE_ATT_OP_READ_BLOB_RSP:
206 STATS_INC(ble_att_stats, read_blob_rsp_tx);
207 break;
208
209 case BLE_ATT_OP_READ_MULT_REQ:
210 STATS_INC(ble_att_stats, read_mult_req_tx);
211 break;
212
213 case BLE_ATT_OP_READ_MULT_RSP:
214 STATS_INC(ble_att_stats, read_mult_rsp_tx);
215 break;
216
217 case BLE_ATT_OP_READ_GROUP_TYPE_REQ:
218 STATS_INC(ble_att_stats, read_group_type_req_tx);
219 break;
220
221 case BLE_ATT_OP_READ_GROUP_TYPE_RSP:
222 STATS_INC(ble_att_stats, read_group_type_rsp_tx);
223 break;
224
225 case BLE_ATT_OP_WRITE_REQ:
226 STATS_INC(ble_att_stats, write_req_tx);
227 break;
228
229 case BLE_ATT_OP_WRITE_RSP:
230 STATS_INC(ble_att_stats, write_rsp_tx);
231 break;
232
233 case BLE_ATT_OP_PREP_WRITE_REQ:
234 STATS_INC(ble_att_stats, prep_write_req_tx);
235 break;
236
237 case BLE_ATT_OP_PREP_WRITE_RSP:
238 STATS_INC(ble_att_stats, prep_write_rsp_tx);
239 break;
240
241 case BLE_ATT_OP_EXEC_WRITE_REQ:
242 STATS_INC(ble_att_stats, exec_write_req_tx);
243 break;
244
245 case BLE_ATT_OP_EXEC_WRITE_RSP:
246 STATS_INC(ble_att_stats, exec_write_rsp_tx);
247 break;
248
249 case BLE_ATT_OP_NOTIFY_REQ:
250 STATS_INC(ble_att_stats, notify_req_tx);
251 break;
252
253 case BLE_ATT_OP_INDICATE_REQ:
254 STATS_INC(ble_att_stats, indicate_req_tx);
255 break;
256
257 case BLE_ATT_OP_INDICATE_RSP:
258 STATS_INC(ble_att_stats, indicate_rsp_tx);
259 break;
260
261 case BLE_ATT_OP_WRITE_CMD:
262 STATS_INC(ble_att_stats, write_cmd_tx);
263 break;
264
265 default:
266 break;
267 }
268 }
269
270 static void
ble_att_inc_rx_stat(uint8_t att_op)271 ble_att_inc_rx_stat(uint8_t att_op)
272 {
273 switch (att_op) {
274 case BLE_ATT_OP_ERROR_RSP:
275 STATS_INC(ble_att_stats, error_rsp_rx);
276 break;
277
278 case BLE_ATT_OP_MTU_REQ:
279 STATS_INC(ble_att_stats, mtu_req_rx);
280 break;
281
282 case BLE_ATT_OP_MTU_RSP:
283 STATS_INC(ble_att_stats, mtu_rsp_rx);
284 break;
285
286 case BLE_ATT_OP_FIND_INFO_REQ:
287 STATS_INC(ble_att_stats, find_info_req_rx);
288 break;
289
290 case BLE_ATT_OP_FIND_INFO_RSP:
291 STATS_INC(ble_att_stats, find_info_rsp_rx);
292 break;
293
294 case BLE_ATT_OP_FIND_TYPE_VALUE_REQ:
295 STATS_INC(ble_att_stats, find_type_value_req_rx);
296 break;
297
298 case BLE_ATT_OP_FIND_TYPE_VALUE_RSP:
299 STATS_INC(ble_att_stats, find_type_value_rsp_rx);
300 break;
301
302 case BLE_ATT_OP_READ_TYPE_REQ:
303 STATS_INC(ble_att_stats, read_type_req_rx);
304 break;
305
306 case BLE_ATT_OP_READ_TYPE_RSP:
307 STATS_INC(ble_att_stats, read_type_rsp_rx);
308 break;
309
310 case BLE_ATT_OP_READ_REQ:
311 STATS_INC(ble_att_stats, read_req_rx);
312 break;
313
314 case BLE_ATT_OP_READ_RSP:
315 STATS_INC(ble_att_stats, read_rsp_rx);
316 break;
317
318 case BLE_ATT_OP_READ_BLOB_REQ:
319 STATS_INC(ble_att_stats, read_blob_req_rx);
320 break;
321
322 case BLE_ATT_OP_READ_BLOB_RSP:
323 STATS_INC(ble_att_stats, read_blob_rsp_rx);
324 break;
325
326 case BLE_ATT_OP_READ_MULT_REQ:
327 STATS_INC(ble_att_stats, read_mult_req_rx);
328 break;
329
330 case BLE_ATT_OP_READ_MULT_RSP:
331 STATS_INC(ble_att_stats, read_mult_rsp_rx);
332 break;
333
334 case BLE_ATT_OP_READ_GROUP_TYPE_REQ:
335 STATS_INC(ble_att_stats, read_group_type_req_rx);
336 break;
337
338 case BLE_ATT_OP_READ_GROUP_TYPE_RSP:
339 STATS_INC(ble_att_stats, read_group_type_rsp_rx);
340 break;
341
342 case BLE_ATT_OP_WRITE_REQ:
343 STATS_INC(ble_att_stats, write_req_rx);
344 break;
345
346 case BLE_ATT_OP_WRITE_RSP:
347 STATS_INC(ble_att_stats, write_rsp_rx);
348 break;
349
350 case BLE_ATT_OP_PREP_WRITE_REQ:
351 STATS_INC(ble_att_stats, prep_write_req_rx);
352 break;
353
354 case BLE_ATT_OP_PREP_WRITE_RSP:
355 STATS_INC(ble_att_stats, prep_write_rsp_rx);
356 break;
357
358 case BLE_ATT_OP_EXEC_WRITE_REQ:
359 STATS_INC(ble_att_stats, exec_write_req_rx);
360 break;
361
362 case BLE_ATT_OP_EXEC_WRITE_RSP:
363 STATS_INC(ble_att_stats, exec_write_rsp_rx);
364 break;
365
366 case BLE_ATT_OP_NOTIFY_REQ:
367 STATS_INC(ble_att_stats, notify_req_rx);
368 break;
369
370 case BLE_ATT_OP_INDICATE_REQ:
371 STATS_INC(ble_att_stats, indicate_req_rx);
372 break;
373
374 case BLE_ATT_OP_INDICATE_RSP:
375 STATS_INC(ble_att_stats, indicate_rsp_rx);
376 break;
377
378 case BLE_ATT_OP_WRITE_CMD:
379 STATS_INC(ble_att_stats, write_cmd_rx);
380 break;
381
382 default:
383 break;
384 }
385 }
386
387 void
ble_att_truncate_to_mtu(const struct ble_l2cap_chan * att_chan,struct os_mbuf * txom)388 ble_att_truncate_to_mtu(const struct ble_l2cap_chan *att_chan,
389 struct os_mbuf *txom)
390 {
391 int32_t extra_len;
392 uint16_t mtu;
393
394 mtu = ble_att_chan_mtu(att_chan);
395 extra_len = OS_MBUF_PKTLEN(txom) - mtu;
396 if (extra_len > 0) {
397 os_mbuf_adj(txom, -extra_len);
398 }
399 }
400
401 uint16_t
ble_att_mtu(uint16_t conn_handle)402 ble_att_mtu(uint16_t conn_handle)
403 {
404 struct ble_l2cap_chan *chan;
405 struct ble_hs_conn *conn;
406 uint16_t mtu;
407 int rc;
408
409 ble_hs_lock();
410
411 rc = ble_att_conn_chan_find(conn_handle, &conn, &chan);
412 if (rc == 0) {
413 mtu = ble_att_chan_mtu(chan);
414 } else {
415 mtu = 0;
416 }
417
418 ble_hs_unlock();
419
420 return mtu;
421 }
422
423 void
ble_att_set_peer_mtu(struct ble_l2cap_chan * chan,uint16_t peer_mtu)424 ble_att_set_peer_mtu(struct ble_l2cap_chan *chan, uint16_t peer_mtu)
425 {
426 if (peer_mtu < BLE_ATT_MTU_DFLT) {
427 peer_mtu = BLE_ATT_MTU_DFLT;
428 }
429
430 chan->peer_mtu = peer_mtu;
431 }
432
433 uint16_t
ble_att_chan_mtu(const struct ble_l2cap_chan * chan)434 ble_att_chan_mtu(const struct ble_l2cap_chan *chan)
435 {
436 uint16_t mtu;
437
438 /* If either side has not exchanged MTU size, use the default. Otherwise,
439 * use the lesser of the two exchanged values.
440 */
441 if (!(ble_l2cap_is_mtu_req_sent(chan)) ||
442 chan->peer_mtu == 0) {
443
444 mtu = BLE_ATT_MTU_DFLT;
445 } else {
446 mtu = min(chan->my_mtu, chan->peer_mtu);
447 }
448
449 BLE_HS_DBG_ASSERT(mtu >= BLE_ATT_MTU_DFLT);
450
451 return mtu;
452 }
453
454 static void
ble_att_rx_handle_unknown_request(uint8_t op,uint16_t conn_handle,struct os_mbuf ** om)455 ble_att_rx_handle_unknown_request(uint8_t op, uint16_t conn_handle,
456 struct os_mbuf **om)
457 {
458 /* If this is command (bit6 is set to 1), do nothing */
459 if (op & 0x40) {
460 return;
461 }
462
463 os_mbuf_adj(*om, OS_MBUF_PKTLEN(*om));
464 ble_att_svr_tx_error_rsp(conn_handle, *om, op, 0,
465 BLE_ATT_ERR_REQ_NOT_SUPPORTED);
466
467 *om = NULL;
468 }
469
470 static int
ble_att_rx(struct ble_l2cap_chan * chan)471 ble_att_rx(struct ble_l2cap_chan *chan)
472 {
473 const struct ble_att_rx_dispatch_entry *entry;
474 uint8_t op;
475 uint16_t conn_handle;
476 struct os_mbuf **om;
477 int rc;
478
479 conn_handle = ble_l2cap_get_conn_handle(chan);
480 if (conn_handle == BLE_HS_CONN_HANDLE_NONE) {
481 return BLE_HS_ENOTCONN;
482 }
483
484 om = &chan->rx_buf;
485 BLE_HS_DBG_ASSERT(*om != NULL);
486
487 rc = os_mbuf_copydata(*om, 0, 1, &op);
488 if (rc != 0) {
489 return BLE_HS_EMSGSIZE;
490 }
491
492 entry = ble_att_rx_dispatch_entry_find(op);
493 if (entry == NULL) {
494 ble_att_rx_handle_unknown_request(op, conn_handle, om);
495 return BLE_HS_ENOTSUP;
496 }
497
498 ble_att_inc_rx_stat(op);
499
500 /* Strip L2CAP ATT header from the front of the mbuf. */
501 os_mbuf_adj(*om, 1);
502
503 rc = entry->bde_fn(conn_handle, om);
504 if (rc != 0) {
505 if (rc == BLE_HS_ENOTSUP) {
506 ble_att_rx_handle_unknown_request(op, conn_handle, om);
507 }
508 return rc;
509 }
510
511 return 0;
512 }
513
514 uint16_t
ble_att_preferred_mtu(void)515 ble_att_preferred_mtu(void)
516 {
517 return ble_att_preferred_mtu_val;
518 }
519
520 int
ble_att_set_preferred_mtu(uint16_t mtu)521 ble_att_set_preferred_mtu(uint16_t mtu)
522 {
523 struct ble_l2cap_chan *chan;
524 struct ble_hs_conn *conn;
525 int i;
526
527 if (mtu < BLE_ATT_MTU_DFLT) {
528 return BLE_HS_EINVAL;
529 }
530 if (mtu > BLE_ATT_MTU_MAX) {
531 return BLE_HS_EINVAL;
532 }
533
534 ble_att_preferred_mtu_val = mtu;
535
536 /* Set my_mtu for established connections that haven't exchanged. */
537 ble_hs_lock();
538
539 i = 0;
540 while ((conn = ble_hs_conn_find_by_idx(i)) != NULL) {
541 chan = ble_hs_conn_chan_find_by_scid(conn, BLE_L2CAP_CID_ATT);
542 BLE_HS_DBG_ASSERT(chan != NULL);
543
544 if (!(chan->flags & BLE_L2CAP_CHAN_F_TXED_MTU)) {
545 chan->my_mtu = mtu;
546 }
547
548 i++;
549 }
550
551 ble_hs_unlock();
552
553 return 0;
554 }
555
556 struct ble_l2cap_chan *
ble_att_create_chan(uint16_t conn_handle)557 ble_att_create_chan(uint16_t conn_handle)
558 {
559 struct ble_l2cap_chan *chan;
560
561 chan = ble_l2cap_chan_alloc(conn_handle);
562 if (chan == NULL) {
563 return NULL;
564 }
565
566 chan->scid = BLE_L2CAP_CID_ATT;
567 chan->dcid = BLE_L2CAP_CID_ATT;
568 chan->my_mtu = ble_att_preferred_mtu_val;
569 chan->rx_fn = ble_att_rx;
570
571 return chan;
572 }
573
574 int
ble_att_init(void)575 ble_att_init(void)
576 {
577 int rc;
578
579 ble_att_preferred_mtu_val = MYNEWT_VAL(BLE_ATT_PREFERRED_MTU);
580
581 rc = stats_init_and_reg(
582 STATS_HDR(ble_att_stats), STATS_SIZE_INIT_PARMS(ble_att_stats,
583 STATS_SIZE_32), STATS_NAME_INIT_PARMS(ble_att_stats), "ble_att");
584 if (rc != 0) {
585 return BLE_HS_EOS;
586 }
587
588 return 0;
589 }
590