CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
host_api_ble.cpp
Go to the documentation of this file.
1
17
20#include "esp_attr.h"
23
24#include "freertos/FreeRTOS.h"
25#include "freertos/semphr.h"
26
27#include <cstring>
28
35namespace pm = cdc::plugin_manager;
36
37extern "C" void* plg_get_active_plugin(void);
38
39namespace {
40
41constexpr uint16_t BLE_MAX_PAYLOAD = 244; // default-MTU payload budget
42constexpr uint32_t PLUGIN_SERVICE_HANDLE = 1; // only one plugin GATT service exists
43constexpr uint8_t MAX_PLUGIN_CHARS = cdc::hal::IBluetoothController::MAX_CHARS_PER_SERVICE;
44constexpr uint8_t WRITE_RING = 4;
45constexpr uint8_t NOTIFY_RING = 4;
46constexpr uint8_t MAX_DISC_CHARS = 8;
47
48// --- synchronisation between the NimBLE task and the plugin tick task ---
49SemaphoreHandle_t s_lock = nullptr;
50void lock_init() { if (!s_lock) s_lock = xSemaphoreCreateMutex(); }
51struct Guard {
52 bool held = false;
53 Guard() { if (s_lock) held = (xSemaphoreTake(s_lock, portMAX_DELAY) == pdTRUE); }
54 ~Guard() { if (held) xSemaphoreGive(s_lock); }
55};
56
57// --- peripheral (GATT server) state ---
58// NimBLE writes the assigned attribute handle here; it stays valid across a
59// deferred (BLE-disabled) registration that is only committed at enable().
60uint16_t s_value_handles[MAX_PLUGIN_CHARS];
61struct PeriphChar {
62 uint32_t write_action_id = 0;
63};
64struct {
65 void* plugin = nullptr;
66 bool active = false;
67 uint8_t uuid[16] = {};
68 uint8_t num_chars = 0;
69 PeriphChar chars[MAX_PLUGIN_CHARS];
70} s_periph;
71
72struct WriteEvt {
73 uint32_t char_handle;
74 uint16_t conn;
75 uint16_t len;
76 uint8_t data[BLE_MAX_PAYLOAD];
77};
78WriteEvt s_wring[WRITE_RING];
79volatile uint8_t s_wr_head = 0, s_wr_tail = 0; // head=produced, tail=consumed
80
81// Payload currently being delivered to a write action (tick-task only).
82uint32_t s_cur_write_char = 0;
83uint16_t s_cur_write_len = 0;
84uint8_t s_cur_write_buf[BLE_MAX_PAYLOAD];
85
86// --- central (GATT client) state ---
87struct NotifyEvt { uint16_t value_handle; uint16_t len; uint8_t data[BLE_MAX_PAYLOAD]; };
88struct WcEvt { uint16_t attr; int16_t status; };
89constexpr uint8_t WC_RING = 4;
90struct {
91 void* plugin = nullptr;
92 bool listeners_registered = false;
93 // Connection handle of the plugin's own central op; shared GATT-client
94 // callbacks ignore events from any other connection (e.g. the system BLE
95 // name resolver) so they don't clobber this state.
96 uint16_t conn = 0xFFFF;
97 uint32_t discover_action_id = 0;
98 uint32_t read_action_id = 0;
99 uint32_t notify_action_id = 0;
100 uint32_t write_action_id = 0;
101
102 // write-completion ring (drop-oldest keeps the latest status visible)
103 WcEvt wcring[WC_RING];
104 uint8_t wc_head = 0, wc_tail = 0;
105
106 // discovery result stash
107 ble_remote_char_t disc[MAX_DISC_CHARS];
108 uint8_t disc_count = 0;
109 bool fire_discovery = false;
110
111 // read result stash
112 uint8_t read_buf[BLE_MAX_PAYLOAD];
113 uint16_t read_len = 0;
114 bool fire_read = false;
115
116 // inbound notification ring
117 NotifyEvt nring[NOTIFY_RING];
118 uint8_t n_head = 0, n_tail = 0;
119} s_cen EXT_RAM_BSS_ATTR;
120
121// --- helpers ---
122
123IBluetoothController* ble() { return getBluetoothControllerInstance(); }
124
125bool ble_allowed() {
126 auto* p = static_cast<pm::Plugin*>(plg_get_active_plugin());
127 return p && p->manifest().capabilities.ble;
128}
129
130// Build a 16-byte little-endian UUID array from a stack-independent BleUuid.
131void uuid_to_bytes(const BleUuid& u, uint8_t out[16]) {
132 if (u.type == BleUuid::UUID_128) {
133 std::memcpy(out, u.u128, 16);
134 } else {
135 static const uint8_t base[16] = {
136 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80,
137 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
138 };
139 std::memcpy(out, base, 16);
140 out[12] = static_cast<uint8_t>(u.u16 & 0xFF);
141 out[13] = static_cast<uint8_t>(u.u16 >> 8);
142 }
143}
144
145// Reserved system service UUIDs (little-endian, BleUuid::from128 order).
146const uint8_t NUS_SVC[16] = { 0x9e,0xca,0xdc,0x24,0x0e,0xe5,0xa9,0xe0,
147 0x93,0xf3,0xa3,0xb5,0x01,0x00,0x40,0x6e };
148const uint8_t VCARD_SVC[16] = { 0x01,0x1A,0x8B,0x6A,0x9D,0x4C,0x6E,0x9A,
149 0x7A,0x4D,0x5D,0x8B,0x20,0x1F,0x2F,0x8E };
150const uint8_t GPG_SVC[16] = { 0x01,0x1A,0x8B,0x6A,0x9D,0x4C,0x6E,0x9A,
151 0x7A,0x4D,0x5D,0x8B,0x30,0x1F,0x2F,0x8E };
152
153// Block the Bluetooth SIG 16-bit range (so a plugin can never shadow HID,
154// Device Info, Battery, GAP, GATT, ...) and the system's 128-bit services.
155bool uuid_is_reserved(const uint8_t uuid[16]) {
156 static const uint8_t sig_base[12] = {
157 0xFB, 0x34, 0x9B, 0x5F, 0x80, 0x00, 0x00, 0x80, 0x00, 0x10, 0x00, 0x00
158 };
159 if (std::memcmp(uuid, sig_base, 12) == 0 && uuid[14] == 0 && uuid[15] == 0) {
160 return true;
161 }
162 return std::memcmp(uuid, NUS_SVC, 16) == 0
163 || std::memcmp(uuid, VCARD_SVC, 16) == 0
164 || std::memcmp(uuid, GPG_SVC, 16) == 0;
165}
166
167// --- central callback registration (host-side, fires on NimBLE/EventBus task) ---
168void on_discovery(uint16_t connHandle, const IBluetoothController::DiscoveredService* svc, bool complete) {
169 Guard g;
170 if (connHandle != s_cen.conn) return; // not our central op
171 if (svc) {
172 uint8_t n = svc->numCharacteristics;
173 if (n > MAX_DISC_CHARS) n = MAX_DISC_CHARS;
174 for (uint8_t i = 0; i < n; i++) {
175 uuid_to_bytes(svc->characteristics[i].uuid, s_cen.disc[i].uuid);
176 s_cen.disc[i].value_handle = svc->characteristics[i].valueHandle;
177 s_cen.disc[i].properties = svc->characteristics[i].properties;
178 s_cen.disc[i].reserved = 0;
179 }
180 s_cen.disc_count = n;
181 }
182 if (complete) s_cen.fire_discovery = true;
183}
184
185void on_char_read(uint16_t connHandle, uint16_t, const uint8_t* data, uint16_t len) {
186 Guard g;
187 if (connHandle != s_cen.conn) return; // not our central op
188 if (len > BLE_MAX_PAYLOAD) len = BLE_MAX_PAYLOAD;
189 std::memcpy(s_cen.read_buf, data, len);
190 s_cen.read_len = len;
191 s_cen.fire_read = true;
192}
193
194void on_notification(uint16_t, uint16_t attr, const uint8_t* data, uint16_t len) {
195 Guard g;
196 uint8_t next = static_cast<uint8_t>((s_cen.n_head + 1) % NOTIFY_RING);
197 if (next == s_cen.n_tail) return; // ring full: drop
198 NotifyEvt& e = s_cen.nring[s_cen.n_head];
199 if (len > BLE_MAX_PAYLOAD) len = BLE_MAX_PAYLOAD;
200 e.value_handle = attr;
201 e.len = len;
202 std::memcpy(e.data, data, len);
203 s_cen.n_head = next;
204}
205
206void on_write_complete(uint16_t connHandle, uint16_t attr, int status) {
207 Guard g;
208 if (connHandle != s_cen.conn) return; // not our central op
209 uint8_t next = static_cast<uint8_t>((s_cen.wc_head + 1) % WC_RING);
210 if (next == s_cen.wc_tail) {
211 // ring full: drop the oldest so the newest status survives
212 s_cen.wc_tail = static_cast<uint8_t>((s_cen.wc_tail + 1) % WC_RING);
213 }
214 WcEvt& e = s_cen.wcring[s_cen.wc_head];
215 e.attr = attr;
216 e.status = static_cast<int16_t>(status);
217 s_cen.wc_head = next;
218}
219
220void ensure_central_listeners() {
221 if (s_cen.listeners_registered) return;
222 auto* b = ble();
223 if (!b) return;
224 b->addServiceDiscoveryCallback(on_discovery);
225 b->addCharacteristicReadCallback(on_char_read);
226 b->addNotificationCallback(on_notification);
227 b->addWriteCompleteCallback(on_write_complete);
228 s_cen.listeners_registered = true;
229}
230
231} // namespace
232
233extern "C" {
234
235/* ---------------------------------------------------------------- state -- */
236
237bool host_ble_is_enabled(void) { auto* b = ble(); return b ? b->isEnabled() : false; }
238
239int host_ble_mac(uint8_t out[6]) {
240 if (!out) return HOST_ERR_INVALID_ARG;
241 auto* b = ble();
242 if (!b) return HOST_ERR_NOT_FOUND;
243 return b->getMacAddress(out) ? HOST_OK : HOST_ERR_GENERIC;
244}
245
246int host_ble_device_name(char* out, size_t out_size) {
247 if (!out || out_size == 0) return HOST_ERR_INVALID_ARG;
248 auto* b = ble();
249 if (!b) return HOST_ERR_NOT_FOUND;
250 const char* name = b->getDeviceName();
251 std::strncpy(out, name ? name : "", out_size - 1);
252 out[out_size - 1] = '\0';
253 return HOST_OK;
254}
255
256int8_t host_ble_rssi(void) { auto* b = ble(); return b ? b->getRssi() : 0; }
257
258/* ----------------------------------------------------- peripheral (GATT) -- */
259
260int host_ble_register_service(ble_service_def_t* def, ble_char_def_t* chars, uint32_t num_chars) {
261 if (!def || !chars || num_chars == 0) return HOST_ERR_INVALID_ARG;
262 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
263 auto* b = ble();
264 if (!b) return HOST_ERR_NOT_FOUND;
265 if (num_chars > MAX_PLUGIN_CHARS) return HOST_ERR_INVALID_ARG;
266 if (uuid_is_reserved(def->uuid)) return HOST_ERR_NO_CAPABILITY;
267
268 void* plugin = plg_get_active_plugin();
269 if (s_periph.active && s_periph.plugin != plugin) return HOST_ERR_BUSY;
270 if (s_periph.active) {
271 b->unregisterGattService(BleUuid::from128(s_periph.uuid));
272 s_periph = {};
273 }
274 lock_init();
275
276 GattCharacteristic gc[MAX_PLUGIN_CHARS] = {};
277 for (uint32_t i = 0; i < num_chars; i++) {
278 s_value_handles[i] = 0;
279 gc[i].uuid = BleUuid::from128(chars[i].uuid);
280 gc[i].properties = chars[i].properties;
281 gc[i].permissions = 0;
282 if (chars[i].properties & BLE_PROP_READ) gc[i].permissions |= cdc::hal::GattPerm::READ;
283 if (chars[i].properties & (BLE_PROP_WRITE | BLE_PROP_WRITE_NO_RSP))
285 gc[i].valueHandle = &s_value_handles[i];
286 const uint32_t char_handle = i + 1;
287 if (chars[i].properties & (BLE_PROP_WRITE | BLE_PROP_WRITE_NO_RSP)) {
288 gc[i].onWrite = [char_handle](uint16_t conn, uint16_t, const uint8_t* d, uint16_t l) -> int {
289 Guard g;
290 uint8_t next = static_cast<uint8_t>((s_wr_head + 1) % WRITE_RING);
291 if (next == s_wr_tail) return 0; // ring full: drop
292 WriteEvt& e = s_wring[s_wr_head];
293 if (l > BLE_MAX_PAYLOAD) l = BLE_MAX_PAYLOAD;
294 e.char_handle = char_handle;
295 e.conn = conn;
296 e.len = l;
297 std::memcpy(e.data, d, l);
298 s_wr_head = next;
299 return 0;
300 };
301 }
302 }
303
304 GattServiceDef svc{};
305 svc.uuid = BleUuid::from128(def->uuid);
306 svc.characteristics = gc;
307 svc.numCharacteristics = static_cast<uint8_t>(num_chars);
308
309 if (!b->registerGattService(svc, /*pluginReserved=*/true)) return HOST_ERR_BUSY;
310
311 s_periph.plugin = plugin;
312 s_periph.active = true;
313 std::memcpy(s_periph.uuid, def->uuid, 16);
314 s_periph.num_chars = static_cast<uint8_t>(num_chars);
315 for (uint32_t i = 0; i < num_chars; i++) {
316 s_periph.chars[i].write_action_id = chars[i].write_action_id;
317 chars[i].char_handle = i + 1;
318 }
319 def->service_handle = PLUGIN_SERVICE_HANDLE;
320 return HOST_OK;
321}
322
323int host_ble_unregister_service(uint32_t service_handle) {
324 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
325 if (!s_periph.active || s_periph.plugin != plg_get_active_plugin()) return HOST_ERR_NOT_FOUND;
326 if (service_handle != PLUGIN_SERVICE_HANDLE) return HOST_ERR_NOT_FOUND;
327 auto* b = ble();
328 if (b) b->unregisterGattService(BleUuid::from128(s_periph.uuid));
329 s_periph = {};
330 return HOST_OK;
331}
332
333static int periph_send(uint32_t char_handle, const uint8_t* data, size_t len, bool indicate) {
334 if (!data && len) return HOST_ERR_INVALID_ARG;
335 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
336 if (!s_periph.active || s_periph.plugin != plg_get_active_plugin()) return HOST_ERR_NOT_FOUND;
337 if (char_handle < 1 || char_handle > s_periph.num_chars) return HOST_ERR_INVALID_ARG;
338 auto* b = ble();
339 if (!b) return HOST_ERR_NOT_FOUND;
340 uint16_t vh = s_value_handles[char_handle - 1];
341 uint16_t conn = b->getConnectionHandle();
342 if (conn == 0xFFFF) return HOST_ERR_NOT_FOUND;
343 bool ok = indicate ? b->sendIndication(conn, vh, data, static_cast<uint16_t>(len))
344 : b->sendNotification(conn, vh, data, static_cast<uint16_t>(len));
345 return ok ? HOST_OK : HOST_ERR_GENERIC;
346}
347
348int host_ble_send_notification(uint32_t char_handle, const uint8_t* data, size_t len) {
349 return periph_send(char_handle, data, len, false);
350}
351
352int host_ble_send_indication(uint32_t char_handle, const uint8_t* data, size_t len) {
353 return periph_send(char_handle, data, len, true);
354}
355
356int host_ble_consume_write(uint32_t char_handle, uint8_t* buf, size_t buf_size) {
357 if (!buf || buf_size == 0) return HOST_ERR_INVALID_ARG;
358 if (char_handle != s_cur_write_char) return 0;
359 size_t n = s_cur_write_len;
360 if (n > buf_size) n = buf_size;
361 std::memcpy(buf, s_cur_write_buf, n);
362 return static_cast<int>(n);
363}
364
365/* ---------------------------------------------------------- central role -- */
366
367int host_ble_scan_start(uint32_t duration_ms) {
368 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
369 auto* b = ble();
370 if (!b) return HOST_ERR_NOT_FOUND;
371 return b->startScan(duration_ms ? duration_ms : 5000) ? HOST_OK : HOST_ERR_GENERIC;
372}
373
374bool host_ble_scan_done(void) { auto* b = ble(); return b ? b->isScanComplete() : true; }
375
376int host_ble_scan_results(ble_scan_result_t* out, size_t* count) {
377 if (!out || !count) return HOST_ERR_INVALID_ARG;
378 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
379 auto* b = ble();
380 if (!b) return HOST_ERR_NOT_FOUND;
381 uint8_t cap = static_cast<uint8_t>(*count > 32 ? 32 : *count);
382 BleScanResult tmp[32];
383 uint8_t n = b->getScanResults(tmp, cap);
384 for (uint8_t i = 0; i < n; i++) {
385 std::memcpy(out[i].addr, tmp[i].mac, 6);
386 out[i].addr_type = tmp[i].addrType;
387 out[i].rssi = tmp[i].rssi;
388 std::strncpy(out[i].name, tmp[i].name, sizeof(out[i].name) - 1);
389 out[i].name[sizeof(out[i].name) - 1] = '\0';
390 }
391 *count = n;
392 return HOST_OK;
393}
394
395int host_ble_connect(const uint8_t addr[6], uint8_t addr_type) {
396 if (!addr) return HOST_ERR_INVALID_ARG;
397 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
398 auto* b = ble();
399 if (!b) return HOST_ERR_NOT_FOUND;
400 s_cen.plugin = plg_get_active_plugin();
401 ensure_central_listeners();
402 return b->connect(addr, addr_type) ? HOST_OK : HOST_ERR_GENERIC;
403}
404
405uint32_t host_ble_conn_handle(void) {
406 auto* b = ble();
407 if (!b) return 0;
408 uint16_t h = b->getConnectionHandle();
409 return h == 0xFFFF ? 0u : static_cast<uint32_t>(h);
410}
411
412int host_ble_disconnect(uint32_t conn) {
413 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
414 auto* b = ble();
415 if (!b) return HOST_ERR_NOT_FOUND;
416 b->disconnectHandle(static_cast<uint16_t>(conn));
417 return HOST_OK;
418}
419
420int host_ble_discover(uint32_t conn, const uint8_t uuid[16], uint32_t action_id) {
421 if (!uuid) return HOST_ERR_INVALID_ARG;
422 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
423 auto* b = ble();
424 if (!b) return HOST_ERR_NOT_FOUND;
425 s_cen.plugin = plg_get_active_plugin();
426 s_cen.conn = static_cast<uint16_t>(conn);
427 s_cen.discover_action_id = action_id;
428 ensure_central_listeners();
429 return b->discoverServiceByUuid(static_cast<uint16_t>(conn), BleUuid::from128(uuid))
431}
432
434 if (!out || !count) return HOST_ERR_INVALID_ARG;
435 Guard g;
436 uint8_t n = s_cen.disc_count;
437 if (n > *count) n = static_cast<uint8_t>(*count);
438 std::memcpy(out, s_cen.disc, n * sizeof(ble_remote_char_t));
439 *count = n;
440 return HOST_OK;
441}
442
443int host_ble_read_char(uint32_t conn, uint16_t value_handle, uint32_t action_id) {
444 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
445 auto* b = ble();
446 if (!b) return HOST_ERR_NOT_FOUND;
447 s_cen.plugin = plg_get_active_plugin();
448 s_cen.conn = static_cast<uint16_t>(conn);
449 s_cen.read_action_id = action_id;
450 ensure_central_listeners();
451 return b->readCharacteristic(static_cast<uint16_t>(conn), value_handle)
453}
454
455int host_ble_consume_read(uint8_t* buf, size_t buf_size) {
456 if (!buf || buf_size == 0) return HOST_ERR_INVALID_ARG;
457 Guard g;
458 size_t n = s_cen.read_len;
459 if (n > buf_size) n = buf_size;
460 std::memcpy(buf, s_cen.read_buf, n);
461 s_cen.read_len = 0;
462 return static_cast<int>(n);
463}
464
465int host_ble_write_char(uint32_t conn, uint16_t value_handle,
466 const uint8_t* data, size_t len, uint8_t with_response) {
467 if (!data && len) return HOST_ERR_INVALID_ARG;
468 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
469 auto* b = ble();
470 if (!b) return HOST_ERR_NOT_FOUND;
471 return b->writeCharacteristic(static_cast<uint16_t>(conn), value_handle,
472 data, static_cast<uint16_t>(len), with_response != 0)
474}
475
476int host_ble_subscribe(uint32_t conn, uint16_t cccd_handle, uint32_t action_id) {
477 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
478 auto* b = ble();
479 if (!b) return HOST_ERR_NOT_FOUND;
480 s_cen.plugin = plg_get_active_plugin();
481 s_cen.notify_action_id = action_id;
482 ensure_central_listeners();
483 return b->enableNotifications(static_cast<uint16_t>(conn), cccd_handle)
485}
486
487int host_ble_subscribe_char(uint32_t conn, uint16_t value_handle, uint32_t action_id) {
488 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
489 auto* b = ble();
490 if (!b) return HOST_ERR_NOT_FOUND;
491 s_cen.plugin = plg_get_active_plugin();
492 s_cen.conn = static_cast<uint16_t>(conn);
493 s_cen.notify_action_id = action_id;
494 ensure_central_listeners();
495 return b->subscribeToCharacteristic(static_cast<uint16_t>(conn), value_handle)
497}
498
499uint16_t host_ble_get_mtu(uint32_t conn) {
500 (void)conn; // single connection today; kept for forward compatibility
501 if (!ble_allowed()) return 0;
502 auto* b = ble();
503 return b ? b->getMtu() : 0;
504}
505
506int host_ble_on_write_complete(uint32_t action_id) {
507 if (!ble_allowed()) return HOST_ERR_NO_CAPABILITY;
508 s_cen.plugin = plg_get_active_plugin();
509 s_cen.write_action_id = action_id;
510 if (action_id) ensure_central_listeners();
511 return HOST_OK;
512}
513
514int host_ble_consume_notification(uint16_t* value_handle_out, uint8_t* buf, size_t buf_size) {
515 if (!value_handle_out || !buf || buf_size == 0) return HOST_ERR_INVALID_ARG;
516 Guard g;
517 if (s_cen.n_tail == s_cen.n_head) return HOST_ERR_NOT_FOUND;
518 NotifyEvt& e = s_cen.nring[s_cen.n_tail];
519 *value_handle_out = e.value_handle;
520 size_t n = e.len;
521 if (n > buf_size) n = buf_size;
522 std::memcpy(buf, e.data, n);
523 s_cen.n_tail = static_cast<uint8_t>((s_cen.n_tail + 1) % NOTIFY_RING);
524 return static_cast<int>(n);
525}
526
527/* ------------------------------------------------ tick pump + unload hook -- */
528
529// Drains queued BLE events on the plugin tick task and fires plugin actions.
530void plg_ble_pump(void) {
531 // Peripheral writes.
532 for (;;) {
533 uint32_t ch = 0, aid = 0;
534 uint16_t conn = 0;
535 {
536 Guard g;
537 if (s_wr_tail == s_wr_head) break;
538 WriteEvt& e = s_wring[s_wr_tail];
539 ch = e.char_handle;
540 conn = e.conn;
541 s_cur_write_len = e.len;
542 std::memcpy(s_cur_write_buf, e.data, e.len);
543 s_wr_tail = static_cast<uint8_t>((s_wr_tail + 1) % WRITE_RING);
544 }
545 if (s_periph.active && s_periph.plugin && ch >= 1 && ch <= s_periph.num_chars) {
546 aid = s_periph.chars[ch - 1].write_action_id;
547 s_cur_write_char = ch;
548 if (aid) {
550 static_cast<pm::Plugin*>(s_periph.plugin), aid, ch, conn);
551 }
552 s_cur_write_char = 0;
553 s_cur_write_len = 0;
554 }
555 }
556
557 // Central completions.
558 if (!s_cen.plugin) return;
559 bool fd, fr, have_notif;
560 {
561 Guard g;
562 fd = s_cen.fire_discovery; s_cen.fire_discovery = false;
563 fr = s_cen.fire_read; s_cen.fire_read = false;
564 have_notif = (s_cen.n_tail != s_cen.n_head);
565 }
566 auto* pl = static_cast<pm::Plugin*>(s_cen.plugin);
567 auto& mgr = pm::PluginManager::instance();
568 if (fd && s_cen.discover_action_id) mgr.dispatchActionTo(pl, s_cen.discover_action_id, 0, 0);
569 if (fr && s_cen.read_action_id) mgr.dispatchActionTo(pl, s_cen.read_action_id, 0, 0);
570 if (have_notif && s_cen.notify_action_id) mgr.dispatchActionTo(pl, s_cen.notify_action_id, 0, 0);
571
572 // Write completions: one action per event, status in user_data, attr in idx.
573 while (s_cen.write_action_id) {
574 uint16_t attr; int16_t status;
575 {
576 Guard g;
577 if (s_cen.wc_tail == s_cen.wc_head) break;
578 WcEvt& e = s_cen.wcring[s_cen.wc_tail];
579 attr = e.attr;
580 status = e.status;
581 s_cen.wc_tail = static_cast<uint8_t>((s_cen.wc_tail + 1) % WC_RING);
582 }
583 mgr.dispatchActionTo(pl, s_cen.write_action_id, attr,
584 static_cast<uint32_t>(static_cast<int32_t>(status)));
585 }
586}
587
588// Drop any BLE resources owned by a plugin that is being unloaded.
589void plg_ble_on_unload(void* plugin) {
590 if (s_periph.active && s_periph.plugin == plugin) {
591 auto* b = ble();
592 if (b) b->unregisterGattService(BleUuid::from128(s_periph.uuid));
593 s_periph = {};
594 }
595 if (s_cen.plugin == plugin) {
596 s_cen.plugin = nullptr;
597 s_cen.discover_action_id = s_cen.read_action_id = s_cen.notify_action_id = 0;
598 s_cen.write_action_id = 0;
599 Guard g;
600 s_cen.wc_tail = s_cen.wc_head;
601 }
602}
603
604} // extern "C"
Discovers, loads, runs and unloads WASM plugins on the badge.
Owned WAMR module instance + per-plugin state.
char name[cdc::hal::ISecureElement::RMEM_NAME_LEN]
static constexpr uint8_t MAX_CHARS_PER_SERVICE
static PluginManager & instance() noexcept
void dispatchActionTo(Plugin *plugin, uint32_t action_id, uint32_t idx, uint32_t user_data)
int host_ble_consume_write(uint32_t char_handle, uint8_t *buf, size_t buf_size)
Pull the next queued inbound write for char_handle.
#define BLE_PROP_WRITE
Definition host_api.h:529
int host_ble_scan_start(uint32_t duration_ms)
Start a central scan for duration_ms milliseconds.
int host_ble_connect(const uint8_t addr[6], uint8_t addr_type)
Connect to a peer. Completion arrives as a BLE_CONNECTED event; read the resulting handle with host_b...
int host_ble_subscribe(uint32_t conn, uint16_t cccd_handle, uint32_t action_id)
Subscribe to notifications on a peer characteristic (by CCCD handle). Each notification fires action_...
int host_ble_send_notification(uint32_t char_handle, const uint8_t *data, size_t len)
Notify subscribers of a value on one of the plugin's characteristics.
int host_ble_discover(uint32_t conn, const uint8_t uuid[16], uint32_t action_id)
Discover the characteristics of one service on a connected peer. Completion fires action_id; read ent...
bool host_ble_scan_done(void)
True when the scan started by host_ble_scan_start() has finished.
int host_ble_read_char(uint32_t conn, uint16_t value_handle, uint32_t action_id)
Start reading a peer characteristic by value handle. Completion fires action_id; read the value with ...
#define BLE_PROP_WRITE_NO_RSP
Definition host_api.h:528
int host_ble_disconnect(uint32_t conn)
Disconnect a connection.
uint16_t host_ble_get_mtu(uint32_t conn)
Usable ATT payload of the current connection (negotiated MTU - 3).
int host_ble_device_name(char *out, size_t out_size)
Copy the local BLE device name into out.
#define BLE_PROP_READ
Definition host_api.h:527
int8_t host_ble_rssi(void)
Signal strength of the active BLE link in dBm, or 0 when idle.
int host_ble_unregister_service(uint32_t service_handle)
Tear down the plugin's registered GATT service.
int host_ble_scan_results(ble_scan_result_t *out, size_t *count)
Read results from the last central scan.
int host_ble_on_write_complete(uint32_t action_id)
Register an action fired on every completed central write.
int host_ble_send_indication(uint32_t char_handle, const uint8_t *data, size_t len)
Indicate (acknowledged notify) a value on a plugin characteristic.
bool host_ble_is_enabled(void)
True when the BLE stack is initialised and advertising or connectable.
int host_ble_consume_notification(uint16_t *value_handle_out, uint8_t *buf, size_t buf_size)
Pull the next queued inbound notification.
int host_ble_subscribe_char(uint32_t conn, uint16_t value_handle, uint32_t action_id)
Subscribe to notifications on a peer characteristic by VALUE handle.
int host_ble_register_service(ble_service_def_t *def, ble_char_def_t *chars, uint32_t num_chars)
Register the plugin's GATT service and its characteristics.
int host_ble_write_char(uint32_t conn, uint16_t value_handle, const uint8_t *data, size_t len, uint8_t with_response)
Write a value to a peer characteristic by value handle.
int host_ble_consume_discovery(ble_remote_char_t *out, size_t *count)
Pull discovered characteristics after a discovery action fires.
int host_ble_mac(uint8_t out[6])
Read the local BLE MAC address.
int host_ble_consume_read(uint8_t *buf, size_t buf_size)
Pull the value delivered by the last read action.
uint32_t host_ble_conn_handle(void)
Current connection handle (central or peripheral), or 0 when idle.
CDC Badge OS plugin host API - canonical C ABI contract.
#define HOST_ERR_NO_CAPABILITY
Definition host_api.h:40
#define HOST_OK
Definition host_api.h:37
#define HOST_ERR_INVALID_ARG
Definition host_api.h:39
#define HOST_ERR_NOT_FOUND
Definition host_api.h:41
#define HOST_ERR_GENERIC
Definition host_api.h:38
#define HOST_ERR_BUSY
Definition host_api.h:44
void plg_ble_on_unload(void *plugin)
void * plg_get_active_plugin(void)
void plg_ble_pump(void)
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
static int periph_send(uint32_t char_handle, const uint8_t *data, size_t len, bool indicate)
constexpr uint8_t WRITE
constexpr uint8_t READ
IBluetoothController * getBluetoothControllerInstance()
Returns singleton Bluetooth stub when NimBLE is unavailable.
static BleUuid from128(const uint8_t v[16])
DiscoveredCharacteristic characteristics[MAX_DISCOVERED_CHARS]
One characteristic of a plugin GATT service (peripheral role).
Definition host_api.h:534
uint32_t write_action_id
Definition host_api.h:538
uint32_t char_handle
Definition host_api.h:539
uint8_t properties
Definition host_api.h:536
One characteristic discovered on a connected peer (central role).
Definition host_api.h:559
One device from a central scan.
Definition host_api.h:551
uint8_t addr_type
Definition host_api.h:553
A plugin GATT service definition (peripheral role). Always primary.
Definition host_api.h:543
uint8_t uuid[16]
Definition host_api.h:544
uint32_t service_handle
Definition host_api.h:547
GattCharacteristic * characteristics