Expand dynamic file index to 2048 entries

Use a compact open-addressed hash table for fast lookup, stable deletion, and tombstone reuse without changing the flash format.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
This commit is contained in:
Pol Henarejos
2026-07-16 20:37:35 +02:00
parent 31b4db939c
commit c8e90824ae
4 changed files with 164 additions and 68 deletions
+120 -56
View File
@@ -23,19 +23,50 @@
#define MAX_DEPTH 4
#define MAX_DYNAMIC_FILES 256
#define DYNAMIC_FILE_SLOTS 2309u
#define MAX_DYNAMIC_FILES 2048u
#define DYNAMIC_FID_EMPTY 0x0000u
#define DYNAMIC_FID_DELETED 0xffffu
extern const uintptr_t end_data_pool;
extern const uintptr_t start_data_pool;
extern const uintptr_t end_rom_pool;
extern const uintptr_t start_rom_pool;
#ifndef ENABLE_EMULATION
file_t sef_phy = {.fid = EF_PHY, .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH | FILE_PERSISTENT, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}};
file_t *ef_phy = &sef_phy;
file_entry_t sef_phy = {.fid = EF_PHY, .parent = 5, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH | FILE_PERSISTENT, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}};
file_t *ef_phy = &sef_phy.file;
#endif
static const file_entry_t *file_metadata(const file_t *file) {
if (!file) {
return NULL;
}
#ifndef ENABLE_EMULATION
if (file == ef_phy) {
return &sef_phy;
}
#endif
uintptr_t addr = (uintptr_t)file - offsetof(file_entry_t, file);
uintptr_t first = (uintptr_t)&file_entries[0];
uintptr_t last = (uintptr_t)file_last;
if (addr < first || addr > last || (addr - first) % sizeof(file_entry_t) != 0) {
return NULL;
}
return (const file_entry_t *)addr;
}
uint8_t file_get_type(const file_t *file) {
const file_entry_t *entry = file_metadata(file);
return entry ? entry->type : FILE_TYPE_WORKING_EF;
}
//puts FCI in the RAPDU
void file_process_fci(const file_t *pe, int fmd) {
const file_entry_t *entry = file_metadata(pe);
uint8_t type = file_get_type(pe);
uint8_t structure = entry ? entry->ef_structure : FILE_EF_TRANSPARENT;
const uint8_t *name = entry ? entry->name : NULL;
res_APDU_size = 0;
if (fmd) {
res_APDU[res_APDU_size++] = 0x6f;
@@ -48,7 +79,7 @@ void file_process_fci(const file_t *pe, int fmd) {
res_APDU[res_APDU_size++] = 0x81;
res_APDU[res_APDU_size++] = 2;
if (pe->data) {
if ((pe->type & FILE_DATA_FUNC) == FILE_DATA_FUNC) {
if ((type & FILE_DATA_FUNC) == FILE_DATA_FUNC) {
int (*data_fn)(const file_t *, int) = (int (*)(const file_t *, int))(uintptr_t)pe->data;
uint16_t len = (uint16_t)data_fn(pe, 0);
res_APDU_size += put_uint16_be(len, res_APDU + res_APDU_size);
@@ -66,13 +97,13 @@ void file_process_fci(const file_t *pe, int fmd) {
res_APDU[res_APDU_size++] = 0x82;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size] = 0;
if (pe->type & FILE_TYPE_INTERNAL_EF) {
if (type & FILE_TYPE_INTERNAL_EF) {
res_APDU[res_APDU_size++] |= 0x08;
}
else if (pe->type & FILE_TYPE_WORKING_EF) {
res_APDU[res_APDU_size++] |= pe->ef_structure & 0x7;
else if (type & FILE_TYPE_WORKING_EF) {
res_APDU[res_APDU_size++] |= structure & 0x7;
}
else if (pe->type & FILE_TYPE_DF) {
else if (type & FILE_TYPE_DF) {
res_APDU[res_APDU_size++] |= 0x38;
}
else {
@@ -82,11 +113,11 @@ void file_process_fci(const file_t *pe, int fmd) {
res_APDU[res_APDU_size++] = 0x83;
res_APDU[res_APDU_size++] = 2;
res_APDU_size += put_uint16_be(pe->fid, res_APDU + res_APDU_size);
if (pe->name) {
if (name) {
res_APDU[res_APDU_size++] = 0x84;
res_APDU[res_APDU_size++] = MIN(pe->name[0], 16);
memcpy(res_APDU + res_APDU_size, pe->name + 2, MIN(pe->name[0], 16));
res_APDU_size += MIN(pe->name[0], 16);
res_APDU[res_APDU_size++] = MIN(name[0], 16);
memcpy(res_APDU + res_APDU_size, name + 2, MIN(name[0], 16));
res_APDU_size += MIN(name[0], 16);
}
memcpy(res_APDU + res_APDU_size, "\x8A\x01\x05", 3); //life-cycle (5 -> activated)
res_APDU_size += 3;
@@ -105,29 +136,31 @@ void file_process_fci(const file_t *pe, int fmd) {
}
}
uint16_t dynamic_files = 0;
file_t dynamic_file[MAX_DYNAMIC_FILES];
static uint16_t dynamic_files = 0;
static file_t dynamic_file[DYNAMIC_FILE_SLOTS];
bool card_terminated = false;
static bool is_parent(const file_t *child, const file_t *parent) {
if (child == parent) {
static bool is_parent(const file_entry_t *child, const file_t *parent) {
if (&child->file == parent) {
return true;
}
if (child == MF) {
if (&child->file == MF) {
return false;
}
return is_parent(&file_entries[child->parent], parent);
}
file_t *get_parent(file_t *f) {
return &file_entries[f->parent];
const file_entry_t *entry = file_metadata(f);
uint8_t parent = entry ? entry->parent : 5;
return &file_entries[parent].file;
}
file_t *file_search_by_name(uint8_t *name, uint16_t namelen) {
for (file_t *p = file_entries; p != file_last; p++) {
for (file_entry_t *p = file_entries; p != file_last; p++) {
if (p->name && *p->name == apdu.nc && memcmp(p->name + 1, name, namelen) == 0) {
return p;
return &p->file;
}
}
return NULL;
@@ -139,13 +172,13 @@ file_t *file_search_by_fid(const uint16_t fid, const file_t *parent, const uint8
return ef_phy;
}
#endif
for (file_t *p = file_entries; p != file_last; p++) {
for (file_entry_t *p = file_entries; p != file_last; p++) {
if (p->fid != 0x0000 && p->fid == fid) {
if (!parent || (parent && is_parent(p, parent))) {
if (!sp || sp == SPECIFY_ANY ||
(((sp & SPECIFY_EF) && (p->type & (FILE_TYPE_INTERNAL_EF|FILE_TYPE_WORKING_EF))) ||
((sp & SPECIFY_DF) && p->type == FILE_TYPE_DF))) {
return p;
return &p->file;
}
}
}
@@ -153,18 +186,52 @@ file_t *file_search_by_fid(const uint16_t fid, const file_t *parent, const uint8
return NULL;
}
static size_t dynamic_hash(uint16_t fid) {
return ((uint32_t)fid * 40503u) % DYNAMIC_FILE_SLOTS;
}
static size_t dynamic_hash_step(uint16_t fid) {
return 1u + (((uint32_t)fid * 97u) % (DYNAMIC_FILE_SLOTS - 1u));
}
static file_t *search_dynamic_file(uint16_t fid) {
for (int i = 0; i < dynamic_files; i++) {
if (dynamic_file[i].fid == fid) {
return &dynamic_file[i];
size_t slot = dynamic_hash(fid);
size_t step = dynamic_hash_step(fid);
for (size_t probes = 0; probes < DYNAMIC_FILE_SLOTS; probes++) {
file_t *file = &dynamic_file[slot];
if (file->fid == DYNAMIC_FID_EMPTY) {
return NULL;
}
if (file->fid == fid) {
return file;
}
slot = (slot + step) % DYNAMIC_FILE_SLOTS;
}
return NULL;
}
static file_t *find_dynamic_slot(uint16_t fid) {
file_t *deleted = NULL;
size_t slot = dynamic_hash(fid);
size_t step = dynamic_hash_step(fid);
for (size_t probes = 0; probes < DYNAMIC_FILE_SLOTS; probes++) {
file_t *file = &dynamic_file[slot];
if (file->fid == DYNAMIC_FID_EMPTY) {
return deleted ? deleted : file;
}
if (file->fid == DYNAMIC_FID_DELETED && !deleted) {
deleted = file;
}
slot = (slot + step) % DYNAMIC_FILE_SLOTS;
}
return deleted;
}
void file_for_each_dynamic(file_iter_cb cb, void *ctx) {
for (int i = 0; i < dynamic_files; i++) {
if (file_has_data(&dynamic_file[i]) && !cb(&dynamic_file[i], ctx)) {
for (size_t i = 0; i < DYNAMIC_FILE_SLOTS; i++) {
if (dynamic_file[i].fid != DYNAMIC_FID_EMPTY &&
dynamic_file[i].fid != DYNAMIC_FID_DELETED &&
file_has_data(&dynamic_file[i]) && !cb(&dynamic_file[i], ctx)) {
break;
}
}
@@ -186,13 +253,13 @@ static uint8_t make_path_buf(const file_t *pe, uint8_t *buf, uint8_t buflen, con
return 0;
}
put_uint16_be(pe->fid, buf);
return make_path_buf(&file_entries[pe->parent], buf + 2, buflen - 2, top) + 2;
return make_path_buf(get_parent((file_t *)pe), buf + 2, buflen - 2, top) + 2;
}
static uint8_t make_path(const file_t *pe, const file_t *top, uint8_t *path) {
uint8_t buf[MAX_DEPTH * 2], *p = path;
put_uint16_be(pe->fid, buf);
uint8_t depth = make_path_buf(&file_entries[pe->parent], buf + 2, sizeof(buf) - 2, top) + 2;
uint8_t depth = make_path_buf(get_parent((file_t *)pe), buf + 2, sizeof(buf) - 2, top) + 2;
for (int d = depth - 2; d >= 0; d -= 2) {
memcpy(p, buf + d, 2);
p += 2;
@@ -205,10 +272,10 @@ file_t *file_search_by_path(const uint8_t *pe_path, uint8_t pathlen, const file_
if (pathlen > sizeof(path)) {
return NULL;
}
for (file_t *p = file_entries; p != file_last; p++) {
uint8_t depth = make_path(p, parent, path);
for (file_entry_t *p = file_entries; p != file_last; p++) {
uint8_t depth = make_path(&p->file, parent, path);
if (pathlen == depth && memcmp(path, pe_path, depth) == 0) {
return p;
return &p->file;
}
}
return NULL;
@@ -220,7 +287,8 @@ const file_t *selected_applet = NULL;
bool isUserAuthenticated = false;
bool file_authenticate_action(const file_t *ef, uint8_t op) {
uint8_t acl = ef->acl[op];
const file_entry_t *entry = file_metadata(ef);
uint8_t acl = entry ? entry->acl[op] : 0x00;
if (acl == 0x0) {
return true;
}
@@ -245,11 +313,12 @@ void file_initialize_flash(bool hard) {
flash_program_block(end_data_pool, empty, sizeof(empty));
flash_commit();
}
for (file_t *f = file_entries; f != file_last; f++) {
for (file_entry_t *f = file_entries; f != file_last; f++) {
if ((f->type & FILE_DATA_FLASH) == FILE_DATA_FLASH) {
f->data = NULL;
}
}
memset(dynamic_file, 0, sizeof(dynamic_file));
dynamic_files = 0;
}
@@ -342,16 +411,17 @@ static int delete_dynamic_file(file_t *f) {
if (f == NULL) {
return PICOKEYS_ERR_FILE_NOT_FOUND;
}
for (int i = 0; i < dynamic_files; i++) {
if (dynamic_file[i].fid == f->fid) {
for (int j = i + 1; j < dynamic_files; j++) {
memcpy(&dynamic_file[j - 1], &dynamic_file[j], sizeof(file_t));
}
dynamic_files--;
return PICOKEYS_OK;
}
uintptr_t addr = (uintptr_t)f;
uintptr_t first = (uintptr_t)&dynamic_file[0];
uintptr_t end = (uintptr_t)&dynamic_file[DYNAMIC_FILE_SLOTS];
if (addr < first || addr >= end || (addr - first) % sizeof(file_t) != 0 ||
f->fid == DYNAMIC_FID_EMPTY || f->fid == DYNAMIC_FID_DELETED) {
return PICOKEYS_ERR_FILE_NOT_FOUND;
}
return PICOKEYS_ERR_FILE_NOT_FOUND;
f->data = NULL;
f->fid = DYNAMIC_FID_DELETED;
dynamic_files--;
return PICOKEYS_OK;
}
file_t *file_new(uint16_t fid) {
@@ -359,22 +429,16 @@ file_t *file_new(uint16_t fid) {
if ((f = file_search(fid))) {
return f;
}
if (dynamic_files == MAX_DYNAMIC_FILES) {
if (fid == DYNAMIC_FID_EMPTY || fid == DYNAMIC_FID_DELETED || dynamic_files >= MAX_DYNAMIC_FILES) {
return NULL;
}
f = &dynamic_file[dynamic_files];
f = find_dynamic_slot(fid);
if (!f) {
return NULL;
}
f->data = NULL;
f->fid = fid;
dynamic_files++;
file_t file = {
.fid = fid,
.parent = 5,
.name = NULL,
.type = FILE_TYPE_WORKING_EF,
.ef_structure = FILE_EF_TRANSPARENT,
.data = NULL,
.acl = { 0 }
};
memcpy(f, &file, sizeof(file_t));
//memset((uint8_t *)f->acl, 0x90, sizeof(f->acl));
return f;
}
uint16_t meta_find(uint16_t fid, uint8_t **out) {
+40 -8
View File
@@ -68,13 +68,44 @@
__pragma( pack(push, 1) )
#endif
typedef struct file {
const uint8_t *name;
uint8_t *data; //should include 2 bytes len at begining
const uint16_t fid;
uint16_t fid;
}
#ifndef _MSC_VER
__attribute__ ((packed))
#endif
file_t;
/*
* Static files need ISO/CCID metadata. Dynamic files only need the flash
* address and FID, so keeping this metadata out of file_t allows the dynamic
* index to grow without increasing its historical RAM budget.
*
* The anonymous union preserves the existing designated initializers for
* file_entries[] while making &entry.file a compact file_t handle.
*/
typedef struct file_entry {
#ifdef ENABLE_EMULATION
const uint8_t *name;
#endif
union {
file_t file;
struct {
uint8_t *data;
uint16_t fid;
}
#ifndef _MSC_VER
__attribute__ ((packed))
#endif
;
};
#ifndef ENABLE_EMULATION
const uint8_t *name;
#endif
uint8_t acl[7];
const uint8_t parent; //entry number in the whole table!!
const uint8_t type;
const uint8_t ef_structure;
uint8_t parent; //entry number in the whole table!!
uint8_t type;
uint8_t ef_structure;
#ifdef ENABLE_EMULATION
uint32_t _padding;
#endif
@@ -84,14 +115,14 @@ __pragma( pack(pop) )
#else
__attribute__ ((packed))
#endif
file_t;
file_entry_t;
extern file_t *currentEF;
extern file_t *currentDF;
extern const file_t *selected_applet;
extern const file_t *MF;
extern const file_t *file_last;
extern const file_entry_t *file_last;
extern bool card_terminated;
extern file_t *file_search_by_fid(const uint16_t fid, const file_t *parent, const uint8_t sp);
@@ -103,7 +134,7 @@ extern void file_process_fci(const file_t *pe, int fmd);
extern void file_scan_flash(void);
extern void file_initialize_flash(bool);
extern file_t file_entries[];
extern file_entry_t file_entries[];
extern uint8_t *file_read(const uint8_t *addr);
extern uint16_t file_read_uint16(const uint8_t *addr);
@@ -112,6 +143,7 @@ extern uint8_t file_read_uint8_offset(const file_t *ef, const uint16_t offset);
extern bool file_has_data(const file_t *);
extern uint8_t *file_get_data(const file_t *tf);
extern uint16_t file_get_size(const file_t *tf);
extern uint8_t file_get_type(const file_t *tf);
extern int file_put_data(file_t *file, const uint8_t *data, uint16_t len);
extern file_t *file_new(uint16_t);
extern int flash_clear_file(file_t *file);
+3 -3
View File
@@ -17,12 +17,12 @@
#include "file.h"
file_t file_entries[] = {
file_entry_t file_entries[] = {
/* 0 */ { .fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL,
.ef_structure = 0, .acl = { 0 } }, // MF
/* 1 */ { .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_NOT_KNOWN, .data = NULL,
.ef_structure = 0, .acl = { 0 } } //end
};
const file_t *MF = &file_entries[0];
const file_t *file_last = &file_entries[sizeof(file_entries) / sizeof(file_t) - 1];
const file_t *MF = &file_entries[0].file;
const file_entry_t *file_last = &file_entries[sizeof(file_entries) / sizeof(file_entry_t) - 1];
+1 -1
View File
@@ -142,7 +142,7 @@ static int flash_write_data_to_file_offset(file_t *file, const uint8_t *data, ui
}
}
uintptr_t new_addr = allocate_free_addr(len, (file->type & FILE_PERSISTENT) == FILE_PERSISTENT);
uintptr_t new_addr = allocate_free_addr(len, (file_get_type(file) & FILE_PERSISTENT) == FILE_PERSISTENT);
//printf("na %x\n",new_addr);
if (new_addr == 0x0) {
return PICOKEYS_ERR_NO_MEMORY;