Files
n_signer/firmware/teensy41/signer/src/selector.cpp
T

112 lines
3.7 KiB
C++

/* selector.cpp — request selector implementation for the Teensy 4.1.
*
* Phase 3 of plans/teensy41_role_path_migration.md.
*
* Ports the selector decision tree from src/selector.c:745. The JSON parsing
* (extracting role/role_path/nostr_index from the cJSON options object) is
* done in dispatch.cpp, which calls selector_resolve() with the populated
* selector_request_t.
*/
#include "selector.h"
#include <string.h>
/* ====================================================================
* Selector request
* ==================================================================== */
__attribute__((section(".flashmem")))
void selector_request_init(selector_request_t *req) {
if (req != NULL) {
memset(req, 0, sizeof(*req));
}
}
/* ====================================================================
* Selector resolution
* ==================================================================== */
/* Resolve a selector request against the role table.
*
* Ported from src/selector.c:745 (selector_resolve). The decision tree:
* 1. nostr_index present → DEPRECATED (error 2006 in the wire protocol)
* 2. role_path without role → ROLE_REQUIRED
* 3. role + role_path → find role, verify path matches template + range
* 4. role only, fixed path (no %d) → use it
* 5. role only, template path → PATH_REQUIRED
* 6. neither → default role ("main"), else NO_DEFAULT
*
* On success, *out points to the matching role entry in the table. The
* caller uses req->role_path (if has_role_path) or the role's role_path
* (if fixed) for key derivation. */
__attribute__((section(".flashmem")))
int selector_resolve(const selector_request_t *req, role_table_t *table,
role_entry_t **out) {
role_entry_t *match = NULL;
if (out != NULL) {
*out = NULL;
}
if (req == NULL || table == NULL || out == NULL) {
return SELECTOR_ERR_NOT_FOUND;
}
/* ---- Deprecated selectors: reject with clear error codes ---- */
/* nostr_index is deprecated */
if (req->has_nostr_index) {
return SELECTOR_ERR_NOSTR_INDEX_DEPRECATED;
}
/* role_path without role is not allowed */
if (req->has_role_path && !req->has_role) {
return SELECTOR_ERR_ROLE_REQUIRED;
}
/* ---- New model: role + role_path combined ---- */
if (req->has_role && req->has_role_path) {
/* Combined selector: look up role by name, verify path matches template */
match = role_table_find_by_name(table, req->role_name);
if (match == NULL) {
return SELECTOR_ERR_NOT_FOUND;
}
/* Verify the requested path matches the role's template AND that the
* extracted index falls within the role's allowed range. */
if (!role_path_matches_with_range(req->role_path, match)) {
return SELECTOR_ERR_PATH_MISMATCH;
}
*out = match;
return SELECTOR_OK;
}
if (req->has_role && !req->has_role_path) {
/* Role specified without path — check if role has a fixed path (no %d) */
match = role_table_find_by_name(table, req->role_name);
if (match == NULL) {
return SELECTOR_ERR_NOT_FOUND;
}
/* If the role has a fixed path (no variable segments), use it */
if (strstr(match->role_path, "%d") == NULL) {
*out = match;
return SELECTOR_OK;
}
/* Role has variable path template — path is required */
return SELECTOR_ERR_PATH_REQUIRED;
}
/* No selectors at all — try default role */
match = role_table_get_default(table);
if (match == NULL) {
return SELECTOR_ERR_NO_DEFAULT;
}
*out = match;
return SELECTOR_OK;
}