CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
host_api_msg.cpp
Go to the documentation of this file.
1
15
19#include "cdc_msg/MessageTransfer.h"
20
21#include "esp_attr.h"
22#include "freertos/FreeRTOS.h"
23#include "freertos/semphr.h"
24
25#include <cstring>
26
27namespace pm = cdc::plugin_manager;
28namespace msg = cdc::msg;
29
30extern "C" void* plg_get_active_plugin(void);
31
32namespace {
33
34constexpr uint8_t MAX_HANDLERS = 8;
35
36// Guards only the pending inbound stash (main task <-> tick task).
37SemaphoreHandle_t s_lock = nullptr;
38void lock_init() { if (!s_lock) s_lock = xSemaphoreCreateMutex(); }
39struct Guard {
40 bool held = false;
41 Guard() { if (s_lock) held = (xSemaphoreTake(s_lock, portMAX_DELAY) == pdTRUE); }
42 ~Guard() { if (held) xSemaphoreGive(s_lock); }
43};
44
45// Handler table: tick-task only, no lock needed.
46struct Handler {
47 bool used = false;
48 void* plugin = nullptr;
49 char mime[HOST_MSG_MIME_MAX] = {};
50 uint32_t action_id = 0;
51};
52Handler s_handlers[MAX_HANDLERS];
53
54// Pending inbound delivery: written by on_deliver (main task), drained by pump.
55struct Pending {
56 bool valid = false;
57 void* plugin = nullptr;
58 uint32_t action_id = 0;
59 char mime[HOST_MSG_MIME_MAX] = {};
60 uint32_t len = 0;
61 uint8_t buf[HOST_MSG_PAYLOAD_MAX];
62};
63EXT_RAM_BSS_ATTR Pending s_pending;
64
65// Payload currently delivered to an action (tick-task only, read by consume).
66char s_cur_mime[HOST_MSG_MIME_MAX] = {};
67uint32_t s_cur_len = 0;
68EXT_RAM_BSS_ATTR uint8_t s_cur_buf[HOST_MSG_PAYLOAD_MAX];
69
70bool msg_allowed() {
71 auto* p = static_cast<pm::Plugin*>(plg_get_active_plugin());
72 if (!p) return false;
73 const auto& caps = p->manifest().capabilities;
74 return caps.ble && !caps.message_types.empty();
75}
76
77int find_handler(void* plugin, const char* mime) {
78 for (int i = 0; i < MAX_HANDLERS; ++i) {
79 if (s_handlers[i].used && s_handlers[i].plugin == plugin &&
80 std::strncmp(s_handlers[i].mime, mime, HOST_MSG_MIME_MAX) == 0) {
81 return i;
82 }
83 }
84 return -1;
85}
86
87// cdc_msg deliver callback. Main task; stash only, never enter WASM.
88bool on_deliver(void* plugin, uint32_t action_id, const uint8_t* data, uint32_t len,
89 const char* mime) {
90 if (!data || len == 0 || len > HOST_MSG_PAYLOAD_MAX) return false;
91 Guard g;
92 if (s_pending.valid) return false; // previous delivery not yet pumped
93 s_pending.valid = true;
94 s_pending.plugin = plugin;
95 s_pending.action_id = action_id;
96 std::strncpy(s_pending.mime, mime ? mime : "", sizeof(s_pending.mime) - 1);
97 s_pending.mime[sizeof(s_pending.mime) - 1] = '\0';
98 s_pending.len = len;
99 std::memcpy(s_pending.buf, data, len);
100 return true;
101}
102
103// Payload for a MIME type handled by a not-yet-loaded plugin: stashed on the
104// main task, then loaded + delivered on the plugin task (handle_deferred).
105struct DeferredMsg {
106 bool valid = false;
107 char mime[HOST_MSG_MIME_MAX] = {};
108 char peerName[msg::kNameBufSize] = {};
109 uint32_t len = 0;
110 uint8_t buf[HOST_MSG_PAYLOAD_MAX];
111};
112EXT_RAM_BSS_ATTR DeferredMsg s_deferred;
113
114int find_handler_by_mime(const char* mime) {
115 for (int i = 0; i < MAX_HANDLERS; ++i) {
116 if (s_handlers[i].used && std::strncmp(s_handlers[i].mime, mime, HOST_MSG_MIME_MAX) == 0) {
117 return i;
118 }
119 }
120 return -1;
121}
122
123// cdc_msg deferred-deliver callback (main task): stash for the plugin task.
124bool stash_deferred(const uint8_t* data, uint32_t len, const char* mime, const char* peerName) {
125 if (!data || len == 0 || len > HOST_MSG_PAYLOAD_MAX) return false;
126 Guard g;
127 if (s_deferred.valid) return false;
128 s_deferred.valid = true;
129 std::strncpy(s_deferred.mime, mime ? mime : "", sizeof(s_deferred.mime) - 1);
130 s_deferred.mime[sizeof(s_deferred.mime) - 1] = '\0';
131 std::strncpy(s_deferred.peerName, peerName ? peerName : "", sizeof(s_deferred.peerName) - 1);
132 s_deferred.peerName[sizeof(s_deferred.peerName) - 1] = '\0';
133 s_deferred.len = len;
134 std::memcpy(s_deferred.buf, data, len);
135 return true;
136}
137
138// Plugin task: load the handler plugin for a stashed deferred message, then
139// deliver the payload to its just-registered action.
140void handle_deferred() {
141 char mime[HOST_MSG_MIME_MAX];
142 uint32_t len;
143 {
144 Guard g;
145 if (!s_deferred.valid) return;
146 std::memcpy(mime, s_deferred.mime, sizeof(mime));
147 std::memcpy(s_cur_buf, s_deferred.buf, s_deferred.len);
148 std::memcpy(s_cur_mime, s_deferred.mime, sizeof(s_cur_mime));
149 len = s_deferred.len;
150 s_cur_len = 0; // not consumable until the handler action actually fires
151 s_deferred.valid = false;
152 }
153 if (pm::PluginManager::instance().activateForMessageType(mime)) {
154 int idx = find_handler_by_mime(mime);
155 if (idx >= 0) {
156 s_cur_len = len; // payload becomes consumable for this dispatch only
158 static_cast<pm::Plugin*>(s_handlers[idx].plugin), s_handlers[idx].action_id, 0, len);
159 }
160 }
161 s_cur_len = 0;
162 s_cur_mime[0] = '\0';
163}
164
165} // namespace
166
167extern "C" {
168
169int host_msg_register_handler(const char* mime_type, uint32_t action_id) {
170 if (!msg_allowed()) return HOST_ERR_NO_CAPABILITY;
171 if (!mime_type || !mime_type[0] ||
172 strnlen(mime_type, HOST_MSG_MIME_MAX) >= HOST_MSG_MIME_MAX) {
174 }
175 void* plugin = plg_get_active_plugin();
176 lock_init();
177
178 // The cdc_msg registry holds one handler per MIME type. Refuse a second,
179 // different plugin claiming a type another loaded plugin already owns,
180 // rather than silently clobbering it (and later cross-unregistering it).
181 for (int i = 0; i < MAX_HANDLERS; ++i) {
182 if (s_handlers[i].used && s_handlers[i].plugin != plugin &&
183 std::strncmp(s_handlers[i].mime, mime_type, HOST_MSG_MIME_MAX) == 0) {
184 return HOST_ERR_BUSY;
185 }
186 }
187
188 int idx = find_handler(plugin, mime_type);
189 if (idx < 0) {
190 for (int i = 0; i < MAX_HANDLERS; ++i) {
191 if (!s_handlers[i].used) { idx = i; break; }
192 }
193 }
194 if (idx < 0) return HOST_ERR_NO_MEMORY;
195
196 Handler& h = s_handlers[idx];
197 h.used = true;
198 h.plugin = plugin;
199 std::strncpy(h.mime, mime_type, sizeof(h.mime) - 1);
200 h.mime[sizeof(h.mime) - 1] = '\0';
201 h.action_id = action_id;
202
203 const char* descKey =
204 (std::strncmp(mime_type, "text/", 5) == 0) ? "core.msg_text" : "core.msg_data";
205 bool ok = msg::MessageTransfer::instance().registerHandler(
206 h.mime, descKey,
207 [plugin, action_id](const uint8_t* d, uint32_t l, const char* m, const char*) -> bool {
208 return on_deliver(plugin, action_id, d, l, m);
209 });
210 if (!ok) {
211 h = Handler{};
212 return HOST_ERR_NO_MEMORY;
213 }
214 return HOST_OK;
215}
216
217int host_msg_unregister_handler(const char* mime_type) {
218 if (!mime_type) return HOST_ERR_INVALID_ARG;
219 void* plugin = plg_get_active_plugin();
220 int idx = find_handler(plugin, mime_type);
221 if (idx < 0) return HOST_ERR_NOT_FOUND;
222 msg::MessageTransfer::instance().unregisterHandler(s_handlers[idx].mime);
223 s_handlers[idx] = Handler{};
224 return HOST_OK;
225}
226
227int host_msg_consume(uint8_t* buf, size_t buf_size, char* mime_out, size_t mime_size) {
228 if (!buf || buf_size == 0) return HOST_ERR_INVALID_ARG;
229 uint32_t n = s_cur_len;
230 if (n > buf_size) n = static_cast<uint32_t>(buf_size);
231 std::memcpy(buf, s_cur_buf, n);
232 if (mime_out && mime_size) {
233 std::strncpy(mime_out, s_cur_mime, mime_size - 1);
234 mime_out[mime_size - 1] = '\0';
235 }
236 return static_cast<int>(n);
237}
238
239int host_msg_send_interactive(const char* mime_type, const uint8_t* data, size_t len) {
240 if (!msg_allowed()) return HOST_ERR_NO_CAPABILITY;
241 if (!mime_type || !data || len == 0 || len > HOST_MSG_PAYLOAD_MAX) return HOST_ERR_INVALID_ARG;
242 bool ok = msg::MessageTransfer::instance().beginInteractiveSend(
243 mime_type, data, static_cast<uint32_t>(len));
244 return ok ? HOST_OK : HOST_ERR_BUSY;
245}
246
247int host_msg_send(const uint8_t addr[6], uint8_t addr_type, const char* mime_type,
248 const uint8_t* data, size_t len) {
249 if (!msg_allowed()) return HOST_ERR_NO_CAPABILITY;
250 if (!addr || !mime_type || !data || len == 0 || len > HOST_MSG_PAYLOAD_MAX) {
252 }
253 bool ok = msg::MessageTransfer::instance().sendTo(
254 addr, addr_type, mime_type, data, static_cast<uint32_t>(len));
255 return ok ? HOST_OK : HOST_ERR_BUSY;
256}
257
258// Register the deferred-handler resolver with cdc_msg (called once at PluginManager init).
259void plg_msg_init(void) {
260 msg::MessageTransfer::instance().setDeferredHandler(
261 [](const char* mime) -> bool {
263 },
264 stash_deferred);
265}
266
267// Drain one completed inbound delivery and fire the owning plugin's action.
268void plg_msg_pump(void) {
269 handle_deferred(); // first: load + deliver to a not-yet-loaded handler plugin
270
271 void* plugin = nullptr;
272 uint32_t aid = 0, len = 0;
273 {
274 Guard g;
275 if (!s_pending.valid) return;
276 plugin = s_pending.plugin;
277 aid = s_pending.action_id;
278 len = s_pending.len;
279 std::memcpy(s_cur_buf, s_pending.buf, len);
280 std::memcpy(s_cur_mime, s_pending.mime, sizeof(s_cur_mime));
281 s_cur_len = len;
282 s_pending.valid = false;
283 }
284 if (plugin && aid) {
286 static_cast<pm::Plugin*>(plugin), aid, 0, len);
287 }
288 s_cur_len = 0; // payload only valid during the action dispatch
289 s_cur_mime[0] = '\0';
290}
291
292// Drop any handlers owned by a plugin being unloaded.
293void plg_msg_on_unload(void* plugin) {
294 for (int i = 0; i < MAX_HANDLERS; ++i) {
295 if (s_handlers[i].used && s_handlers[i].plugin == plugin) {
296 msg::MessageTransfer::instance().unregisterHandler(s_handlers[i].mime);
297 s_handlers[i] = Handler{};
298 }
299 }
300 Guard g;
301 if (s_pending.valid && s_pending.plugin == plugin) s_pending.valid = false;
302}
303
304} // extern "C"
Discovers, loads, runs and unloads WASM plugins on the badge.
Owned WAMR module instance + per-plugin state.
static PluginManager & instance() noexcept
void dispatchActionTo(Plugin *plugin, uint32_t action_id, uint32_t idx, uint32_t user_data)
bool messageTypeInstalled(const char *mime) const
True if any installed plugin's manifest declares this MIME type for message transfer....
bool valid
int host_msg_send(const uint8_t addr[6], uint8_t addr_type, const char *mime_type, const uint8_t *data, size_t len)
Send a typed payload directly to a known peer address (no picker).
int host_msg_unregister_handler(const char *mime_type)
Drop a previously registered handler.
int host_msg_send_interactive(const char *mime_type, const uint8_t *data, size_t len)
Send a typed payload via the firmware-owned interactive peer picker.
int host_msg_consume(uint8_t *buf, size_t buf_size, char *mime_out, size_t mime_size)
Pull the payload delivered by the most recent inbound message action.
#define HOST_MSG_PAYLOAD_MAX
Maximum payload a plugin may send or receive in one transfer.
Definition host_api.h:1352
int host_msg_register_handler(const char *mime_type, uint32_t action_id)
Register that this plugin handles an incoming MIME type.
#define HOST_MSG_MIME_MAX
Maximum MIME type string length including the NUL.
Definition host_api.h:1354
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_NO_MEMORY
Definition host_api.h:43
#define HOST_ERR_NOT_FOUND
Definition host_api.h:41
#define HOST_ERR_BUSY
Definition host_api.h:44
void * plg_get_active_plugin(void)
void plg_msg_pump(void)
void plg_msg_on_unload(void *plugin)
void * plg_get_active_plugin(void)
void plg_msg_init(void)