Fixed bug in the PCSC extension and added more Doxygen documentation to all extensions. (0.41.8)
This commit is contained in:
parent
be590414af
commit
1016ebab51
9 changed files with 598 additions and 134 deletions
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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";
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue