CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
host_api_ext_feature.cpp
Go to the documentation of this file.
1
18
24#include "cdc_ui/I18n.h"
25#include "cdc_log.h"
26
27#include "esp_heap_caps.h"
28#include "freertos/FreeRTOS.h"
29#include "freertos/semphr.h"
30
31#include <cstring>
32#include <string>
33
34namespace pm = cdc::plugin_manager;
35
36extern "C" void* plg_get_active_plugin(void);
37
38namespace {
39
40const char* TAG = "PLG_FEAT";
41
42constexpr uint8_t MAX_HANDLERS = 4;
43
44SemaphoreHandle_t s_lock = nullptr;
45void lock_init() { if (!s_lock) s_lock = xSemaphoreCreateMutex(); }
46struct Guard {
47 bool held = false;
48 Guard() { if (s_lock) held = (xSemaphoreTake(s_lock, portMAX_DELAY) == pdTRUE); }
49 ~Guard() { if (held) xSemaphoreGive(s_lock); }
50};
51
52// Provider handler table: registered from plugin_init, dropped on unload.
53struct Handler {
54 bool used = false;
55 void* plugin = nullptr;
56 char feature[HOST_EXT_FEATURE_NAME_MAX] = {};
57 uint32_t action_id = 0;
58};
59Handler s_handlers[MAX_HANDLERS];
60
61// Single job slot walking Idle -> Stashed -> Delivered -> Idle.
62struct FeatureJob {
63 enum class St : uint8_t { Idle, Stashed, Delivered };
64 St st = St::Idle;
65 char feature[HOST_EXT_FEATURE_NAME_MAX] = {};
66 std::string provider_id;
67 void* caller = nullptr; // Plugin*; may dangle after the handoff
68 std::string caller_id; // for isLoaded() re-check before dispatch
69 uint32_t status_action_id = 0;
70 uint8_t* data = nullptr; // PSRAM, owned by the slot
71 uint32_t len = 0;
72};
73FeatureJob s_job;
74
75// Payload of the job currently delivered to a handler action (consumable for
76// that dispatch only, like host_msg_consume).
77char s_cur_feature[HOST_EXT_FEATURE_NAME_MAX] = {};
78uint32_t s_cur_len = 0;
79uint8_t* s_cur_data = nullptr;
80
81void job_free_locked() {
82 if (s_job.data) { heap_caps_free(s_job.data); s_job.data = nullptr; }
83 s_job.len = 0;
84 s_job.st = FeatureJob::St::Idle;
85 s_job.caller = nullptr;
86 s_job.caller_id.clear();
87 s_job.provider_id.clear();
88 s_job.status_action_id = 0;
89 s_job.feature[0] = '\0';
90}
91
92int find_handler(void* plugin, const char* feature) {
93 for (int i = 0; i < MAX_HANDLERS; ++i) {
94 if (s_handlers[i].used && s_handlers[i].plugin == plugin &&
95 std::strncmp(s_handlers[i].feature, feature, HOST_EXT_FEATURE_NAME_MAX) == 0) {
96 return i;
97 }
98 }
99 return -1;
100}
101
102int find_handler_by_feature(const char* feature) {
103 for (int i = 0; i < MAX_HANDLERS; ++i) {
104 if (s_handlers[i].used &&
105 std::strncmp(s_handlers[i].feature, feature, HOST_EXT_FEATURE_NAME_MAX) == 0) {
106 return i;
107 }
108 }
109 return -1;
110}
111
112// Fire the caller's status action if the caller plugin is still loaded under
113// the same pointer. Must be called WITHOUT holding s_lock (dispatch enters WASM).
114void report_to_caller(void* caller, const std::string& caller_id,
115 uint32_t action_id, int32_t status) {
116 if (!caller || !action_id || caller_id.empty()) return;
117 if (!pm::PluginManager::instance().isLoaded(caller_id)) return;
118 // dispatchActionTo re-validates pointer membership in the fore/background
119 // slots, so a reused address for a different plugin is dropped there.
121 static_cast<pm::Plugin*>(caller), action_id, 0,
122 static_cast<uint32_t>(status));
123}
124
125} // namespace
126
127extern "C" {
128
129int host_ext_feature_available(const char* feature) {
133}
134
135int host_ext_feature_use(const char* feature, const uint8_t* data, size_t len,
136 uint32_t status_action_id) {
139 if (!data && len > 0) return HOST_ERR_INVALID_ARG;
140
141 auto* caller = static_cast<pm::Plugin*>(plg_get_active_plugin());
142 if (!caller) return HOST_ERR_GENERIC;
143
144 std::string provider = pm::PluginManager::instance().featureProviderId(feature);
145 if (provider.empty()) {
146 cdc::ui::showMessage(cdc::ui::tr("core.feature_missing"),
148 return HOST_ERR_NOT_FOUND;
149 }
150 if (provider == caller->id()) return HOST_ERR_INVALID_ARG;
151
152 uint8_t* copy = nullptr;
153 if (len > 0) {
154 copy = static_cast<uint8_t*>(heap_caps_malloc(len, MALLOC_CAP_SPIRAM));
155 if (!copy) return HOST_ERR_NO_MEMORY;
156 std::memcpy(copy, data, len);
157 }
158
159 lock_init();
160 Guard g;
161 if (s_job.st != FeatureJob::St::Idle) {
162 if (copy) heap_caps_free(copy);
163 return HOST_ERR_BUSY;
164 }
165 s_job.st = FeatureJob::St::Stashed;
166 std::strncpy(s_job.feature, feature, sizeof(s_job.feature) - 1);
167 s_job.feature[sizeof(s_job.feature) - 1] = '\0';
168 s_job.provider_id = std::move(provider);
169 s_job.caller = caller;
170 s_job.caller_id = caller->id();
171 s_job.status_action_id = status_action_id;
172 s_job.data = copy;
173 s_job.len = static_cast<uint32_t>(len);
174 return HOST_OK;
175}
176
177int host_ext_feature_register_handler(const char* feature, uint32_t action_id) {
179 auto* p = static_cast<pm::Plugin*>(plg_get_active_plugin());
180 if (!p) return HOST_ERR_GENERIC;
181
182 bool declared = false;
183 for (const auto& name : p->manifest().capabilities.provides) {
184 if (name == feature) { declared = true; break; }
185 }
186 if (!declared) return HOST_ERR_NO_CAPABILITY;
187
188 int idx = find_handler(p, feature);
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 = p;
199 std::strncpy(h.feature, feature, sizeof(h.feature) - 1);
200 h.feature[sizeof(h.feature) - 1] = '\0';
201 h.action_id = action_id;
202 return HOST_OK;
203}
204
205int host_ext_feature_consume(uint8_t* buf, size_t buf_size,
206 char* feature_out, size_t feature_size) {
207 if (!buf || buf_size == 0) return HOST_ERR_INVALID_ARG;
208 uint32_t n = s_cur_len;
209 if (n > buf_size) n = static_cast<uint32_t>(buf_size);
210 if (n > 0 && s_cur_data) std::memcpy(buf, s_cur_data, n);
211 if (feature_out && feature_size) {
212 std::strncpy(feature_out, s_cur_feature, feature_size - 1);
213 feature_out[feature_size - 1] = '\0';
214 }
215 return static_cast<int>(n);
216}
217
218int host_ext_feature_result(int32_t status_code) {
219 auto* p = plg_get_active_plugin();
220 if (!p) return HOST_ERR_GENERIC;
221
222 void* caller = nullptr;
223 std::string caller_id;
224 uint32_t aid = 0;
225 {
226 Guard g;
227 if (s_job.st != FeatureJob::St::Delivered) return HOST_ERR_NOT_FOUND;
228 int idx = find_handler_by_feature(s_job.feature);
229 if (idx < 0 || s_handlers[idx].plugin != p) return HOST_ERR_NOT_FOUND;
230 caller = s_job.caller;
231 caller_id = s_job.caller_id;
232 aid = s_job.status_action_id;
233 job_free_locked();
234 }
235 report_to_caller(caller, caller_id, aid, status_code);
236 return HOST_OK;
237}
238
239// Plugin tick task: start the provider for a stashed job and deliver it.
241 char feature[HOST_EXT_FEATURE_NAME_MAX];
242 std::string provider_id;
243 {
244 Guard g;
245 if (s_job.st != FeatureJob::St::Stashed) return;
246 std::memcpy(feature, s_job.feature, sizeof(feature));
247 provider_id = s_job.provider_id;
248 }
249
250 auto& mgr = pm::PluginManager::instance();
251 bool started = mgr.activePluginId() == provider_id;
252 if (!started) started = (mgr.startPlugin(provider_id) == pm::StartResult::Ok);
253
254 int idx = started ? find_handler_by_feature(feature) : -1;
255 if (idx < 0) {
256 // Provider failed to start or never registered its handler: fail the
257 // job back to the caller.
258 void* caller = nullptr;
259 std::string caller_id;
260 uint32_t aid = 0;
261 {
262 Guard g;
263 if (s_job.st != FeatureJob::St::Stashed) return;
264 caller = s_job.caller;
265 caller_id = s_job.caller_id;
266 aid = s_job.status_action_id;
267 job_free_locked();
268 }
269 LOG_W(TAG, "feature '%s': provider %s unavailable", feature, provider_id.c_str());
270 report_to_caller(caller, caller_id, aid, HOST_EXT_FEATURE_STATUS_ERROR);
271 return;
272 }
273
274 uint32_t len = 0;
275 {
276 Guard g;
277 if (s_job.st != FeatureJob::St::Stashed) return;
278 s_job.st = FeatureJob::St::Delivered;
279 s_cur_data = s_job.data;
280 s_cur_len = s_job.len;
281 std::memcpy(s_cur_feature, s_job.feature, sizeof(s_cur_feature));
282 len = s_job.len;
283 }
284 mgr.dispatchActionTo(static_cast<pm::Plugin*>(s_handlers[idx].plugin),
285 s_handlers[idx].action_id, 0, len);
286 // Payload stays owned by the job slot until host_ext_feature_result, but
287 // it is only consumable during the dispatch above.
288 Guard g;
289 s_cur_len = 0;
290 s_cur_data = nullptr;
291 s_cur_feature[0] = '\0';
292}
293
294// Drop handlers and fail/clear the job when one of its plugins unloads.
295void plg_ext_feature_on_unload(void* plugin) {
296 for (int i = 0; i < MAX_HANDLERS; ++i) {
297 if (s_handlers[i].used && s_handlers[i].plugin == plugin) {
298 s_handlers[i] = Handler{};
299 }
300 }
301
302 lock_init();
303 void* caller = nullptr;
304 std::string caller_id;
305 uint32_t aid = 0;
306 bool provider_died = false;
307 {
308 Guard g;
309 if (s_job.st == FeatureJob::St::Idle) return;
310 if (s_job.caller == plugin) {
311 // Caller gone: keep the job (the provider may still print), but
312 // drop the status routing.
313 s_job.caller = nullptr;
314 s_job.caller_id.clear();
315 s_job.status_action_id = 0;
316 return;
317 }
318 if (s_job.st == FeatureJob::St::Delivered) {
319 int idx = find_handler_by_feature(s_job.feature);
320 // Handler entries for `plugin` were cleared above; a Delivered job
321 // whose feature no longer resolves lost its provider.
322 provider_died = (idx < 0);
323 }
324 if (!provider_died) return;
325 caller = s_job.caller;
326 caller_id = s_job.caller_id;
327 aid = s_job.status_action_id;
328 job_free_locked();
329 }
330 report_to_caller(caller, caller_id, aid, HOST_EXT_FEATURE_STATUS_ERROR);
331}
332
333} // extern "C"
static const char * TAG
Validation for external-feature names declared under the manifest provides capability and passed to t...
Internationalization with English fallbacks in code and overlay translations loaded at runtime from a...
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]
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
static PluginManager & instance() noexcept
void dispatchActionTo(Plugin *plugin, uint32_t action_id, uint32_t idx, uint32_t user_data)
bool featureInstalled(const char *feature) const
True if any installed plugin's manifest provides this external feature. Reads the cached index (same ...
std::string featureProviderId(const char *feature) const
Installed plugin id providing this external feature, or empty.
int host_ext_feature_register_handler(const char *feature, uint32_t action_id)
Register this plugin as the live handler for a feature it provides.
#define HOST_EXT_FEATURE_NAME_MAX
Maximum external-feature name length including the NUL.
Definition host_api.h:1919
int host_ext_feature_result(int32_t status_code)
Report the outcome of the job this provider is currently handling.
#define HOST_EXT_FEATURE_PAYLOAD_MAX
Maximum payload a caller may hand to a provider in one job.
Definition host_api.h:1921
int host_ext_feature_use(const char *feature, const uint8_t *data, size_t len, uint32_t status_action_id)
Invoke feature with a payload; the provider runs in the foreground.
#define HOST_EXT_FEATURE_STATUS_ERROR
Provider status: generic failure. Provider-defined codes are >= 1.
Definition host_api.h:1925
int host_ext_feature_consume(uint8_t *buf, size_t buf_size, char *feature_out, size_t feature_size)
Pull the payload of the job that fired the current handler action.
int host_ext_feature_available(const char *feature)
Check whether an installed plugin provides feature.
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_GENERIC
Definition host_api.h:38
#define HOST_ERR_BUSY
Definition host_api.h:44
void plg_ext_feature_pump(void)
void * plg_get_active_plugin(void)
void plg_ext_feature_on_unload(void *plugin)
static const char * TAG
bool isValidExtFeatureName(const char *name)
True if name is a well-formed external-feature name.
const char * tr(const char *key)
Look up a translation by string key.
Definition I18n.h:209
void showMessage(const char *message, MessageIcon icon=MessageIcon::NONE, uint32_t timeoutMs=0, MessageBox::CloseCallback onClose=nullptr)
Shows the shared modal message box.