seos_att.c 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492
  1. #include "seos_att_i.h"
  2. #define TAG "SeosAtt"
  3. struct att_read_by_type_req {
  4. uint8_t opcode;
  5. uint16_t start_handle;
  6. uint16_t end_handle;
  7. union {
  8. uint16_t short_uuid;
  9. uint8_t long_uuid[16];
  10. } attribute_type;
  11. } __packed;
  12. struct att_find_info_req {
  13. uint8_t opcode;
  14. uint16_t start_handle;
  15. uint16_t end_handle;
  16. } __packed;
  17. struct att_find_type_value_req {
  18. uint8_t opcode;
  19. uint16_t start_handle;
  20. uint16_t end_handle;
  21. uint16_t att_type;
  22. uint8_t att_value[0];
  23. } __packed;
  24. struct att_write_req {
  25. uint8_t opcode;
  26. uint16_t handle;
  27. } __packed;
  28. static uint8_t seos_reader_service_backwards[] =
  29. {0x02, 0x00, 0x00, 0x7a, 0x17, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x00, 0x98, 0x00, 0x00};
  30. static uint8_t seos_cred_service_backwards[] =
  31. {0x02, 0x00, 0x00, 0x7a, 0x17, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00, 0x01, 0x98, 0x00, 0x00};
  32. SeosAtt* seos_att_alloc(Seos* seos) {
  33. SeosAtt* seos_att = malloc(sizeof(SeosAtt));
  34. memset(seos_att, 0, sizeof(SeosAtt));
  35. seos_att->seos = seos;
  36. seos_att->seos_l2cap = seos_l2cap_alloc(seos);
  37. seos_l2cap_set_receive_callback(seos_att->seos_l2cap, seos_att_process_payload, seos_att);
  38. seos_l2cap_set_central_connection_callback(
  39. seos_att->seos_l2cap, seos_att_central_connection_start, seos_att);
  40. seos_att->tx_buf = bit_buffer_alloc(128);
  41. seos_att->tx_mtu = 0;
  42. seos_att->rx_mtu = 0x0200;
  43. return seos_att;
  44. }
  45. void seos_att_free(SeosAtt* seos_att) {
  46. furi_assert(seos_att);
  47. seos_l2cap_free(seos_att->seos_l2cap);
  48. bit_buffer_free(seos_att->tx_buf);
  49. free(seos_att);
  50. }
  51. void seos_att_start(SeosAtt* seos_att, BleMode mode, FlowMode flow_mode) {
  52. seos_att->ble_mode = mode;
  53. seos_att->flow_mode = flow_mode;
  54. seos_l2cap_start(seos_att->seos_l2cap, mode, flow_mode);
  55. }
  56. void seos_att_stop(SeosAtt* seos_att) {
  57. seos_l2cap_stop(seos_att->seos_l2cap);
  58. }
  59. void seos_att_central_connection_start(void* context) {
  60. SeosAtt* seos_att = (SeosAtt*)context;
  61. FURI_LOG_D(TAG, "central connnection start");
  62. bit_buffer_reset(seos_att->tx_buf);
  63. if(seos_att->flow_mode == FLOW_READER) {
  64. uint16_t start = 0x0001;
  65. uint16_t end = 0xffff;
  66. uint16_t attribute_type = PRIMARY_SERVICE;
  67. bit_buffer_append_byte(seos_att->tx_buf, ATT_FIND_BY_TYPE_VALUE_REQ);
  68. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)&start, sizeof(start));
  69. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)&end, sizeof(end));
  70. bit_buffer_append_bytes(
  71. seos_att->tx_buf, (uint8_t*)&attribute_type, sizeof(attribute_type));
  72. bit_buffer_append_bytes(
  73. seos_att->tx_buf, seos_cred_service_backwards, sizeof(seos_cred_service_backwards));
  74. } else if(seos_att->flow_mode == FLOW_CRED) {
  75. // First thing to do with any new connection is the MTU
  76. bit_buffer_append_byte(seos_att->tx_buf, ATT_EXCHANGE_MTU_REQ);
  77. bit_buffer_append_bytes(
  78. seos_att->tx_buf, (uint8_t*)&seos_att->rx_mtu, sizeof(seos_att->rx_mtu));
  79. }
  80. seos_l2cap_send(seos_att->seos_l2cap, seos_att->tx_buf);
  81. }
  82. void seos_att_notify(SeosAtt* seos_att, uint16_t handle, BitBuffer* content) {
  83. seos_log_bitbuffer(TAG, "notify", content);
  84. size_t content_len = bit_buffer_get_size_bytes(content);
  85. BitBuffer* tx = bit_buffer_alloc(1 + sizeof(handle) + content_len);
  86. bit_buffer_append_byte(tx, ATT_HANDLE_VALUE_NTF);
  87. bit_buffer_append_bytes(tx, (uint8_t*)&handle, sizeof(handle));
  88. bit_buffer_append_bytes(tx, bit_buffer_get_data(content), content_len);
  89. seos_l2cap_send(seos_att->seos_l2cap, tx);
  90. bit_buffer_free(tx);
  91. }
  92. void seos_att_write_request(SeosAtt* seos_att, BitBuffer* content) {
  93. seos_log_bitbuffer(TAG, "write_request", content);
  94. size_t content_len = bit_buffer_get_size_bytes(content);
  95. BitBuffer* tx = bit_buffer_alloc(1 + sizeof(seos_att->value_handle) + content_len);
  96. bit_buffer_append_byte(tx, ATT_WRITE_CMD);
  97. bit_buffer_append_bytes(
  98. tx, (uint8_t*)&(seos_att->value_handle), sizeof(seos_att->value_handle));
  99. bit_buffer_append_bytes(tx, bit_buffer_get_data(content), content_len);
  100. seos_l2cap_send(seos_att->seos_l2cap, tx);
  101. bit_buffer_free(tx);
  102. }
  103. // TODO: figure out the proper name for data that comes in
  104. void seos_att_process_payload(void* context, BitBuffer* message) {
  105. SeosAtt* seos_att = (SeosAtt*)context;
  106. // seos_log_bitbuffer(TAG, "process payload", message);
  107. bit_buffer_reset(seos_att->tx_buf);
  108. const uint8_t* data = bit_buffer_get_data(message);
  109. uint8_t opcode = data[0];
  110. struct att_read_by_type_req* s;
  111. uint16_t* start_handle;
  112. uint16_t* end_handle;
  113. uint16_t attribute_type;
  114. size_t length = 0;
  115. if(seos_att->ble_mode == BLE_CENTRAL && ((opcode & 0x01) == 0x00)) {
  116. bool reject = true;
  117. FURI_LOG_I(
  118. TAG,
  119. "%s request(0x%02x) when operating as Central",
  120. reject ? "Rejecting" : "Ignoring",
  121. opcode);
  122. if(reject) {
  123. bit_buffer_append_byte(seos_att->tx_buf, ATT_ERROR_RSP);
  124. bit_buffer_append_byte(seos_att->tx_buf, opcode);
  125. bit_buffer_append_bytes(seos_att->tx_buf, data + 1, sizeof(uint16_t));
  126. bit_buffer_append_byte(seos_att->tx_buf, 0x0a);
  127. seos_l2cap_send(seos_att->seos_l2cap, seos_att->tx_buf);
  128. }
  129. return;
  130. }
  131. switch(opcode) {
  132. case ATT_ERROR_RSP:
  133. uint8_t error_with_opcode = data[1];
  134. FURI_LOG_W(TAG, "Error with command %02x", error_with_opcode);
  135. break;
  136. case ATT_EXCHANGE_MTU_REQ: // MTU request
  137. // Trying a new way to copy the uint16_t
  138. seos_att->tx_mtu = *(uint16_t*)(data + 1);
  139. FURI_LOG_D(TAG, "MTU REQ = %04x", seos_att->tx_mtu);
  140. bit_buffer_append_byte(seos_att->tx_buf, ATT_EXCHANGE_MTU_RSP);
  141. bit_buffer_append_bytes(
  142. seos_att->tx_buf, (uint8_t*)&seos_att->rx_mtu, sizeof(seos_att->rx_mtu));
  143. break;
  144. case ATT_EXCHANGE_MTU_RSP:
  145. seos_att->tx_mtu = *(uint16_t*)(data + 1);
  146. FURI_LOG_D(TAG, "MTU RSP = %04x", seos_att->tx_mtu);
  147. uint16_t start = 0x0001;
  148. uint16_t end = 0xffff;
  149. attribute_type = PRIMARY_SERVICE;
  150. bit_buffer_append_byte(seos_att->tx_buf, ATT_FIND_BY_TYPE_VALUE_REQ);
  151. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)&start, sizeof(start));
  152. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)&end, sizeof(end));
  153. bit_buffer_append_bytes(
  154. seos_att->tx_buf, (uint8_t*)&attribute_type, sizeof(attribute_type));
  155. bit_buffer_append_bytes(
  156. seos_att->tx_buf,
  157. seos_reader_service_backwards,
  158. sizeof(seos_reader_service_backwards));
  159. break;
  160. case ATT_READ_BY_TYPE_REQ:
  161. s = (struct att_read_by_type_req*)(data);
  162. FURI_LOG_D(
  163. TAG,
  164. "ATT read by type %04x - %04x, type %04x",
  165. s->start_handle,
  166. s->end_handle,
  167. s->attribute_type.short_uuid);
  168. if(s->attribute_type.short_uuid == CHARACTERISTIC) {
  169. if(s->start_handle == 0x0006) {
  170. bit_buffer_append_byte(seos_att->tx_buf, ATT_READ_BY_TYPE_RSP);
  171. uint8_t response[] = {0x07, 0x07, 0x00, 0x20, 0x08, 0x00, 0x05, 0x2a};
  172. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  173. } else if(s->start_handle == 0x000a) {
  174. bit_buffer_append_byte(seos_att->tx_buf, ATT_READ_BY_TYPE_RSP);
  175. uint8_t flow_mode_byte = seos_att->flow_mode == FLOW_READER ? 0x00 : 0x01;
  176. uint8_t response[] = {0x15, 0x0b, 0x00, 0x14, 0x0c, 0x00, 0x02, 0x00,
  177. 0x00, 0x7a, 0x17, 0x00, 0x00, 0x80, 0x00, 0x10,
  178. 0x00, 0x00, flow_mode_byte, 0xaa, 0x00, 0x00};
  179. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  180. }
  181. } else if(s->attribute_type.short_uuid == DEVICE_NAME) {
  182. if(s->start_handle == 0x0001) {
  183. uint8_t response[] = {0x09, 0x03, 0x00, 0x46, 0x6c, 0x69, 0x70, 0x70, 0x65, 0x72};
  184. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  185. }
  186. }
  187. if(bit_buffer_get_size_bytes(seos_att->tx_buf) == 0) {
  188. FURI_LOG_W(TAG, "Return error");
  189. bit_buffer_reset(seos_att->tx_buf);
  190. bit_buffer_append_byte(seos_att->tx_buf, ATT_ERROR_RSP);
  191. bit_buffer_append_byte(seos_att->tx_buf, opcode);
  192. bit_buffer_append_bytes(
  193. seos_att->tx_buf, (uint8_t*)&s->start_handle, sizeof(s->start_handle));
  194. bit_buffer_append_byte(seos_att->tx_buf, ATT_ERROR_CODE_ATTRIBUTE_NOT_FOUND);
  195. }
  196. break;
  197. case ATT_READ_BY_TYPE_RSP:
  198. seos_log_buffer(
  199. TAG, "ATT_READ_BY_TYPE_RSP", (uint8_t*)data, bit_buffer_get_size_bytes(message));
  200. uint16_t* handle;
  201. handle = (uint16_t*)(data + 2);
  202. seos_att->characteristic_handle = *handle;
  203. // skip properties byte
  204. handle = (uint16_t*)(data + 5);
  205. seos_att->value_handle = *handle;
  206. *handle = *handle + 1;
  207. bit_buffer_append_byte(seos_att->tx_buf, ATT_FIND_INFORMATION_REQ);
  208. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)handle, sizeof(uint16_t));
  209. bit_buffer_append_bytes(
  210. seos_att->tx_buf, (uint8_t*)&seos_att->service_end_handle, sizeof(uint16_t));
  211. break;
  212. case ATT_READ_BY_GROUP_TYPE_REQ:
  213. s = (struct att_read_by_type_req*)(data);
  214. FURI_LOG_D(
  215. TAG,
  216. "ATT read by group type %04x - %04x, type %04x",
  217. s->start_handle,
  218. s->end_handle,
  219. s->attribute_type.short_uuid);
  220. if(s->attribute_type.short_uuid == PRIMARY_SERVICE) {
  221. if(s->start_handle == 0x0001) {
  222. bit_buffer_append_byte(seos_att->tx_buf, ATT_READ_BY_GROUP_TYPE_RSP);
  223. uint8_t response[] = {
  224. 0x06, 0x01, 0x00, 0x05, 0x00, 0x00, 0x18, 0x06, 0x00, 0x09, 0x00, 0x01, 0x18};
  225. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  226. } else if(s->start_handle == 0x000a) {
  227. bit_buffer_append_byte(seos_att->tx_buf, ATT_READ_BY_GROUP_TYPE_RSP);
  228. uint8_t flow_mode_byte = seos_att->flow_mode == FLOW_READER ? 0x00 : 0x01;
  229. uint8_t response[] = {0x14, 0x0a, 0x00, 0x0d, 0x00, 0x02, 0x00,
  230. 0x00, 0x7a, 0x17, 0x00, 0x00, 0x80, 0x00,
  231. 0x10, 0x00, 0x00, flow_mode_byte, 0x98, 0x00, 0x00};
  232. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  233. }
  234. }
  235. // Didn't match either attribute_type or start_handle
  236. if(bit_buffer_get_size_bytes(seos_att->tx_buf) == 0) {
  237. FURI_LOG_W(TAG, "Return error");
  238. bit_buffer_reset(seos_att->tx_buf);
  239. bit_buffer_append_byte(seos_att->tx_buf, ATT_ERROR_RSP);
  240. bit_buffer_append_byte(seos_att->tx_buf, opcode);
  241. bit_buffer_append_bytes(
  242. seos_att->tx_buf, (uint8_t*)&s->start_handle, sizeof(s->start_handle));
  243. bit_buffer_append_byte(seos_att->tx_buf, ATT_ERROR_CODE_ATTRIBUTE_NOT_FOUND);
  244. }
  245. break;
  246. case ATT_READ_BY_GROUP_TYPE_RSP:
  247. seos_log_buffer(
  248. TAG, "ATT_READ_BY_GROUP_TYPE_RSP", (uint8_t*)data, bit_buffer_get_size_bytes(message));
  249. // NOTE: this might not be actively used
  250. uint8_t size = data[1];
  251. size_t i = 2;
  252. do {
  253. start_handle = (uint16_t*)(data + i);
  254. end_handle = (uint16_t*)(data + sizeof(uint16_t) + i);
  255. // +4 for 2 uint16_t
  256. if(size == (sizeof(seos_cred_service_backwards) + 4)) {
  257. if(memcmp(
  258. seos_cred_service_backwards,
  259. data + i + 4,
  260. sizeof(seos_cred_service_backwards)) == 0) {
  261. seos_att->service_start_handle = *start_handle;
  262. seos_att->service_end_handle = *end_handle;
  263. }
  264. }
  265. i += size;
  266. } while(i < bit_buffer_get_size_bytes(message));
  267. *end_handle = *end_handle + 1;
  268. bit_buffer_append_byte(seos_att->tx_buf, ATT_READ_BY_GROUP_TYPE_REQ);
  269. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)end_handle, sizeof(uint16_t));
  270. uint8_t suffix[] = {0xff, 0xff, 0x00, 0x28};
  271. bit_buffer_append_bytes(seos_att->tx_buf, suffix, sizeof(suffix));
  272. break;
  273. case ATT_FIND_INFORMATION_REQ:
  274. struct att_find_info_req* e = (struct att_find_info_req*)(data);
  275. FURI_LOG_D(TAG, "ATT find information %04x - %04x", e->start_handle, e->end_handle);
  276. bit_buffer_append_byte(seos_att->tx_buf, ATT_FIND_INFORMATION_RSP);
  277. if(e->start_handle == 0x0009) {
  278. uint8_t response[] = {0x01, 0x09, 0x00, 0x02, 0x29};
  279. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  280. } else if(e->start_handle == 0x000d) {
  281. uint8_t response[] = {0x01, 0x0d, 0x00, 0x02, 0x29};
  282. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  283. } else {
  284. FURI_LOG_W(TAG, "unhandled handle in ATT_FIND_INFORMATION_REQ");
  285. }
  286. break;
  287. case ATT_FIND_INFORMATION_RSP:
  288. seos_log_buffer(
  289. TAG, "ATT_FIND_INFORMATION_RSP", (uint8_t*)data, bit_buffer_get_size_bytes(message));
  290. // 05 01 3c00 0029 3d00 0229
  291. uint8_t format = data[1];
  292. if(format == 1) { // short UUID
  293. uint16_t* handle;
  294. uint16_t* uuid;
  295. size_t i = 2;
  296. do {
  297. handle = (uint16_t*)(data + i);
  298. uuid = (uint16_t*)(data + i + 2);
  299. i += 4;
  300. if(*uuid == CCCD) {
  301. seos_att->cccd_handle = *handle;
  302. }
  303. } while(i < bit_buffer_get_size_bytes(message));
  304. if(seos_att->cccd_handle > 0) {
  305. FURI_LOG_I(TAG, "Subscribe to phone/device");
  306. uint16_t value = ENABLE_NOTIFICATION_VALUE;
  307. bit_buffer_append_byte(seos_att->tx_buf, ATT_WRITE_CMD);
  308. bit_buffer_append_bytes(
  309. seos_att->tx_buf, (uint8_t*)&seos_att->cccd_handle, sizeof(uint16_t));
  310. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)&value, sizeof(value));
  311. }
  312. }
  313. break;
  314. case ATT_FIND_BY_TYPE_VALUE_REQ:
  315. struct att_find_type_value_req* t = (struct att_find_type_value_req*)(data);
  316. FURI_LOG_D(
  317. TAG,
  318. "ATT_FIND_BY_TYPE_VALUE_REQ %04x - %04x for %04x",
  319. t->start_handle,
  320. t->end_handle,
  321. t->att_type);
  322. if(t->att_type == PRIMARY_SERVICE) {
  323. bit_buffer_append_byte(seos_att->tx_buf, ATT_FIND_BY_TYPE_VALUE_RSP);
  324. if(seos_att->flow_mode == FLOW_CRED) {
  325. uint8_t response[] = {0x0a, 0x00, 0x0e, 0x00};
  326. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  327. } else if(seos_att->flow_mode == FLOW_READER) {
  328. uint8_t response[] = {0x0c, 0x00, 0x0e, 0x00};
  329. bit_buffer_append_bytes(seos_att->tx_buf, response, sizeof(response));
  330. }
  331. }
  332. break;
  333. case ATT_FIND_BY_TYPE_VALUE_RSP:
  334. seos_log_buffer(
  335. TAG, "ATT_FIND_BY_TYPE_VALUE_RSP", (uint8_t*)data, bit_buffer_get_size_bytes(message));
  336. start_handle = (uint16_t*)(data + 1);
  337. end_handle = (uint16_t*)(data + 3);
  338. seos_att->service_start_handle = *start_handle;
  339. seos_att->service_end_handle = *end_handle;
  340. attribute_type = CHARACTERISTIC;
  341. bit_buffer_append_byte(seos_att->tx_buf, ATT_READ_BY_TYPE_REQ);
  342. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)start_handle, sizeof(uint16_t));
  343. bit_buffer_append_bytes(seos_att->tx_buf, (uint8_t*)end_handle, sizeof(uint16_t));
  344. bit_buffer_append_bytes(
  345. seos_att->tx_buf, (uint8_t*)&attribute_type, sizeof(attribute_type));
  346. break;
  347. case ATT_WRITE_REQ:
  348. struct att_write_req* w = (struct att_write_req*)(data);
  349. length = bit_buffer_get_size_bytes(message) - sizeof(struct att_write_req);
  350. FURI_LOG_D(TAG, "ATT Write Req %d bytes to %04x", length, w->handle);
  351. if(w->handle == 0x0009) {
  352. bit_buffer_append_byte(seos_att->tx_buf, ATT_WRITE_RSP);
  353. } else if(w->handle == 0x000d) {
  354. uint16_t* value = (uint16_t*)(data + sizeof(struct att_write_req));
  355. if(*value == DISABLE_NOTIFICATION_VALUE) {
  356. FURI_LOG_I(TAG, "Unsubscribe");
  357. } else if(*value == ENABLE_NOTIFICATION_VALUE) {
  358. FURI_LOG_I(TAG, "Subscribe");
  359. if(seos_att->on_subscribe) {
  360. // comes in as 0x000d, but we need to use 0x000c: I'm sure there is a reason for this that I'm just not aware of
  361. seos_att->on_subscribe(seos_att->on_subscribe_context, w->handle - 1);
  362. bit_buffer_append_byte(seos_att->tx_buf, ATT_WRITE_RSP);
  363. } else {
  364. FURI_LOG_W(TAG, "No on_subscribe callback defined");
  365. }
  366. }
  367. }
  368. break;
  369. case ATT_WRITE_RSP:
  370. FURI_LOG_D(TAG, "ATT_WRITE_RSP");
  371. break;
  372. case ATT_WRITE_CMD:
  373. struct att_write_req* c = (struct att_write_req*)(data);
  374. length = bit_buffer_get_size_bytes(message) - sizeof(struct att_write_req);
  375. FURI_LOG_D(TAG, "ATT Write CMD %d bytes to %04x", length, c->handle);
  376. if(c->handle == 0x000c) {
  377. BitBuffer* attribute_value = bit_buffer_alloc(length);
  378. bit_buffer_append_bytes(
  379. attribute_value,
  380. bit_buffer_get_data(message) + sizeof(struct att_write_req),
  381. length);
  382. if(seos_att->write_request) {
  383. seos_att->write_request(seos_att->write_request_context, attribute_value);
  384. }
  385. bit_buffer_free(attribute_value);
  386. } else {
  387. seos_log_bitbuffer(TAG, "write to unsupported handle", message);
  388. }
  389. // No response to CMD expected
  390. break;
  391. case ATT_HANDLE_VALUE_NTF:
  392. struct att_write_req* n = (struct att_write_req*)(data);
  393. length = bit_buffer_get_size_bytes(message) - sizeof(struct att_write_req);
  394. FURI_LOG_D(TAG, "ATT handle value notify %d bytes to %04x", length, n->handle);
  395. if(n->handle == 0x000d) {
  396. if(seos_att->notify) {
  397. seos_att->notify(
  398. seos_att->notify_context,
  399. bit_buffer_get_data(message) + sizeof(struct att_write_req),
  400. length);
  401. }
  402. } else {
  403. FURI_LOG_W(TAG, "Notify with unhandled handle");
  404. }
  405. break;
  406. case ATT_HANDLE_VALUE_CFM:
  407. FURI_LOG_I(TAG, "Indication confirmation");
  408. break;
  409. default:
  410. FURI_LOG_W(TAG, "seos_att_process_message no handler for 0x%02x", opcode);
  411. break;
  412. }
  413. if(bit_buffer_get_size_bytes(seos_att->tx_buf) > 0) {
  414. seos_log_bitbuffer(TAG, "sending", seos_att->tx_buf);
  415. seos_l2cap_send(seos_att->seos_l2cap, seos_att->tx_buf);
  416. }
  417. }
  418. void seos_att_set_on_subscribe_callback(
  419. SeosAtt* seos_att,
  420. SeosAttOnSubscribeCallback callback,
  421. void* context) {
  422. seos_att->on_subscribe = callback;
  423. seos_att->on_subscribe_context = context;
  424. }
  425. void seos_att_set_write_request_callback(
  426. SeosAtt* seos_att,
  427. SeosAttWriteRequestCallback callback,
  428. void* context) {
  429. seos_att->write_request = callback;
  430. seos_att->write_request_context = context;
  431. }
  432. void seos_att_set_notify_callback(SeosAtt* seos_att, SeosAttNotifyCallback callback, void* context) {
  433. seos_att->notify = callback;
  434. seos_att->notify_context = context;
  435. }