1
0
Fork 0
forked from fun/fun

Fixed bug in the PCSC extension and added more Doxygen documentation to all extensions. (0.41.8)

This commit is contained in:
Johannes Findeisen 2026-05-07 22:23:01 +02:00
commit 1016ebab51
9 changed files with 598 additions and 134 deletions

View file

@ -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)

View file

@ -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;

View file

@ -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;

View file

@ -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<string, any> (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 <string.h>
/**
* @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<string,any> (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<string, any> (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
* "<unsupported>" 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) {

View file

@ -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 <openssl/evp.h>
/* 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 <stdlib.h>
/**
* @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";

View file

@ -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 <PCSC/wintypes.h>
#endif
#include <string.h>
#include <stdio.h>
/**
* @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

View file

@ -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 <sqlite3.h>
/**
* @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;

View file

@ -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("<root><x/></root>", 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 <libxml/parser.h>
@ -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;

View file

@ -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;