From 1016ebab512448fa068659741e387888723e69a2 Mon Sep 17 00:00:00 2001 From: hanez Date: Thu, 7 May 2026 22:23:01 +0200 Subject: [PATCH] Fixed bug in the PCSC extension and added more Doxygen documentation to all extensions. (0.41.8) --- CMakeLists.txt | 2 +- src/extensions/curl.c | 68 ++++++++++++++++--- src/extensions/ini.c | 75 +++++++++++++++++---- src/extensions/json.c | 105 ++++++++++++++++++++++++------ src/extensions/openssl.c | 128 +++++++++++++++++++++++++++--------- src/extensions/pcsc.c | 107 ++++++++++++++++++++++++------ src/extensions/sqlite.c | 103 +++++++++++++++++++++++++---- src/extensions/xml2.c | 137 ++++++++++++++++++++++++++++++++------- web/css/main.scss | 3 +- 9 files changed, 596 insertions(+), 132 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 48704bc..c00ae51 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.10) -project(fun VERSION 0.41.7 LANGUAGES C) +project(fun VERSION 0.41.8 LANGUAGES C) set(CMAKE_C_STANDARD 99) set(CMAKE_C_STANDARD_REQUIRED ON) diff --git a/src/extensions/curl.c b/src/extensions/curl.c index c3cf7ad..ebd47d4 100644 --- a/src/extensions/curl.c +++ b/src/extensions/curl.c @@ -11,8 +11,43 @@ * @file curl.c * @brief libcurl helpers and buffers used by HTTP-related VM opcodes. * - * Declares small buffer helpers and libcurl write callbacks that support - * network-related opcodes when FUN_WITH_CURL is enabled. + * This module centralizes small, concrete libcurl utilities used by VM + * opcodes under src/vm/http/*.c (or similar). The opcodes themselves perform + * VM stack marshalling and call these helpers for I/O glue. Keeping the + * concrete logic here mirrors other extensions (e.g., PCRE2, SQLite) and + * allows the opcode code to remain minimal. + * + * Build-time feature flag: + * - The code in this file is compiled only when FUN_WITH_CURL is enabled. + * When disabled, curl-related opcodes should be compiled with no-op + * fallbacks in their respective files. + * + * Buffering and ownership model: + * - FunCurlBuf is a small growable buffer intended for use with libcurl's + * CURLOPT_WRITEFUNCTION callback. The buffer expands with realloc() as data + * arrives and is maintained NUL-terminated for convenience when the content + * is treated as a C-string. The caller is responsible for allocating and + * freeing the FunCurlBuf fields (i.e., initialize { .d=NULL, .n=0 } before + * first use and free(b.d) afterwards). + * + * Callbacks provided: + * - fun_curl_write_cb(): appends incoming data to a FunCurlBuf, keeping it + * NUL-terminated. Returns the number of bytes handled (sz*nm) on success or + * 0 on allocation failure to signal an error to libcurl. + * - fun_curl_file_write_cb(): writes incoming data directly to a FILE* passed + * via CURLOPT_WRITEDATA. Returns the number of elements written as per + * fwrite(). + * + * Error handling: + * - The write-to-buffer callback returns 0 on realloc failure, causing libcurl + * to abort the transfer and report CURLE_WRITE_ERROR. The file writer relies + * on fwrite()'s return value; callers should check the CURLcode after + * performing the transfer for final status. + * + * Thread-safety: + * - These helpers themselves are stateless and thread-safe as long as each + * CURL easy handle and associated buffers/FILE* are not shared concurrently + * across threads without external synchronization. */ /* Ensure libcurl headers and helpers are defined at file scope (not inside vm_run) */ @@ -23,7 +58,13 @@ * @brief Simple growable buffer for libcurl write callbacks. * * The buffer grows via realloc as more data arrives. The content is kept - * NUL-terminated for convenience. + * NUL-terminated for convenience so it can be treated as a C-string. + * + * Usage pattern: + * - Initialize: FunCurlBuf b = { .d = NULL, .n = 0 }; + * - Set CURLOPT_WRITEFUNCTION = fun_curl_write_cb and CURLOPT_WRITEDATA = &b + * - After curl_easy_perform(), b.d points to the collected data (length b.n). + * - Free with free(b.d) when done. */ typedef struct { char *d; /**< Data pointer (NUL-terminated). */ @@ -34,13 +75,15 @@ typedef struct { * @brief libcurl write callback that appends data to a FunCurlBuf. * * Reallocates the destination buffer as needed and keeps it NUL-terminated. + * The buffer must be initialized by the caller with { .d=NULL, .n=0 } prior to + * first use. * * @param ptr Pointer to incoming data block provided by libcurl. - * @param sz Size of each data element. - * @param nm Number of elements in this block. - * @param ud User data; must be a FunCurlBuf*. - * @return Number of bytes actually handled (sz*nm) on success, 0 on failure - * to signal an error to libcurl. + * @param sz Size of each data element (as provided by libcurl). + * @param nm Number of elements in this block (as provided by libcurl). + * @param ud User data; must be a FunCurlBuf* (passed via CURLOPT_WRITEDATA). + * @return Number of bytes actually handled (sz*nm) on success; 0 on failure to + * signal an error to libcurl (which will abort with CURLE_WRITE_ERROR). */ static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) { size_t add = sz * nm; @@ -57,11 +100,16 @@ static size_t fun_curl_write_cb(void *ptr, size_t sz, size_t nm, void *ud) { /** * @brief libcurl write callback that writes directly to a FILE*. * + * Suitable for large downloads or when incremental flushing to disk is + * desired. The FILE* should be opened in binary mode (e.g., "wb"). + * * @param ptr Pointer to incoming data. * @param sz Size of each element. * @param nm Number of elements. - * @param ud User data; must be a FILE* opened for writing in binary mode. - * @return Number of elements written (as returned by fwrite). + * @param ud User data; must be a FILE* opened for writing in binary mode + * (passed via CURLOPT_WRITEDATA). + * @return Number of elements written (as returned by fwrite). Returning a + * value smaller than nm will signal an error to libcurl. */ static size_t fun_curl_file_write_cb(void *ptr, size_t sz, size_t nm, void *ud) { FILE *f = (FILE *)ud; diff --git a/src/extensions/ini.c b/src/extensions/ini.c index 98d4483..006cb2d 100644 --- a/src/extensions/ini.c +++ b/src/extensions/ini.c @@ -9,10 +9,34 @@ /** * @file ini.c - * @brief INI parsing helpers and VM opcode support via iniparser. + * @brief iniparser helpers for Fun VM INI-related opcodes (conditional build). * - * Provides includes and declarations required for INI-related opcodes when - * FUN_WITH_INI is enabled at build time. + * This module centralizes small utilities and a tiny handle registry used by + * VM opcodes that interact with INI configuration files through the + * iniparser library. Placing the concrete logic in src/extensions/ keeps the + * opcode implementations minimal — they focus on VM stack marshalling and + * delegate the concrete work here, mirroring other extensions (PCRE2, SQLite, XML2). + * + * Build-time feature flag: + * - All code in this file is compiled only when FUN_WITH_INI is enabled. + * When disabled, INI-related opcodes should provide safe no-op fallbacks + * in their respective VM files. + * + * Registry and ownership model: + * - A very small fixed-size registry (g_ini) maps small positive integers to + * iniparser dictionaries. The registry OWNS the dictionary pointer and will + * call iniparser_freedict() when a handle is freed via ini_free_handle(). + * - Handles are in the range [1, 63]; 0 indicates failure/invalid. + * - The registry does not perform I/O; callers are responsible for creating + * the dictionary (e.g., iniparser_load()) prior to registration. + * + * Thread-safety: + * - Not thread-safe. If used from multiple threads, coordinate access + * externally. + * + * Key formatting helper: + * - ini_make_full_key() produces a section-qualified key of the form + * "section:key", which is what iniparser expects for lookups. */ #ifdef FUN_WITH_INI @@ -37,19 +61,33 @@ /** * @brief One slot in the global INI handle registry. - * @details Associates an iniparser dictionary pointer with an in-use flag. + * + * Associates an iniparser dictionary pointer with an in-use flag. The + * registry takes ownership of the dictionary for the lifetime of the slot and + * will free it when the handle is released via ini_free_handle(). */ typedef struct { dictionary *dict; int in_use; } IniSlot; +/** + * @brief Fixed-size registry of iniparser dictionaries. + * + * Index 0 is reserved and never used for valid handles. Valid handles are in + * the range [1, 63]. When an entry's in_use flag is 0, the slot is available. + */ IniSlot g_ini[64]; /** * @brief Allocate a registry handle for a newly created dictionary. - * @param d Pointer to an iniparser dictionary. - * @return Handle id (>0) on success or 0 on failure. + * + * Transfers ownership of the dictionary pointer to the registry on success. + * + * @param d Pointer to an initialized iniparser dictionary (e.g., from + * iniparser_load()). Must not be NULL. + * @return int Positive handle (>0) on success; 0 if no free slot is available + * or if d is NULL. */ int ini_alloc_handle(dictionary *d) { if (!d) return 0; @@ -65,8 +103,13 @@ int ini_alloc_handle(dictionary *d) { /** * @brief Look up a dictionary pointer by registry handle. + * + * The returned pointer is owned by the registry; callers must not free it + * directly. Use ini_free_handle() to release the association. + * * @param h Handle id previously returned by ini_alloc_handle(). - * @return Pointer to dictionary or NULL if not found. + * @return dictionary* Pointer to dictionary if the handle is valid and in use; + * NULL otherwise. */ dictionary *ini_get(int h) { if (h > 0 && h < (int)(sizeof(g_ini) / sizeof(g_ini[0])) && g_ini[h].in_use) return g_ini[h].dict; @@ -75,8 +118,12 @@ dictionary *ini_get(int h) { /** * @brief Free a previously allocated handle and close its dictionary. + * + * If the slot holds a dictionary, iniparser_freedict() is called. The slot is + * then marked available for reuse. + * * @param h Handle id to free. - * @return 1 on success, 0 on error (invalid handle or not in use). + * @return int 1 on success; 0 if the handle is invalid or not in use. */ int ini_free_handle(int h) { if (h <= 0 || h >= (int)(sizeof(g_ini) / sizeof(g_ini[0])) || !g_ini[h].in_use) return 0; @@ -87,11 +134,17 @@ int ini_free_handle(int h) { } /** - * @brief Build a fully qualified key "section:key" into a caller-provided buffer. + * @brief Build a fully qualified key of the form "section:key". + * + * Writes into a caller-provided buffer a key qualified by section, as expected + * by iniparser lookups. If sec is NULL, an empty section is used. If key is + * NULL, an empty key is used. The function is a no-op if buf is NULL or cap is + * 0. Output is always NUL-terminated (subject to snprintf semantics). + * * @param buf Destination buffer. * @param cap Capacity of buf in bytes (including terminator). - * @param sec Section name (may be NULL for default section). - * @param key Key name (must not be NULL). + * @param sec Section name (may be NULL for default/empty section). + * @param key Key name (may be NULL to produce an empty key). */ void ini_make_full_key(char *buf, size_t cap, const char *sec, const char *key) { if (!buf || cap == 0) return; diff --git a/src/extensions/json.c b/src/extensions/json.c index 29b4832..23d37f4 100644 --- a/src/extensions/json.c +++ b/src/extensions/json.c @@ -9,11 +9,47 @@ /** * @file json.c - * @brief JSON extension helpers and VM opcode cases (conditional build). + * @brief json-c helpers for Fun VM JSON-related opcodes (conditional build). * - * Provides conversions between json-c objects and the Fun Value type and - * supplies VM opcode case implementations when FUN_WITH_JSON is enabled. - * This unit may be included or compiled conditionally based on build flags. + * This module centralizes small utilities that convert between json-c objects + * and the Fun VM's Value type. Keeping the concrete conversion logic in + * src/extensions/ allows VM opcode implementations to remain minimal — they + * focus on VM stack marshalling and delegate the heavy lifting here, mirroring + * other extensions (PCRE2, SQLite, XML2, INI). + * + * Build-time feature flag: + * - All code in this file is compiled only when FUN_WITH_JSON is enabled. When + * disabled, JSON-related opcodes should provide safe no-op fallbacks in + * their respective VM files. + * + * Type mapping between json-c and Fun: + * - json null -> Fun Nil + * - json boolean -> Fun Bool + * - json number -> Fun Int (64-bit) or Fun Float (double) depending on + * the underlying json-c representation + * - json string -> Fun String (assumed UTF-8 from json-c) + * - json array -> Fun Array (elements converted recursively) + * - json object -> Fun Map (values converted recursively) + * + * Ownership and memory: + * - The conversion functions allocate new Fun Values and/or new json-c trees. + * The caller owns the returned result and is responsible for releasing it + * (free_value for Fun Values, json_object_put for json-c objects) when no + * longer needed. + * - No global state is retained; all allocations are tied to the returned + * objects. + * + * Encoding and limits: + * - json-c strings are treated as UTF-8; the Fun VM strings are expected to be + * UTF-8 as well. No transcoding is performed. + * - Deeply nested inputs convert recursively; extremely deep trees may exhaust + * the C stack. Cycles are not possible in well-formed JSON; if presented via + * custom json_object graphs, behaviour is undefined (may loop or duplicate). + * + * Thread-safety: + * - Not intrinsically thread-safe or unsafe. The helpers are stateless; each + * invocation operates on its arguments only. Coordinate external access to + * shared json_object instances if they are mutated concurrently. */ #ifdef FUN_WITH_JSON @@ -24,21 +60,30 @@ //#include /** - * @brief Convert a json-c object into a Fun Value. + * @brief Convert a json-c object tree into a Fun Value. * - * Maps json-c primitive and compound types to the closest Fun Value - * representation. + * Mapping rules: * - null -> Nil * - boolean -> Bool - * - number (int/double) -> Int/Float - * - string -> String - * - array -> Array (recursively converted) - * - object -> Map (values recursively converted) + * - number (int/double) -> Int/Float (uses json_object_get_int64/json_object_get_double) + * - string -> String (assumes UTF-8, no transcoding) + * - array -> Array (elements converted recursively, preserving order) + * - object -> Map (values converted recursively; keys are copied as-is) * - * @param j Pointer to a json_object; may be NULL. - * @return A Value representing the converted JSON data. Ownership of the - * returned Value belongs to the caller and must be freed with - * free_value() when no longer needed. + * Error handling and ownership: + * - If j is NULL, returns Nil. + * - The returned Value is newly created and owned by the caller, who must + * dispose it with free_value() when done. Internally allocated temporaries + * are released before returning. + * + * Notes: + * - json-c may store numbers as double even when they look integral; such + * values will end up as Float in Fun. + * - Deep or large JSON inputs are traversed recursively; excessive depth could + * lead to stack pressure. + * + * @param j Pointer to a json_object (may be NULL). + * @return Value Converted Value (Nil on NULL input or on unsupported types). */ static Value json_to_fun(json_object *j) { if (!j) return make_nil(); @@ -84,14 +129,32 @@ static Value json_to_fun(json_object *j) { } /** - * @brief Convert a Fun Value into a json-c object. + * @brief Convert a Fun Value into a newly allocated json-c object tree. * - * Produces a newly-allocated json_object tree representing the supplied - * Value. Unsupported Fun types are stringified using a placeholder. + * Mapping rules: + * - Nil -> json null + * - Bool -> json boolean + * - Int (64-bit) -> json int64 + * - Float (double) -> json double + * - String -> json string (assumes UTF-8 already) + * - Array -> json array (elements converted recursively) + * - Map -> json object (keys are taken from the map's string keys) * - * @param v Pointer to the source Value. Must not be NULL. - * @return Newly created json_object* on success. The caller owns the - * returned object and must release it with json_object_put(). + * Unsupported or opaque Fun types are stringified using the placeholder + * "" to keep the conversion total. + * + * Ownership: + * - The caller owns the returned json_object* and must release it with + * json_object_put() when no longer needed. + * + * Notes and limitations: + * - Map keys are enumerated via map_keys_array(); only string keys are used. + * Non-string keys are ignored. + * - json-c does not support NaN/Inf as JSON numbers in a standard way; if such + * values appear in Fun Float, they are forwarded to json-c as-is. + * + * @param v Pointer to the source Value (must not be NULL). + * @return json_object* Newly created tree representing v. */ static json_object *fun_to_json(const Value *v) { switch (v->type) { diff --git a/src/extensions/openssl.c b/src/extensions/openssl.c index 3b26efe..feb5448 100644 --- a/src/extensions/openssl.c +++ b/src/extensions/openssl.c @@ -9,12 +9,53 @@ /** * @file openssl.c - * @brief OpenSSL-based hashing helpers used by crypto-related opcodes. + * @brief OpenSSL-based hashing helpers used by crypto-related VM opcodes. * - * Provides thin wrappers around OpenSSL EVP routines to compute common - * digests (MD5, RIPEMD-160, SHA-256, SHA-512). When FUN_WITH_OPENSSL is - * disabled, these helpers fall back to returning empty strings to keep the - * VM behavior consistent. + * This module centralizes small, concrete helpers around the OpenSSL EVP + * message-digest API so that VM opcodes under src/vm/crypto/*.c can remain + * minimal and focus on VM stack marshalling. Keeping the algorithm-specific + * logic in src/extensions/ mirrors other extensions (PCRE2, SQLite, XML2, + * JSON, INI) and improves maintainability. + * + * Build-time feature flag: + * - All code in this file is compiled only when FUN_WITH_OPENSSL is enabled. + * When disabled, the helpers provide safe fallbacks that allocate and return + * an empty string (""), preserving the VM's expectations around string + * ownership while signaling the absence of cryptographic support. + * + * Algorithms covered: + * - MD5 + * - SHA-256 + * - SHA-512 + * - RIPEMD-160 (may be unavailable on some OpenSSL builds; see notes below) + * + * Ownership and memory model: + * - Each helper returns a newly allocated, NUL-terminated lowercase hex string + * that the caller owns and must free() when no longer needed. + * - On allocation failure or when an algorithm/provider is unavailable, NULL + * is returned (except in disabled builds where an allocated empty string is + * returned). Callers should check for NULL before use. + * + * Zero-length input: + * - The EVP_Digest() API supports computing a digest for zero-length inputs. + * Passing len==0 is valid; data may be NULL in that case. Passing data==NULL + * with len>0 returns NULL to indicate misuse. + * + * Error handling: + * - Any failure to acquire an EVP_MD, an unexpected digest length, or memory + * allocation failure results in a NULL return (again, except in the disabled + * build where an allocated empty string is returned to keep the shape). + * + * OpenSSL 3.x provider note: + * - RIPEMD-160 is part of the legacy provider in OpenSSL 3.x and may not be + * available unless the provider is enabled at runtime/compile time. In such + * environments EVP_ripemd160() can return NULL and this module will surface + * that as a NULL return value to the caller. + * + * Thread-safety: + * - The helpers are stateless and thread-safe as long as the underlying + * OpenSSL library initialization/finalization follows OpenSSL's guidelines + * for multithreaded use. No shared global state is kept here. */ /* @@ -23,8 +64,13 @@ #ifdef FUN_WITH_OPENSSL #include -/* Always use EVP_MD_get_size; if headers don't declare it, provide a - * forward declaration to allow linking against OpenSSL libcrypto. */ +/** + * @brief Forward declaration for EVP_MD_get_size on older headers. + * + * Some older OpenSSL headers might not declare EVP_MD_get_size even though the + * symbol is available in libcrypto. Guarded declaration keeps compilation + * working across versions while always calling the same function. + */ #ifndef EVP_MD_get_size int EVP_MD_get_size(const EVP_MD *md); #endif @@ -32,14 +78,20 @@ int EVP_MD_get_size(const EVP_MD *md); #include /** - * @brief Compute MD5 digest and return it as a lowercase hex string. + * @brief Compute MD5 and return it as a lowercase hexadecimal string. + * + * Behavior and edge cases: + * - Accepts zero-length input (len==0); in this case data may be NULL. + * - Returns a string of length 32 characters (2 hex chars per 16-byte digest) + * plus the NUL terminator, on success. + * - Returns NULL if the algorithm is unavailable or on allocation failure. + * - When FUN_WITH_OPENSSL is disabled, returns an allocated empty string "". * * @param data Pointer to input bytes (may be NULL if len==0). - * @param len Number of input bytes. - * @return Newly-allocated NUL-terminated hex string on success; NULL on - * failure. When FUN_WITH_OPENSSL is disabled, returns an allocated - * empty string to keep behavior consistent. - * @note The caller owns the returned buffer and must free() it. + * @param len Number of input bytes. + * @return char* Newly-allocated NUL-terminated hex string on success; NULL on + * failure (except disabled builds return an allocated empty + * string). The caller must free() the returned buffer. */ static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { static const char hexdig[] = "0123456789abcdef"; @@ -83,13 +135,18 @@ static char *fun_openssl_md5_hex(const unsigned char *data, size_t len) { } /** - * @brief Compute SHA-256 digest and return it as a lowercase hex string. + * @brief Compute SHA-256 and return it as a lowercase hexadecimal string. + * + * Details: + * - Output length is 64 hex characters (2 per 32-byte digest) plus NUL. + * - data may be NULL if len==0; otherwise must be non-NULL. + * - Returns NULL on failure; in disabled builds returns an allocated "". * * @param data Pointer to input bytes (may be NULL if len==0). - * @param len Number of input bytes. - * @return Newly-allocated hex string on success; NULL on failure. When - * FUN_WITH_OPENSSL is disabled, returns an allocated empty string. - * @note The caller must free() the returned buffer. + * @param len Number of input bytes. + * @return char* Newly-allocated hex string on success; NULL on failure (except + * disabled builds return an allocated empty string). The caller + * must free() the buffer. */ static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) { static const char hexdig[] = "0123456789abcdef"; @@ -132,13 +189,18 @@ static char *fun_openssl_sha256_hex(const unsigned char *data, size_t len) { } /** - * @brief Compute SHA-512 digest and return it as a lowercase hex string. + * @brief Compute SHA-512 and return it as a lowercase hexadecimal string. + * + * Details: + * - Output length is 128 hex characters (2 per 64-byte digest) plus NUL. + * - data may be NULL if len==0; otherwise must be non-NULL. + * - Returns NULL on failure; in disabled builds returns an allocated "". * * @param data Pointer to input bytes (may be NULL if len==0). - * @param len Number of input bytes. - * @return Newly-allocated hex string on success; NULL on failure. When - * FUN_WITH_OPENSSL is disabled, returns an allocated empty string. - * @note The caller must free() the returned buffer. + * @param len Number of input bytes. + * @return char* Newly-allocated hex string on success; NULL on failure (except + * disabled builds return an allocated empty string). The caller + * must free() the buffer. */ static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) { static const char hexdig[] = "0123456789abcdef"; @@ -181,17 +243,21 @@ static char *fun_openssl_sha512_hex(const unsigned char *data, size_t len) { } /** - * @brief Compute RIPEMD-160 digest and return it as a lowercase hex string. + * @brief Compute RIPEMD-160 and return it as a lowercase hexadecimal string. * - * On some OpenSSL builds (e.g., 3.x without legacy provider), RIPEMD-160 may - * be unavailable and EVP_ripemd160() can return NULL. + * Availability and details: + * - On OpenSSL 3.x, RIPEMD-160 typically resides in the legacy provider and + * may be unavailable unless explicitly enabled; EVP_ripemd160() can return + * NULL in that case and this helper returns NULL. + * - Output length is 40 hex characters (2 per 20-byte digest) plus NUL. + * - data may be NULL if len==0; otherwise must be non-NULL. + * - In disabled builds, returns an allocated empty string. * * @param data Pointer to input bytes (may be NULL if len==0). - * @param len Number of input bytes. - * @return Newly-allocated hex string on success; NULL if the digest is - * unavailable or another error occurs. When FUN_WITH_OPENSSL is - * disabled, returns an allocated empty string. - * @note The caller must free() the returned buffer. + * @param len Number of input bytes. + * @return char* Newly-allocated hex string on success; NULL if the algorithm + * is unavailable or on error (except disabled builds return an + * allocated empty string). Caller must free() the buffer. */ static char *fun_openssl_ripemd160_hex(const unsigned char *data, size_t len) { static const char hexdig[] = "0123456789abcdef"; diff --git a/src/extensions/pcsc.c b/src/extensions/pcsc.c index fb43a99..0cf8a6a 100644 --- a/src/extensions/pcsc.c +++ b/src/extensions/pcsc.c @@ -9,11 +9,44 @@ /** * @file pcsc.c - * @brief PC/SC smartcard helper registries and lookup utilities. + * @brief PC/SC smartcard helper registries and lookup utilities for VM opcodes. * - * Provides small fixed-size registries for PC/SC contexts and card handles, - * plus allocation and lookup helpers used by VM opcodes when FUN_WITH_PCSC - * is enabled. + * This module centralizes tiny fixed-size registries for PC/SC resources and + * minimal helper functions used by VM opcodes under src/vm/pcsc/*.c. Keeping + * the concrete handle management here allows the opcode implementations to + * focus on VM stack marshalling, mirroring the approach taken by other + * extensions (PCRE2, SQLite, XML2, JSON, INI, cURL, OpenSSL). + * + * Build-time feature flag: + * - All code in this file is compiled only when FUN_WITH_PCSC is enabled. When + * disabled, the referencing opcodes should compile to safe no-op fallbacks + * (typically pushing 0, Nil, or empty arrays) while retaining the same stack + * behaviour. + * + * Registries and ownership model: + * - Context registry (g_pcsc_ctx): stores SCARDCONTEXT values obtained via + * SCardEstablishContext(). The registry DOES NOT call SCardReleaseContext() + * automatically — releasing is the responsibility of the opcode that owns + * the lifecycle. The registry merely tracks usage and provides a stable, + * small integer id for referencing a context in later operations. + * - Card registry (g_pcsc_card): stores SCARDHANDLE and the negotiated + * protocol (proto) obtained via SCardConnect(). Similarly, the registry does + * not call SCardDisconnect(); the VM opcode that created the handle is in + * charge of eventual teardown. + * - Handles are 1-based indices into the fixed arrays (contexts: up to 8, + * cards: up to 32). A return value of 0 indicates failure (no free slot or + * invalid request). + * + * Error handling and limits: + * - Allocation helpers return 0 when no free slot is available. Lookup helpers + * return NULL if the id is out of range or the slot is not currently in use. + * - The fixed sizes (8 contexts, 32 cards) are pragmatic defaults for typical + * scripts. Increase cautiously if your workloads require more concurrent + * resources. + * + * Thread-safety: + * - This module is not thread-safe. If the interpreter is used from multiple + * threads, coordinate access to these registries externally. */ #ifdef FUN_WITH_PCSC @@ -31,16 +64,33 @@ #include #endif #include +#include +/** + * @brief One slot in the PC/SC context registry. + * + * Ownership notes: + * - The registry does not own the context; it merely stores the value returned + * by SCardEstablishContext(). Callers/opcodes are responsible for invoking + * SCardReleaseContext() at the appropriate time. + */ typedef struct { - SCARDCONTEXT ctx; - int in_use; + SCARDCONTEXT ctx; /**< Established context value. */ + int in_use; /**< 1 if the slot is currently allocated; 0 otherwise. */ } pcsc_ctx_entry; +/** + * @brief One slot in the PC/SC card handle registry. + * + * Ownership notes: + * - The registry does not disconnect cards; it only stores the handle returned + * by SCardConnect() and the negotiated protocol. The VM opcode that created + * the handle is responsible for calling SCardDisconnect(). + */ typedef struct { - SCARDHANDLE h; - DWORD proto; - int in_use; + SCARDHANDLE h; /**< Connected card handle from SCardConnect(). */ + DWORD proto; /**< Negotiated protocol flags (e.g., SCARD_PROTOCOL_T0/T1). */ + int in_use; /**< 1 if the slot is currently allocated; 0 otherwise. */ } pcsc_card_entry; static pcsc_ctx_entry g_pcsc_ctx[8]; @@ -49,7 +99,10 @@ static pcsc_card_entry g_pcsc_card[32]; /** * @brief Allocate a free context slot in the PC/SC registry. * - * @return A 1-based slot id on success, or 0 if no free slot is available. + * Scans the small fixed-size context registry for an available slot, marks it + * as in use, clears the stored value, and returns a 1-based identifier. + * + * @return int A 1-based slot id on success; 0 if no free slot is available. */ static int pcsc_alloc_ctx_slot(void) { for (int i = 0; i < (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0])); ++i) { @@ -65,7 +118,11 @@ static int pcsc_alloc_ctx_slot(void) { /** * @brief Allocate a free card slot in the PC/SC registry. * - * @return A 1-based slot id on success, or 0 if no free slot is available. + * Scans the card registry for an unused entry, sets initial values, and + * returns a small positive identifier that can be used by opcodes to index the + * slot later. + * + * @return int A 1-based slot id on success; 0 if no free slot is available. */ static int pcsc_alloc_card_slot(void) { for (int i = 0; i < (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0])); ++i) { @@ -82,28 +139,36 @@ static int pcsc_alloc_card_slot(void) { /** * @brief Lookup a context slot by id. * - * @param id 1-based context id previously returned by pcsc_alloc_ctx_slot(). - * @return Pointer to the registry entry if valid and in use; NULL otherwise. + * Validates the provided 1-based identifier, ensures the slot is currently in + * use, and returns a pointer to the internal registry entry. + * + * @param id int 1-based context id previously returned by pcsc_alloc_ctx_slot(). + * @return pcsc_ctx_entry* Pointer to the entry if valid and in use; NULL otherwise. */ static pcsc_ctx_entry *pcsc_get_ctx(int id) { - if (id <= 0) return NULL; + if (id <= 0) return 0; int idx = id - 1; - if (idx < 0 || idx >= (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0]))) return NULL; - if (!g_pcsc_ctx[idx].in_use) return NULL; + if (idx < 0 || idx >= (int)(sizeof(g_pcsc_ctx) / sizeof(g_pcsc_ctx[0]))) return 0; + if (!g_pcsc_ctx[idx].in_use) return 0; return &g_pcsc_ctx[idx]; } /** * @brief Lookup a card slot by id. * - * @param id 1-based card id previously returned by pcsc_alloc_card_slot(). - * @return Pointer to the registry entry if valid and in use; NULL otherwise. + * Validates the provided 1-based identifier, ensures the slot is currently in + * use, and returns a pointer to the internal registry entry, which exposes the + * SCARDHANDLE and negotiated protocol for subsequent operations (e.g., + * SCardTransmit()). + * + * @param id int 1-based card id previously returned by pcsc_alloc_card_slot(). + * @return pcsc_card_entry* Pointer to the entry if valid and in use; NULL otherwise. */ static pcsc_card_entry *pcsc_get_card(int id) { - if (id <= 0) return NULL; + if (id <= 0) return 0; int idx = id - 1; - if (idx < 0 || idx >= (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0]))) return NULL; - if (!g_pcsc_card[idx].in_use) return NULL; + if (idx < 0 || idx >= (int)(sizeof(g_pcsc_card) / sizeof(g_pcsc_card[0]))) return 0; + if (!g_pcsc_card[idx].in_use) return 0; return &g_pcsc_card[idx]; } #endif diff --git a/src/extensions/sqlite.c b/src/extensions/sqlite.c index 87d69cb..667c86f 100644 --- a/src/extensions/sqlite.c +++ b/src/extensions/sqlite.c @@ -11,42 +11,110 @@ * @file sqlite.c * @brief SQLite handle registry and helper utilities for the Fun VM extension. * - * This module provides a very small registry for SQLite database handles when - * the project is compiled with FUN_WITH_SQLITE. It assigns small integer - * identifiers to opened sqlite3 connections and allows retrieval or removal of - * those entries. The registry itself does not open or close SQLite databases — - * it only stores pointers provided by the caller. + * This translation unit implements a tiny in-process registry for SQLite + * connection handles that can be used by the VM opcodes living under + * src/vm/sqlite/*.c. The registry abstracts over raw sqlite3* pointers and + * assigns small positive integer identifiers to each connection. VM opcodes can + * then pass these identifiers around instead of raw pointers. * - * Thread-safety: This registry is not thread-safe. Callers must ensure - * external synchronization if used from multiple threads. + * Build-time feature flag + * ----------------------- + * The code is compiled only when the CMake option FUN_WITH_SQLITE is enabled + * (i.e., the preprocessor symbol FUN_WITH_SQLITE is defined). When disabled, + * this file contributes no symbols and the corresponding opcodes should be + * compiled out or provide appropriate fallbacks. + * + * Ownership and lifetime + * ---------------------- + * - The registry does NOT open or close databases. It merely stores pointers + * that were created elsewhere (e.g., via sqlite3_open()). + * - Adding an entry does not transfer ownership of the sqlite3 connection. + * Callers remain responsible for invoking sqlite3_close() at the appropriate + * time. + * - Removing an entry from the registry does NOT close the connection; it only + * forgets the mapping between id and pointer. + * - Integer identifiers are monotonically increasing per process. Once a handle + * id is deleted, it will not be reused within the same process lifetime. + * + * Error handling + * -------------- + * Functions in this module perform only basic validation and memory allocation. + * Allocation failures return NULL (for lookups/additions) or are silently + * ignored (for deletions of non-existent ids). No SQLite API calls are made + * here, so no sqlite error codes are produced by this module itself. + * + * Thread-safety + * ------------- + * The registry is implemented as a simple singly-linked list with no + * synchronization. It is NOT thread-safe. If the VM uses SQLite from multiple + * threads, the caller must provide external synchronization around all calls to + * these helpers. + * + * Example + * ------- + * @code{.c} + * // Open a database elsewhere: + * sqlite3 *db = NULL; + * if (sqlite3_open(":memory:", &db) == SQLITE_OK) { + * // Register and get an id: + * SqlHandle *h = sql_reg_add(db); + * int id = h ? h->id : -1; + * + * // Later look it up: + * SqlHandle *same = sql_reg_get(id); + * if (same) { + * // use same->db with SQLite APIs + * } + * + * // When finished, drop the registry entry and close manually: + * sql_reg_del(id); + * sqlite3_close(db); + * } + * @endcode */ #ifdef FUN_WITH_SQLITE #include /** * @brief Node in a singly-linked list of registered SQLite handles. + * + * Each node associates a monotonically increasing positive integer identifier + * with a raw sqlite3* pointer. The list head is stored in a file-static global + * (g_sql_handles). */ typedef struct SqlHandle { - int id; - sqlite3 *db; - struct SqlHandle *next; + int id; /**< Positive identifier assigned by the registry. */ + sqlite3 *db; /**< Opaque pointer to an opened sqlite3 connection. */ + struct SqlHandle *next; /**< Next entry in the singly-linked list. */ } SqlHandle; -/** Global head of the SQLite handle list. */ +/** + * @brief Global head of the SQLite handle list. + * + * NULL denotes an empty list. The list is modified only by the helpers in this + * file and is never exposed directly to callers. + */ static SqlHandle *g_sql_handles = NULL; -/** Next positive identifier to assign to a newly added handle. */ +/** + * @brief Next positive identifier to assign to a newly added handle. + * + * Starts at 1 and increases monotonically. Identifiers are not reused across + * deletions within a process lifetime. + */ static int g_sql_next_id = 1; /** * @brief Add a sqlite3 handle to the registry. * - * The function allocates a new list node, assigns a fresh positive id, and - * prepends it to the internal registry list. + * Allocates a new list node, assigns a fresh positive id, and prepends it to + * the internal registry list. Ownership of the sqlite3 connection remains with + * the caller; this registry does not close the handle during deletion. * * @param db Valid pointer to an opened sqlite3 connection. * @return Pointer to the newly created SqlHandle entry on success; NULL on * allocation failure. The returned pointer remains owned by the * registry; do not free it directly. + * * @note This function does not take ownership of the sqlite3 connection in the * sense of closing it; removal from the registry will not call * sqlite3_close(). @@ -64,8 +132,13 @@ static SqlHandle *sql_reg_add(sqlite3 *db) { /** * @brief Look up a registered SQLite handle by id. * + * Performs a linear search over the internal list to find a matching id. + * * @param id Positive identifier previously returned by sql_reg_add(). * @return Pointer to the SqlHandle entry if found; NULL otherwise. + * + * @note The returned pointer is owned by the registry and must not be freed by + * the caller. */ static SqlHandle *sql_reg_get(int id) { for (SqlHandle *p = g_sql_handles; p; p = p->next) @@ -79,8 +152,10 @@ static SqlHandle *sql_reg_get(int id) { * Deletes the list node associated with the given id. * * @param id Positive identifier of the entry to remove. + * * @note This function does not close the underlying sqlite3 connection; the * caller is responsible for calling sqlite3_close() if appropriate. + * @note If the id does not exist, the function is a no-op. */ static void sql_reg_del(int id) { SqlHandle **pp = &g_sql_handles; diff --git a/src/extensions/xml2.c b/src/extensions/xml2.c index 964e03e..5197b70 100644 --- a/src/extensions/xml2.c +++ b/src/extensions/xml2.c @@ -9,19 +9,72 @@ /** * @file xml2.c - * @brief Lightweight libxml2 handle registry for Fun VM extension helpers. + * @brief libxml2 handle registries and helper utilities for the Fun VM extension. * - * This module provides small fixed-size registries for libxml2 objects when - * compiled with FUN_WITH_XML2. Documents and nodes can be associated with - * small integer handles to make them easier to pass around inside the - * interpreter and its C API boundaries. The document registry owns the - * underlying xmlDoc, while the node registry does not own the xmlNode — nodes - * are freed when their owning document is freed. + * Overview + * -------- + * This translation unit implements compact, fixed-size registries that assign + * small positive integer handles to libxml2 objects. VM opcodes under + * src/vm/xml2/*.c and higher-level helpers can exchange these integer handles + * across the VM stack without exposing raw pointers. * - * Limits: The registries are fixed-size (docs: 64, nodes: 256). Handle value 0 - * is reserved and indicates failure/invalid. + * Build-time feature flag + * ----------------------- + * Code in this file is compiled only when the CMake option FUN_WITH_XML2 is + * enabled (i.e., the preprocessor symbol FUN_WITH_XML2 is defined). When + * disabled, this file contributes no symbols and the corresponding VM opcodes + * should be compiled out or provide a graceful fallback. * - * Thread-safety: Not thread-safe. Coordinate access externally if needed. + * Ownership and lifetime + * ---------------------- + * - Document registry (XmlDocSlot) TAKES OWNERSHIP of the registered xmlDocPtr. + * Releasing the document handle will call xmlFreeDoc() for the stored + * pointer. + * - Node registry (XmlNodeSlot) DOES NOT take ownership of xmlNodePtr values. + * Nodes are owned by their document. Releasing a node handle only clears the + * registry slot; it does not free the underlying xmlNode memory. + * - When a document is freed, the libxml2 tree below it is destroyed by + * libxml2, thereby invalidating any node pointers previously returned from + * that document. The small node handle registry in this file does not track + * which document owns which node. Callers must ensure they do not use node + * handles after their owning document has been released; they should clear + * those node handles explicitly if necessary. + * + * Limits and handle ranges + * ------------------------ + * - Document handles are allocated from a fixed-size array of 64 slots. Valid + * handles are in the inclusive range [1, 63]. + * - Node handles are allocated from a fixed-size array of 256 slots. Valid + * handles are in the inclusive range [1, 255]. + * - The value 0 is reserved and indicates failure or an invalid handle. + * + * Error handling + * -------------- + * - Allocation requests fail with a return value of 0 when no free slot is + * available. + * - Lookups return NULL for invalid or unused handles. + * - Free functions return 1 when a valid in-use handle was released, and 0 + * otherwise. Document release also frees the underlying xmlDoc via + * xmlFreeDoc(). Node release does not free the xmlNode memory. + * + * Thread-safety + * ------------- + * - The registries are simple, unsynchronized arrays. They are NOT + * thread-safe. If the VM uses libxml2 handles across multiple threads, + * external synchronization is required around all calls into this module. + * + * Example + * ------- + * @code{.c} + * // Assume xmlInitParser() was called by the embedding application. + * xmlDocPtr d = xmlReadMemory("", 20, "-", NULL, 0); + * int dh = xml_doc_alloc(d); // takes ownership of d + * xmlDocPtr same = xml_doc_get(dh); + * // ... use 'same' with libxml2 APIs ... + * + * // When done, free the document handle; this calls xmlFreeDoc(same). + * (void)xml_doc_free_handle(dh); + * @endcode */ #ifdef FUN_WITH_XML2 #include @@ -30,12 +83,12 @@ /** * @brief Slot describing a registered XML document. * - * The registry owns the xmlDocPtr and will free it when the handle is - * released via xml_doc_free_handle(). + * The registry owns the xmlDocPtr and will free it when the handle is released + * via xml_doc_free_handle(). */ typedef struct { - xmlDocPtr doc; - int in_use; + xmlDocPtr doc; /**< Pointer to a parsed libxml2 document (owned by registry). */ + int in_use; /**< Non-zero if the slot is occupied. */ } XmlDocSlot; /** * @brief Slot describing a registered XML node. @@ -44,20 +97,38 @@ typedef struct { * a node handle does not free the node memory. */ typedef struct { - xmlNodePtr node; - int in_use; + xmlNodePtr node; /**< Pointer to a node in some document (not owned). */ + int in_use; /**< Non-zero if the slot is occupied. */ } XmlNodeSlot; +/** + * @brief Fixed-size registry storage for documents. + * + * Index 0 is reserved. Valid document handle indices are in [1, 63]. + */ static XmlDocSlot g_xml_docs[64]; +/** + * @brief Fixed-size registry storage for nodes. + * + * Index 0 is reserved. Valid node handle indices are in [1, 255]. + */ static XmlNodeSlot g_xml_nodes[256]; /** * @brief Allocate a document handle for the given xmlDoc pointer. * - * @param d Valid xmlDocPtr to register. Ownership is transferred to the - * registry, which will xmlFreeDoc() it when the handle is freed. + * Allocates a free slot in the document registry and stores the supplied + * xmlDocPtr. Ownership is transferred to the registry; releasing the handle + * will call xmlFreeDoc() for the stored pointer. + * + * @param d Valid xmlDocPtr to register. The caller must not free the document + * directly after a successful registration. * @return Positive handle in the range [1, 63] on success; 0 if no slot is * available. + * + * @note Passing NULL is undefined behavior for this helper in the sense that + * it will simply not find a slot and return 0 if no slot is free; callers + * should validate inputs before calling. */ static int xml_doc_alloc(xmlDocPtr d) { for (int i = 1; i < (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])); ++i) { @@ -72,6 +143,9 @@ static int xml_doc_alloc(xmlDocPtr d) { /** * @brief Retrieve a registered xmlDoc by handle. * + * Performs bounds and state checks on the registry and returns the stored + * xmlDocPtr for the handle if present. + * * @param h Handle previously returned by xml_doc_alloc(). * @return xmlDocPtr if the handle is valid and in use; NULL otherwise. */ @@ -82,8 +156,17 @@ static xmlDocPtr xml_doc_get(int h) { /** * @brief Free a document handle and the underlying xmlDoc. * + * If the handle is valid and in use, calls xmlFreeDoc() for the stored + * document pointer, clears the slot, and returns 1. + * * @param h Handle to release. * @return 1 if the handle was valid and has been released; 0 otherwise. + * + * @note Freeing a document invalidates any xmlNodePtr previously obtained from + * that document. The node registry in this file does not automatically + * clear entries that reference nodes from the freed document; callers are + * responsible for avoiding use-after-free by releasing or ignoring such + * node handles. */ static int xml_doc_free_handle(int h) { if (h <= 0 || h >= (int)(sizeof(g_xml_docs) / sizeof(g_xml_docs[0])) || !g_xml_docs[h].in_use) return 0; @@ -96,8 +179,11 @@ static int xml_doc_free_handle(int h) { /** * @brief Allocate a node handle for the given xmlNode pointer. * - * @param n Valid xmlNodePtr to register. Ownership is NOT transferred; - * nodes are managed by their owning document. + * Allocates a free slot in the node registry and stores the supplied pointer. + * Ownership is not transferred; the memory is managed by the owning document. + * + * @param n Valid xmlNodePtr to register. Must remain valid for as long as the + * handle is in use and the owning document is alive. * @return Positive handle in the range [1, 255] on success; 0 if no slot is * available. */ @@ -114,6 +200,9 @@ static int xml_node_alloc(xmlNodePtr n) { /** * @brief Retrieve a registered xmlNode by handle. * + * Performs bounds and state checks on the registry and returns the stored + * xmlNodePtr for the handle if present. + * * @param h Handle previously returned by xml_node_alloc(). * @return xmlNodePtr if the handle is valid and in use; NULL otherwise. */ @@ -124,11 +213,15 @@ static xmlNodePtr xml_node_get(int h) { /** * @brief Free a node handle without freeing the underlying node. * - * Nodes are owned by their document; releasing the document invalidates any - * associated node handles. + * Clears the registry slot associated with the given handle. The underlying + * xmlNode memory is not freed because nodes are owned by their document. * * @param h Handle to release. * @return 1 if the handle was valid and has been released; 0 otherwise. + * + * @note Releasing or freeing a document invalidates any nodes originating from + * that document. Callers should avoid using node handles after their + * document has been released. */ static int xml_node_free_handle(int h) { if (h <= 0 || h >= (int)(sizeof(g_xml_nodes) / sizeof(g_xml_nodes[0])) || !g_xml_nodes[h].in_use) return 0; diff --git a/web/css/main.scss b/web/css/main.scss index c190332..3b44318 100644 --- a/web/css/main.scss +++ b/web/css/main.scss @@ -2,6 +2,7 @@ # Only the main Sass file needs front matter (the dashes are enough) --- @charset "utf-8"; +@use "sass:color"; $base-font-family: "CPS"; $base-font-size: 16px; $small-font-size: $base-font-size * 0.875; @@ -16,7 +17,7 @@ $headline-color: #bc6c21; //#b40000; $hover-color: #bc6c21; $topbutton-color: $headline-color; $brand-color: #6d0076; -$brand-color-light: lighten($brand-color, 40%); +$brand-color-light: #ed14ff; $grey-color: #828282; $grey-color-light: #e8e8e8; $grey-color-dark: #dbdbdb;