21#include <freertos/FreeRTOS.h>
22#include <freertos/task.h>
23#include <freertos/semphr.h>
25static const char*
TAG =
"CTAPHID";
29#ifndef CTAPHID_DEBUG_PACKETS
30#define CTAPHID_DEBUG_PACKETS DEBUG_MODE
32#define CTAPHID_MSG_TIMEOUT_MS 500
33#define CTAPHID_MAX_CHANNELS 8
34#define CTAPHID_RESPONSE_QUEUE_SIZE 8
39#define CTAPHID_RATE_LIMIT_WINDOW_MS 1000
40#define CTAPHID_RATE_LIMIT_MAX_CMDS 200
68 if (cid == 0)
return NULL;
88 ctaphid_channel_t *ch = &
g_ctaphid.channels[index];
96 ch->response_offset = 0;
98 ch->response_pending =
false;
116 uint32_t oldest_time = UINT32_MAX;
119 !
g_ctaphid.channels[i].response_pending &&
120 g_ctaphid.channels[i].last_activity < oldest_time) {
121 oldest_time =
g_ctaphid.channels[i].last_activity;
126 if (oldest_idx >= 0) {
127 LOG_W(
TAG,
"Evicting oldest channel 0x%08lX",
g_ctaphid.channels[oldest_idx].cid);
161 uint16_t bcnt,
const uint8_t *data, uint16_t data_len) {
165 packet[0] = (cid >> 24) & 0xFF;
166 packet[1] = (cid >> 16) & 0xFF;
167 packet[2] = (cid >> 8) & 0xFF;
168 packet[3] = cid & 0xFF;
169 packet[4] = cmd | 0x80;
170 packet[5] = (bcnt >> 8) & 0xFF;
171 packet[6] = bcnt & 0xFF;
174 if (data && data_len > 0) {
176 memcpy(packet + 7, data, copy);
189 const uint8_t *data, uint16_t data_len) {
193 packet[0] = (cid >> 24) & 0xFF;
194 packet[1] = (cid >> 16) & 0xFF;
195 packet[2] = (cid >> 8) & 0xFF;
196 packet[3] = cid & 0xFF;
197 packet[4] = seq & 0x7F;
200 if (data && data_len > 0) {
202 memcpy(packet + 5, data, copy);
213static void prepare_response(uint32_t cid, uint8_t cmd,
const uint8_t *data, uint16_t len) {
216 LOG_W(
TAG,
"prepare_response: no channel for CID 0x%08lX", cid);
219 ch->response_cid = cid;
220 ch->response_cmd = cmd;
221 if (data && len > 0) {
222 if (len > ch->response_buffer_size) {
223 LOG_E(
TAG,
"Response too large: %u > %u", len, ch->response_buffer_size);
224 len = ch->response_buffer_size;
226 memcpy(ch->response_buffer, data, len);
228 ch->response_len = len;
229 ch->response_offset = 0;
230 ch->response_pending =
true;
232 LOG_I(
TAG,
"Prepared CBOR response len=%u status=0x%02X", len, data[0]);
245 const uint8_t *data, uint16_t len) {
247 ch->response_cid = send_cid;
248 ch->response_cmd = cmd;
249 if (data && len > 0) {
250 if (len > ch->response_buffer_size) {
251 LOG_E(
TAG,
"Response too large: %u > %u", len, ch->response_buffer_size);
252 len = ch->response_buffer_size;
254 memcpy(ch->response_buffer, data, len);
256 ch->response_len = len;
257 ch->response_offset = 0;
258 ch->response_pending =
true;
267static void handle_init(uint32_t cid,
const uint8_t *data, uint16_t len) {
288 uint8_t response[17];
289 memcpy(response, data, 8);
290 response[8] = (new_cid >> 24) & 0xFF;
291 response[9] = (new_cid >> 16) & 0xFF;
292 response[10] = (new_cid >> 8) & 0xFF;
293 response[11] = new_cid & 0xFF;
310static void handle_ping(uint32_t cid,
const uint8_t *data, uint16_t len) {
342static void handle_cbor(uint32_t cid,
const uint8_t *data, uint16_t len) {
357 uint16_t response_len = ch->response_buffer_size;
362 ch->response_cid = cid;
364 ch->response_len = response_len;
365 ch->response_offset = 0;
366 ch->response_pending =
true;
367 if (response_len > 0) {
368 LOG_I(
TAG,
"Prepared CBOR response len=%u status=0x%02X",
369 response_len, ch->response_buffer[0]);
379 uint32_t cid = ch->cid;
380 uint8_t cmd = ch->cmd;
381 uint8_t *data = ch->buffer;
382 uint16_t len = ch->bcnt;
409 EXT_RAM_BSS_ATTR
static uint8_t u2f_response[512];
410 uint16_t u2f_response_len =
u2f_process_apdu(data, len, u2f_response,
sizeof(u2f_response));
440 g_ctaphid.mutex = xSemaphoreCreateMutex();
442 LOG_E(
TAG,
"Mutex creation failed");
457 if (cbor_count) *cbor_count =
g_ctaphid.cbor_cmd_count;
458 if (msg_count) *msg_count =
g_ctaphid.msg_cmd_count;
475 if (!
g_ctaphid.initialized || !packet)
return false;
477 xSemaphoreTake(
g_ctaphid.mutex, portMAX_DELAY);
480 uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
496 uint32_t cid = ((uint32_t)packet[0] << 24) | ((uint32_t)packet[1] << 16) |
497 ((uint32_t)packet[2] << 8) | packet[3];
498 bool is_init = (packet[4] & 0x80) != 0;
502 uint8_t cmd = packet[4] & 0x7F;
503 uint16_t bcnt = ((uint16_t)packet[5] << 8) | packet[6];
517 ctaphid_channel_t temp = {};
523 temp.last_activity = now;
533 LOG_W(
TAG,
"Unknown CID 0x%08lX", cid);
553 ch->last_activity = xTaskGetTickCount() * portTICK_PERIOD_MS;
557 memcpy(ch->buffer, packet + 7, copy);
561 if (ch->offset >= ch->bcnt) {
566 uint8_t seq = packet[4] & 0x7F;
569 if (!ch || !ch->active) {
570 LOG_W(
TAG,
"Continuation for inactive channel");
577 if (seq != ch->seq) {
578 LOG_W(
TAG,
"Invalid sequence: got %d, expected %d", seq, ch->seq);
586 uint16_t remaining = ch->bcnt - ch->offset;
588 memcpy(ch->buffer + ch->offset, packet + 5, copy);
591 ch->last_activity = xTaskGetTickCount() * portTICK_PERIOD_MS;
594 if (ch->offset >= ch->bcnt) {
609 if (
g_ctaphid.channels[i].response_pending)
return true;
621 if (
g_ctaphid.channels[idx].response_pending) {
635 if (!packet)
return false;
637 xSemaphoreTake(
g_ctaphid.mutex, portMAX_DELAY);
647 LOG_W(
TAG,
"Send aborted: CTAP2 cancelled by host (cid=0x%08lX offset=%u/%u)",
648 ch->response_cid, ch->response_offset, ch->response_len);
649 ch->response_pending =
false;
650 ch->response_offset = 0;
651 ch->response_len = 0;
656 uint16_t remaining = ch->response_len - ch->response_offset;
658 if (ch->response_offset == 0) {
662 ch->response_buffer, remaining);
664 ch->response_offset = sent;
669 ch->response_buffer + ch->response_offset,
672 ch->response_offset += sent;
676 if (ch->response_offset >= ch->response_len) {
677 ch->response_pending =
false;
691 uint8_t data = status;
704 uint8_t data = error;
713 if (!
g_ctaphid.channels[i].response_pending) {
720 LOG_W(
TAG,
"send_error: no channel slot available for CID 0x%08lX", cid);
724 LOG_W(
TAG,
"Sending error 0x%02X to CID 0x%08lX", error, cid);
733 uint32_t now = xTaskGetTickCount() * portTICK_PERIOD_MS;
735 xSemaphoreTake(
g_ctaphid.mutex, portMAX_DELAY);
738 ctaphid_channel_t *ch = &
g_ctaphid.channels[i];
740 LOG_W(
TAG,
"Channel 0x%08lX timed out", ch->cid);
763 if (
g_ctaphid.channels[i].active)
return true;
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
#define LOG_D(tag, fmt,...)
#define LOG_I(tag, fmt,...)
#define LOG_E(tag, fmt,...)
void ctap2_cancel(void)
Marks current CTAP2 operation as cancelled.
uint8_t ctap2_process_command(const uint8_t *cmd, uint16_t cmd_len, uint8_t *response, uint16_t *response_len)
Dispatches one CTAP2 command and writes response payload.
void ctap2_clear_cancel(void)
Clears the cancel flag. Called when a new CTAPHID channel is opened so a cancel from a previous chann...
bool ctap2_is_cancelled(void)
Returns true if the current CTAP2 operation has been cancelled.
#define CTAPHID_RATE_LIMIT_MAX_CMDS
void ctaphid_get_cmd_counts(uint32_t *cbor_count, uint32_t *msg_count)
Returns cumulative counters for CTAPHID CBOR and MSG commands.
static void handle_cancel(uint32_t cid)
Handles CTAPHID CANCEL requests and aborts active CTAP2 work.
bool ctaphid_init(void)
Initializes CTAPHID transport state and synchronization primitives.
#define CTAPHID_MAX_CHANNELS
void ctaphid_check_timeout(void)
Expires active channels whose message assembly timeout elapsed.
static void handle_cbor(uint32_t cid, const uint8_t *data, uint16_t len)
Handles CTAPHID CBOR requests by dispatching to the CTAP2 command processor.
static void init_channel_slot(int index, uint32_t cid)
Allocates or reuses a channel slot for the provided channel identifier.
static void prepare_response(uint32_t cid, uint8_t cmd, const uint8_t *data, uint16_t len)
Stores a command response so it can be packetized and read out later.
bool ctaphid_is_busy(void)
Reports whether any CTAPHID channel currently has an active transaction.
uint32_t rate_window_start
#define CTAPHID_RATE_LIMIT_WINDOW_MS
Runtime state and channel bookkeeping for CTAPHID transport.
uint32_t ctaphid_get_current_cid(void)
Returns the channel identifier of the currently processed request.
#define CTAPHID_MSG_TIMEOUT_MS
static ctaphid_channel_t * pick_next_response_channel(void)
Selects the next channel with a pending response in a round-robin fashion.
static ctaphid_channel_t * find_channel(uint32_t cid)
Returns the channel record for a given channel identifier.
bool ctaphid_has_response(void)
Indicates whether any channel has a response queued for host retrieval.
static void build_init_packet(uint8_t *packet, uint32_t cid, uint8_t cmd, uint16_t bcnt, const uint8_t *data, uint16_t data_len)
Builds a CTAPHID initialization packet.
static uint8_t s_response_buffers[8][CTAPHID_MAX_MSG_SIZE]
static struct @105157365137133325115210077320112075163264255254 g_ctaphid
static void prepare_response_on(ctaphid_channel_t *ch, uint32_t send_cid, uint8_t cmd, const uint8_t *data, uint16_t len)
Stores a response on the given channel slot but addressed to a different CID.
static void process_complete_message(ctaphid_channel_t *ch)
Dispatches a fully assembled channel message to its command handler.
ctaphid_channel_t channels[8]
static void handle_ping(uint32_t cid, const uint8_t *data, uint16_t len)
Handles CTAPHID PING by echoing the request payload.
static void handle_wink(uint32_t cid)
Handles CTAPHID WINK requests.
static void build_cont_packet(uint8_t *packet, uint32_t cid, uint8_t seq, const uint8_t *data, uint16_t data_len)
Builds a CTAPHID continuation packet.
void ctaphid_reset_cmd_counts(void)
Resets CTAPHID command counters.
#define CTAPHID_DEBUG_PACKETS
Enable verbose packet-level debug logging when feature flags allow it.
bool ctaphid_get_response_packet(uint8_t *packet)
Retrieves the next response HID packet from a per-channel response queue.
static ctaphid_channel_t * alloc_channel(uint32_t cid)
void ctaphid_send_error(uint32_t cid, uint8_t error)
Queues a CTAPHID ERROR response for the given channel.
static void handle_init(uint32_t cid, const uint8_t *data, uint16_t len)
Handles CTAPHID INIT and returns negotiated channel/capability data.
bool ctaphid_process_packet(const uint8_t *packet)
Processes one incoming 64-byte CTAPHID packet.
static uint32_t allocate_cid(void)
Allocates the next non-broadcast CTAPHID channel identifier.
static uint8_t s_msg_buffers[8][CTAPHID_MAX_MSG_SIZE]
Message and response buffers located in PSRAM to save internal RAM.
void ctaphid_send_keepalive(uint32_t cid, uint8_t status)
Sends a CTAPHID KEEPALIVE packet immediately over USB.
uint8_t response_rr_index
bool fido2_usb_write(const uint8_t *buffer)
Sends one CTAPHID packet over USB HID.
#define CTAPHID_ERR_INVALID_CHANNEL
#define CTAPHID_ERR_CHANNEL_BUSY
#define CTAPHID_INIT_DATA
#define CTAPHID_ERR_MSG_TIMEOUT
#define CTAPHID_VENDOR_LAST
#define CTAPHID_PACKET_SIZE
#define CTAPHID_ERR_INVALID_CMD
#define CTAPHID_ERR_INVALID_SEQ
#define CTAPHID_ERR_INVALID_LEN
#define CTAPHID_ERR_OTHER
#define CTAPHID_KEEPALIVE
#define CTAPHID_CONT_DATA
#define CTAPHID_BROADCAST_CID
#define CTAPHID_MAX_MSG_SIZE
#define CTAPHID_VENDOR_FIRST
void winkBacklight(uint8_t count=2, uint16_t period_ms=150)
Blink the backlight as a visual "look at me" signal.
bool fido2_ui_abort_prompt()
Forcibly denies any in-flight user-presence prompt.
bool fido2_usb_write(const uint8_t *buffer)
Sends one CTAPHID packet over USB HID.
uint16_t u2f_process_apdu(const uint8_t *apdu, uint16_t apdu_len, uint8_t *response, uint16_t response_max)
Parses U2F APDU and dispatches to instruction handlers.