diff --git a/exe/iodine b/exe/iodine index da22b8c..795ce01 100755 --- a/exe/iodine +++ b/exe/iodine @@ -103,7 +103,7 @@ module Iodine return nil end build = Rack::Builder.parse_file(fn) - build[0] + build end def self.iodine___load_neorack fn return nil unless fn diff --git a/ext/iodine/fio-stl.h b/ext/iodine/fio-stl.h index 14c2bcc..cbc7aab 100644 --- a/ext/iodine/fio-stl.h +++ b/ext/iodine/fio-stl.h @@ -1,5 +1,5 @@ /* ***************************************************************************** -Copyright: Boaz Segev, 2019-2023 +Copyright: Boaz Segev, 2019-2024 License: ISC / MIT (choose your license) Feel free to copy, use and enjoy according to the license provided. @@ -68,7 +68,7 @@ supports macros that will help detect and validate it's version. /** PATCH version: Bug fixes, minor features may be added. */ #define FIO_VERSION_PATCH 0 /** Build version: optional build info (string), i.e. "beta.02" */ -#define FIO_VERSION_BUILD "alpha.08" +#define FIO_VERSION_BUILD "alpha.10" #ifdef FIO_VERSION_BUILD /** Version as a String literal (MACRO). */ @@ -246,7 +246,7 @@ Address Sanitizer Detection /* ***************************************************************************** Intrinsic Availability Flags ***************************************************************************** */ -#if 1 /* Allow Intrinsic / SIMD / Neon ? */ +#if !defined(DEBUG) /* Allow Intrinsic / SIMD / Neon ? */ #if defined(__ARM_FEATURE_CRYPTO) && \ (defined(__ARM_NEON) || defined(__ARM_NEON__)) && \ __has_include("arm_acle.h") && __has_include("arm_neon.h") @@ -397,6 +397,32 @@ Function Attributes #define FIO_WEAK __attribute__((weak)) #endif +#ifndef FIO_IFUNC_DEF_GETSET +/** Defines a "get" function for a field within a struct / union. */ +#define FIO_IFUNC_DEF_GET(namespace, T_type, F_type, field_name) \ + /** Returns current value of property within the struct / union. */ \ + FIO_IFUNC F_type FIO_NAME(namespace, field_name)(T_type * o) { \ + FIO_ASSERT_DEBUG(o, "NULL " FIO_MACRO2STR(namespace) " pointer @ `get`!"); \ + return o->field_name; \ + } + +/** Defines a "set" function for a field within a struct / union. */ +#define FIO_IFUNC_DEF_SET(namespace, T_type, F_type, field_name) \ + /** Sets a new value, returning the old one */ \ + FIO_IFUNC F_type FIO_NAME(FIO_NAME(namespace, field_name), \ + set)(T_type * o, F_type new_value) { \ + FIO_ASSERT_DEBUG(o, "NULL " FIO_MACRO2STR(namespace) " pointer @ `set`!"); \ + F_type old_value = o->field_name; \ + o->field_name = new_value; \ + return old_value; \ + } + +/** Defines get/set functions for a field within a struct / union. */ +#define FIO_IFUNC_DEF_GETSET(namespace, T_type, F_type, field_name) \ + FIO_IFUNC_DEF_GET(namespace, T_type, F_type, field_name) \ + FIO_IFUNC_DEF_SET(namespace, T_type, F_type, field_name) + +#endif /* FIO_IFUNC_DEF_GETSET */ /* ***************************************************************************** Constructors and Destructors ***************************************************************************** */ @@ -548,7 +574,7 @@ Sleep / Thread Scheduling Macros #define FIO_THREAD_YIELD() __asm__ __volatile__("yield" ::: "memory") #elif defined(_MSC_VER) #define FIO_THREAD_YIELD() YieldProcessor() -#else FIO_OS_POSIX +#else /* FIO_OS_POSIX */ /** Yields the thread, hinting to the processor about spinlock loop. */ #define FIO_THREAD_YIELD() sched_yield() #endif @@ -2521,6 +2547,46 @@ FIO_IFUNC void fio_bit_flip(void *map, size_t bit) { ((uint8_t *)map)[bit >> 3] ^= (uint8_t)((1UL << (bit & 7))); } +/* ***************************************************************************** +Fun Primes +***************************************************************************** */ + +/* Primes with with 16 bits, half of them set. */ +#define FIO_U16_HASH_PRIME0 0xDA23U +#define FIO_U16_HASH_PRIME1 0xB48BU +#define FIO_U16_HASH_PRIME2 0xC917U +#define FIO_U16_HASH_PRIME3 0xD855U +#define FIO_U16_HASH_PRIME4 0xE0B9U +#define FIO_U16_HASH_PRIME5 0xE471U +#define FIO_U16_HASH_PRIME6 0x85CDU +#define FIO_U16_HASH_PRIME7 0xD433U +#define FIO_U16_HASH_PRIME8 0xE951U +#define FIO_U16_HASH_PRIME9 0xA8E5U + +/* Primes with with 32 bits, half of them set. */ +#define FIO_U32_HASH_PRIME0 0xC19F5985UL +#define FIO_U32_HASH_PRIME1 0x8D567931UL +#define FIO_U32_HASH_PRIME2 0x9C178B17UL +#define FIO_U32_HASH_PRIME3 0xA4B842DFUL +#define FIO_U32_HASH_PRIME4 0xB0B94EC9UL +#define FIO_U32_HASH_PRIME5 0xFA9E7084UL +#define FIO_U32_HASH_PRIME6 0xCA63037BUL +#define FIO_U32_HASH_PRIME7 0xD728C15DUL +#define FIO_U32_HASH_PRIME8 0xA872A277UL +#define FIO_U32_HASH_PRIME9 0xF5781551UL + +/* Primes with with 64 bits, half of them set. */ +#define FIO_U64_HASH_PRIME0 0x39664DEECA23D825 +#define FIO_U64_HASH_PRIME1 0x48644F7B3959621F +#define FIO_U64_HASH_PRIME2 0x613A19F5CB0D98D5 +#define FIO_U64_HASH_PRIME3 0x84B56B93C869EA0F +#define FIO_U64_HASH_PRIME4 0x8EE38D13E0D95A8D +#define FIO_U64_HASH_PRIME5 0x92E99EC981F0E279 +#define FIO_U64_HASH_PRIME6 0xDDC3100BEF158BB1 +#define FIO_U64_HASH_PRIME7 0x918F4D38049F78BD +#define FIO_U64_HASH_PRIME8 0xB6C9F8032A35E2D9 +#define FIO_U64_HASH_PRIME9 0xFA2A5F16D2A128D5 + /* ***************************************************************************** 64bit addition (ADD) / subtraction (SUB) / multiplication (MUL) with carry. ***************************************************************************** */ @@ -2719,10 +2785,10 @@ typedef union { uint16x8_t x16[1]; uint8x16_t x8[1]; #elif __has_attribute(vector_size) - uint64_t x64 __attribute__((vector_size(16))); - uint64_t x32 __attribute__((vector_size(16))); - uint64_t x16 __attribute__((vector_size(16))); - uint64_t x8 __attribute__((vector_size(16))); + uint64_t __attribute__((vector_size(16))) x64[1]; + uint32_t __attribute__((vector_size(16))) x32[1]; + uint16_t __attribute__((vector_size(16))) x16[1]; + uint8_t __attribute__((vector_size(16))) x8[1]; #endif #if defined(__SIZEOF_INT128__) __uint128_t alignment_for_u128_[1]; @@ -2743,10 +2809,10 @@ typedef union { uint16x8_t x16[2]; uint8x16_t x8[2]; #elif __has_attribute(vector_size) - uint64_t x64 __attribute__((vector_size(32))); - uint64_t x32 __attribute__((vector_size(32))); - uint64_t x16 __attribute__((vector_size(32))); - uint64_t x8 __attribute__((vector_size(32))); + uint64_t __attribute__((vector_size(32))) x64[1]; + uint32_t __attribute__((vector_size(32))) x32[1]; + uint16_t __attribute__((vector_size(32))) x16[1]; + uint8_t __attribute__((vector_size(32))) x8[1]; #endif #if defined(__SIZEOF_INT128__) __uint128_t alignment_for_u128_[2]; @@ -2771,10 +2837,10 @@ typedef union { uint16x8_t x16[4]; uint8x16_t x8[4]; #elif __has_attribute(vector_size) - uint64_t x64 __attribute__((vector_size(64))); - uint64_t x32 __attribute__((vector_size(64))); - uint64_t x16 __attribute__((vector_size(64))); - uint64_t x8 __attribute__((vector_size(64))); + uint64_t __attribute__((vector_size(64))) x64[1]; + uint32_t __attribute__((vector_size(64))) x32[1]; + uint16_t __attribute__((vector_size(64))) x16[1]; + uint8_t __attribute__((vector_size(64))) x8[1]; #endif } fio_u512 FIO_ALIGN(16); @@ -2794,10 +2860,10 @@ typedef union { uint16x8_t x16[8]; uint8x16_t x8[8]; #elif __has_attribute(vector_size) - uint64_t x64 __attribute__((vector_size(128))); - uint64_t x32 __attribute__((vector_size(128))); - uint64_t x16 __attribute__((vector_size(128))); - uint64_t x8 __attribute__((vector_size(128))); + uint64_t __attribute__((vector_size(128))) x64[1]; + uint32_t __attribute__((vector_size(128))) x32[1]; + uint16_t __attribute__((vector_size(128))) x16[1]; + uint8_t __attribute__((vector_size(128))) x8[1]; #endif } fio_u1024 FIO_ALIGN(16); @@ -2818,10 +2884,10 @@ typedef union { uint16x8_t x16[16]; uint8x16_t x8[16]; #elif __has_attribute(vector_size) - uint64_t x64 __attribute__((vector_size(256))); - uint64_t x32 __attribute__((vector_size(256))); - uint64_t x16 __attribute__((vector_size(256))); - uint64_t x8 __attribute__((vector_size(256))); + uint64_t __attribute__((vector_size(256))) x64[1]; + uint32_t __attribute__((vector_size(256))) x32[1]; + uint16_t __attribute__((vector_size(256))) x16[1]; + uint8_t __attribute__((vector_size(256))) x8[1]; #endif } fio_u2048 FIO_ALIGN(16); @@ -2843,10 +2909,10 @@ typedef union { uint16x8_t x16[32]; uint8x16_t x8[32]; #elif __has_attribute(vector_size) - uint64_t x64 __attribute__((vector_size(512))); - uint64_t x32 __attribute__((vector_size(512))); - uint64_t x16 __attribute__((vector_size(512))); - uint64_t x8 __attribute__((vector_size(512))); + uint64_t __attribute__((vector_size(512))) x64[1]; + uint32_t __attribute__((vector_size(512))) x32[1]; + uint16_t __attribute__((vector_size(512))) x16[1]; + uint8_t __attribute__((vector_size(512))) x8[1]; #endif } fio_u4096 FIO_ALIGN(16); @@ -2939,7 +3005,8 @@ FIO_MATH_TYPE_LOADER(4096, 512) Vector Helpers - Vector Math Operations ***************************************************************************** */ -#if FIO_HAS_UX || !defined(DEBUG) +#if FIO_HAS_UX && !defined(DEBUG) + /** Performs `a op b` (+,-, *, etc') as a vector of `bit` long words. */ #define FIO_MATH_UXXX_OP(t, a, b, bits, op) \ do { \ @@ -2980,7 +3047,7 @@ Vector Helpers - Vector Math Operations do { \ for (size_t i__ = 0; i__ < (sizeof((t).u##bits) / sizeof((t).u##bits[0])); \ ++i__) \ - (t).u##bits[i__] = op(a).u##bits[i__]; \ + (t).u##bits[i__] = op((a).u##bits[i__]); \ } while (0) #endif /* FIO_HAS_UX */ @@ -2994,13 +3061,14 @@ Vector Helpers - Vector Math Operations } while (0) #define FIO___UXXX_DEF_OP(total_bits, bits, opnm, op) \ - FIO_IFUNC void fio_u##total_bits##_##opnm##bits(fio_u##total_bits *target, \ - fio_u##total_bits *a, \ - fio_u##total_bits *b) { \ + FIO_IFUNC void fio_u##total_bits##_##opnm##bits( \ + fio_u##total_bits *target, \ + const fio_u##total_bits *a, \ + const fio_u##total_bits *b) { \ FIO_MATH_UXXX_OP(((target)[0]), ((a)[0]), ((b)[0]), bits, op); \ } \ FIO_IFUNC void fio_u##total_bits##_c##opnm##bits(fio_u##total_bits *target, \ - fio_u##total_bits *a, \ + const fio_u##total_bits *a, \ uint##bits##_t b) { \ FIO_MATH_UXXX_COP(((target)[0]), ((a)[0]), (b), bits, op); \ } \ @@ -3012,8 +3080,8 @@ Vector Helpers - Vector Math Operations } #define FIO___UXXX_DEF_OP2(total_bits, bits, opnm, op) \ FIO_IFUNC void fio_u##total_bits##_##opnm(fio_u##total_bits *target, \ - fio_u##total_bits *a, \ - fio_u##total_bits *b) { \ + const fio_u##total_bits *a, \ + const fio_u##total_bits *b) { \ FIO_MATH_UXXX_OP(((target)[0]), ((a)[0]), ((b)[0]), bits, op); \ } @@ -3034,7 +3102,7 @@ Vector Helpers - Vector Math Operations FIO___UXXX_DEF_OP4T_INNER(total_bits, xor, ^) \ FIO___UXXX_DEF_OP2(total_bits, 64, xor, ^) \ FIO_IFUNC void fio_u##total_bits##_inv(fio_u##total_bits *target, \ - fio_u##total_bits *a) { \ + const fio_u##total_bits *a) { \ FIO_MATH_UXXX_SOP(((target)[0]), ((a)[0]), 64, ~); \ } @@ -3081,7 +3149,7 @@ Vector Helpers - Multi-Precision Math return borrow; \ } \ /** Returns -1, 0, or 1 if a < b, a == b or a > a (respectively). */ \ - FIO_MIFN int fio_u##bits##_cmp(fio_u##bits *a, fio_u##bits *b) { \ + FIO_MIFN int fio_u##bits##_cmp(const fio_u##bits *a, const fio_u##bits *b) { \ unsigned is_eq = 1; \ unsigned is_bigger = 0; \ for (size_t i = (bits / 64); i--;) { \ @@ -3432,7 +3500,7 @@ Everything Inclusion #undef FIO_MALLOC #undef FIO_MUSTACHE #undef FIO_PUBSUB -#undef FIO_SERVER +#undef FIO_IO #undef FIOBJ_MALLOC #define FIO_CLI #define FIO_CORE @@ -3448,18 +3516,19 @@ Everything Inclusion #undef FIO_MEMALT #define FIO_FIOBJ #define FIO_HTTP +#define FIO_IO #define FIO_MALLOC #define FIO_MUSTACHE #define FIO_PUBSUB -#define FIO_SERVER #define FIO_MEMALT #endif #define FIO___INCLUDE_AGAIN #endif /* FIO_EVERYTHING */ + /* ***************************************************************************** -Basics Inclusion +FIO_BASIC Basic Kitchen Sink Inclusion ***************************************************************************** */ #if !defined(FIO___RECURSIVE_INCLUDE) && defined(FIO_BASIC) && \ !defined(H___FIO_BASIC___H) @@ -3496,7 +3565,7 @@ Basics Inclusion #define FIO___INCLUDE_AGAIN #endif /* FIO_BASIC */ /* ***************************************************************************** -Poor-man's Cryptographic Elements +FIO_CRYPT Poor-man's Cryptographic Elements ***************************************************************************** */ #if defined(FIO_CRYPT) #undef FIO_CHACHA @@ -3511,7 +3580,7 @@ Poor-man's Cryptographic Elements #endif /* FIO_CRYPT */ /* ***************************************************************************** -Core Inclusion +FIO_CORE Core Inclusion ***************************************************************************** */ #if defined(FIO_CORE) #undef FIO_ATOL @@ -3574,6 +3643,10 @@ Memory Allocation - FIO_MALLOC as a "global" default memory allocator #define FIO_MEMORY_ENABLE_BIG_ALLOC 1 #endif +#ifndef FIO_MEMORY_INITIALIZE_ALLOCATIONS +/* should memory be initialized to zero? */ +#define FIO_MEMORY_INITIALIZE_ALLOCATIONS 1 +#endif /* ***************************************************************************** Memory Allocation - FIO_MALLOC defines a FIOBJ dedicated memory allocator ***************************************************************************** */ @@ -3641,11 +3714,11 @@ FIO_MAP Ordering & Naming Shortcut #endif #if defined(FIO_HTTP) || defined(FIO_PUBSUB) -#undef FIO_SERVER -#define FIO_SERVER +#undef FIO_IO +#define FIO_IO #endif -#if defined(FIO_HTTP) || defined(FIO_SERVER) +#if defined(FIO_HTTP) || defined(FIO_IO) #undef FIO_POLL #define FIO_POLL #endif @@ -3675,7 +3748,7 @@ FIO_MAP Ordering & Naming Shortcut #define FIO_WEBSOCKET_PARSER #endif -#if defined(FIO_POLL) || defined(FIO_SERVER) || defined(FIO_PUBSUB) +#if defined(FIO_POLL) || defined(FIO_IO) || defined(FIO_PUBSUB) #undef FIO_SOCK #define FIO_SOCK #endif @@ -3695,21 +3768,21 @@ FIO_MAP Ordering & Naming Shortcut #define FIO_STR #endif -#if defined(FIO_SERVER) +#if defined(FIO_IO) #undef FIO_STREAM #define FIO_STREAM #endif -#if defined(FIO_HTTP_HANDLE) || defined(FIO_SERVER) || defined(FIO_QUEUE) -#undef FIO_TIME -#define FIO_TIME -#endif - -#if defined(FIO_SERVER) +#if defined(FIO_IO) #undef FIO_QUEUE #define FIO_QUEUE #endif +#if defined(FIO_HTTP_HANDLE) || defined(FIO_QUEUE) +#undef FIO_TIME +#define FIO_TIME +#endif + /* ***************************************************************************** @@ -3763,7 +3836,7 @@ FIO_MAP Ordering & Naming Shortcut #endif #if defined(FIO_CLI) || defined(FIO_MEMORY_NAME) || defined(FIO_POLL) || \ - defined(FIO_STATE) + defined(FIO_STATE) || defined(FIO_HTTP_HANDLE) #undef FIO_IMAP_CORE #define FIO_IMAP_CORE #endif @@ -3780,7 +3853,7 @@ FIO_MAP Ordering & Naming Shortcut #define FIO_RAND #endif -#if defined(FIO_SERVER) +#if defined(FIO_IO) #undef FIO_SIGNAL #define FIO_SIGNAL #endif @@ -3967,6 +4040,7 @@ Leak Counter Helpers #define FIO_LEAK_COUNTER_DEF(name) #define FIO_LEAK_COUNTER_ON_ALLOC(name) #define FIO_LEAK_COUNTER_ON_FREE(name) +#define FIO_LEAK_COUNTER_COUNT(name) ((size_t)0) #else #define FIO_LEAK_COUNTER_DEF(name) \ FIO_IFUNC size_t FIO_NAME(fio___leak_counter, name)(size_t i) { \ @@ -6076,7 +6150,7 @@ Big Numbers FIO_IFUNC void fio___uXXX_hex_read(uint64_t *t, char **p, size_t l) { char *start = *p; - start += ((unsigned)(start[0] == '0' & start[1] == 'x') << 1); + start += (((unsigned)(start[0] == '0') & (start[1] == 'x')) << 1); char *pos = start; while (fio_i2c((uint8_t)*pos) < 16) ++pos; @@ -6666,6 +6740,80 @@ iMap Creation Macro #define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE #endif +#define FIO___IMAP_SEEKER_TYPE(bits) \ + typedef struct { \ + uint##bits##_t pos; \ + uint##bits##_t ipos; \ + uint##bits##_t set_val; \ + bool is_valid; \ + } fio___imap##bits##_seeker_s; \ + /** Returns the index map position and array position of a value, if any. */ \ + FIO_SFUNC fio___imap##bits##_seeker_s fio___imap##bits##_seek( \ + void *ary, \ + uint##bits##_t *imap, \ + const uint##bits##_t capa_bits, \ + void *pobj, \ + uint##bits##_t hash, \ + bool cmp_fn(void *arry, void *obj, uint##bits##_t indx), \ + const size_t max_attempts) { \ + fio___imap##bits##_seeker_s r = {(uint##bits##_t)(~(uint##bits##_t)0), \ + (uint##bits##_t)(~(uint##bits##_t)0), \ + (uint##bits##_t)(~(uint##bits##_t)0)}; \ + if (!ary) \ + return r; \ + const uint##bits##_t capa = ((uint##bits##_t)1 << capa_bits); \ + const uint##bits##_t pos_mask = (uint##bits##_t)(capa - 1); \ + const uint##bits##_t hash_mask = (uint##bits##_t) ~pos_mask; \ + uint##bits##_t tester = (hash & hash_mask); /* hide `tester` lower bits */ \ + uint##bits##_t pos = hash; /* use more bits */ \ + /* make sure tester isn't a reserved value (0 || ~0) */ \ + tester += (!tester) << capa_bits; \ + tester -= (hash_mask == tester) << capa_bits; \ + r.set_val = tester; /* store tester value */ \ + size_t attempts = max_attempts; \ + /* tests up to 3 groups of 4 bytes (uint32_t) within a 64 byte group */ \ + for (;;) { \ + for (size_t mini_steps = 0;;) { \ + pos &= pos_mask; \ + const uint##bits##_t pos_hash = imap[pos] & hash_mask; \ + const uint##bits##_t pos_index = imap[pos] & pos_mask; \ + if ((pos_hash == tester) && cmp_fn(ary, pobj, pos_index)) { \ + r.ipos = pos; \ + r.pos = pos_index; \ + r.set_val = tester | pos_index; \ + r.is_valid = 1; \ + return r; \ + } \ + if (!imap[pos]) { \ + r.ipos = pos; \ + r.set_val = tester; /* mark empty slot */ \ + return r; \ + } \ + if (imap[pos] == (uint##bits##_t)(~(uint##bits##_t)0)) { \ + r.ipos = pos; \ + } \ + if (!((--attempts))) \ + return r; \ + if (mini_steps == 2) \ + break; \ + pos += 3 + mini_steps; /* 0, 3, 7 => max of 56 byte distance */ \ + ++mini_steps; \ + } \ + pos += (uint##bits##_t)0xC19F5985UL; /* big step */ \ + } \ + } \ + /** utilizes the values returned by the seeker object. */ \ + FIO_IFUNC void fio___imap##bits##_set(uint##bits##_t *imap, \ + uint##bits##_t ipos, \ + uint##bits##_t set_val) { \ + imap[ipos] = set_val; \ + } + +FIO___IMAP_SEEKER_TYPE(8) +FIO___IMAP_SEEKER_TYPE(16) +FIO___IMAP_SEEKER_TYPE(32) +FIO___IMAP_SEEKER_TYPE(64) + /* ***************************************************************************** iMap Cleanup ***************************************************************************** */ @@ -6973,42 +7121,6 @@ Here's a few resources about hashes that might explain more: ***************************************************************************** */ -/* Primes with with 16 bits, half of them set. */ -#define FIO_U16_HASH_PRIME0 0xDA23U -#define FIO_U16_HASH_PRIME1 0xB48BU -#define FIO_U16_HASH_PRIME2 0xC917U -#define FIO_U16_HASH_PRIME3 0xD855U -#define FIO_U16_HASH_PRIME4 0xE0B9U -#define FIO_U16_HASH_PRIME5 0xE471U -#define FIO_U16_HASH_PRIME6 0x85CDU -#define FIO_U16_HASH_PRIME7 0xD433U -#define FIO_U16_HASH_PRIME8 0xE951U -#define FIO_U16_HASH_PRIME9 0xA8E5U - -/* Primes with with 32 bits, half of them set. */ -#define FIO_U32_HASH_PRIME0 0xC19F5985UL -#define FIO_U32_HASH_PRIME1 0x8D567931UL -#define FIO_U32_HASH_PRIME2 0x9C178B17UL -#define FIO_U32_HASH_PRIME3 0xA4B842DFUL -#define FIO_U32_HASH_PRIME4 0xB0B94EC9UL -#define FIO_U32_HASH_PRIME5 0xFA9E7084UL -#define FIO_U32_HASH_PRIME6 0xCA63037BUL -#define FIO_U32_HASH_PRIME7 0xD728C15DUL -#define FIO_U32_HASH_PRIME8 0xA872A277UL -#define FIO_U32_HASH_PRIME9 0xF5781551UL - -/* Primes with with 64 bits, half of them set. */ -#define FIO_U64_HASH_PRIME0 0x39664DEECA23D825 -#define FIO_U64_HASH_PRIME1 0x48644F7B3959621F -#define FIO_U64_HASH_PRIME2 0x613A19F5CB0D98D5 -#define FIO_U64_HASH_PRIME3 0x84B56B93C869EA0F -#define FIO_U64_HASH_PRIME4 0x8EE38D13E0D95A8D -#define FIO_U64_HASH_PRIME5 0x92E99EC981F0E279 -#define FIO_U64_HASH_PRIME6 0xDDC3100BEF158BB1 -#define FIO_U64_HASH_PRIME7 0x918F4D38049F78BD -#define FIO_U64_HASH_PRIME8 0xB6C9F8032A35E2D9 -#define FIO_U64_HASH_PRIME9 0xFA2A5F16D2A128D5 - /** Adds bit entropy to a pointer values. Designed to be unsafe. */ FIO_IFUNC uint64_t fio_risky_num(uint64_t n, uint64_t seed) { seed ^= fio_lrot64(seed, 47); @@ -7049,7 +7161,6 @@ SFUNC uint64_t fio_risky_hash(const void *data_, size_t len, uint64_t seed) { v[i + 4] += w[i]; \ } \ } while (0) - /* Approach inspired by komihash, copyrighted: Aleksey Vaneev, MIT license */ const uint8_t *data = (const uint8_t *)data_; uint64_t v[8] FIO_ALIGN(16), w[8] FIO_ALIGN(16) = {0}; @@ -7093,7 +7204,6 @@ SFUNC uint64_t fio_risky_hash(const void *data_, size_t len, uint64_t seed) { v[0] = w[5] + fio_math_mulc64(w[4], w[6], v + 1); v[0] += v[1]; return v[0]; - #undef FIO___RISKY_HASH_ROUND64 } @@ -10513,7 +10623,7 @@ OS specific patches. #include #endif #ifndef FIO_SOCK_FD_ISVALID -#define FIO_SOCK_FD_ISVALID(fd) ((size_t)fd <= (size_t)0x7FFFFFFF) +#define FIO_SOCK_FD_ISVALID(fd) ((size_t)(fd) <= (size_t)0x7FFFFFFF) #endif /** Acts as POSIX write. Use this macro for portability with WinSock2. */ #define fio_sock_write(fd, data, len) send((fd), (data), (len), 0) @@ -10568,7 +10678,7 @@ FIO_IFUNC int fio_sock_dup(int original) { #include #include #ifndef FIO_SOCK_FD_ISVALID -#define FIO_SOCK_FD_ISVALID(fd) ((int)fd != (int)-1) +#define FIO_SOCK_FD_ISVALID(fd) ((int)(fd) != (int)-1) #endif /** Acts as POSIX write. Use this macro for portability with WinSock2. */ #define fio_sock_write(fd, data, len) write((fd), (data), (len)) @@ -16286,8 +16396,8 @@ SFUNC int fio_poll_review(fio_poll_s *p, size_t timeout_) { struct kevent events[FIO_POLL_MAX_EVENTS] = {{0}}; const struct timespec timeout = { - .tv_sec = (time_t)(timeout_ / 1024), - .tv_nsec = (suseconds_t)((timeout_ & (1023UL)) * 1000000)}; + .tv_sec = (time_t)(timeout_ / 1000), + .tv_nsec = (suseconds_t)((timeout_ % 1000) * 1000000)}; /* wait for events and handle them */ int active_count = kevent(p->fd, NULL, 0, events, FIO_POLL_MAX_EVENTS, &timeout); @@ -19998,7 +20108,7 @@ SFUNC int fio_string_write_base32enc(fio_str_info_s *dest, fio_string_realloc_fn reallocate, const void *raw, size_t raw_len) { - const static uint8_t base32ecncode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + static const uint8_t base32ecncode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; int r = 0; size_t expected = ((raw_len * 8) / 5) + 1; if (fio_string___write_validate_len(dest, reallocate, &expected)) { @@ -22171,10 +22281,10 @@ Cleanup #undef FIO_MUSTACHE #undef FIO___UNTAG_T #endif /* FIO_MUSTACHE */ -/* ************************************************************************** */ +/* ************************************************************************* */ #if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ #define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_STR_NAME fio /* Development inclusion - ignore line */ +#define FIO_CRYPTO_CORE /* Development inclusion - ignore line */ #include "./include.h" /* Development inclusion - ignore line */ #endif /* Development inclusion - ignore line */ /* ***************************************************************************** @@ -22182,5916 +22292,5475 @@ Cleanup - Dynamic Strings (binary safe) + A Template for New Types / Modules + Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ -#ifdef FIO_STR_SMALL -#ifndef FIO_STR_NAME -#define FIO_STR_NAME FIO_STR_SMALL -#endif -#ifndef FIO_STR_OPTIMIZE4IMMUTABILITY -#define FIO_STR_OPTIMIZE4IMMUTABILITY 1 -#endif -#endif /* FIO_STR_SMALL */ - -#if defined(FIO_STR_NAME) - -#ifndef FIO_STR_OPTIMIZE_EMBEDDED -/** - * For each unit (0 by default), adds `sizeof(char *)` bytes to the type size, - * increasing the amount of strings that could be embedded within the type - * without additional memory allocation. - * - * For example, when using a reference counter wrapper on a 64bit system, it - * would make sense to set this value to 1 - allowing the type size to fully - * utilize a 16 byte memory allocation alignment. - */ -#define FIO_STR_OPTIMIZE_EMBEDDED 0 -#endif - -#ifndef FIO_STR_OPTIMIZE4IMMUTABILITY -/** - * Minimizes the struct size, storing only string length and pointer. - * - * By avoiding extra (mutable related) data, such as the allocated memory's - * capacity, strings require less memory. However, this does introduce a - * performance penalty when editing the string data. - */ -#define FIO_STR_OPTIMIZE4IMMUTABILITY 0 -#endif +#if defined(FIO_CRYPTO_CORE) && !defined(H___FIO_CRYPTO_CORE___H) +#define H___FIO_CRYPTO_CORE___H -#if FIO_STR_OPTIMIZE4IMMUTABILITY -/* enforce limit after which FIO_STR_OPTIMIZE4IMMUTABILITY makes no sense */ -#if FIO_STR_OPTIMIZE_EMBEDDED > 1 -#undef FIO_STR_OPTIMIZE_EMBEDDED -#define FIO_STR_OPTIMIZE_EMBEDDED 1 -#endif -#else -/* enforce limit due to 6 bit embedded string length limit (assumes 64 bit) */ -#if FIO_STR_OPTIMIZE_EMBEDDED > 4 -#undef FIO_STR_OPTIMIZE_EMBEDDED -#define FIO_STR_OPTIMIZE_EMBEDDED 4 -#endif -#endif /* FIO_STR_OPTIMIZE4IMMUTABILITY*/ +/* ***************************************************************************** +Module Implementation - inlined functions +***************************************************************************** */ /* ***************************************************************************** -String API - Initialization and Destruction +Module Implementation - possibly externed functions. ***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/** - * The `fio_str_s` type should be considered opaque. - * - * The type's attributes should be accessed ONLY through the accessor - * functions: `fio_str2cstr`, `fio_str_len`, `fio_str2ptr`, `fio_str_capa`, - * etc'. - * - * Note: when the `small` flag is present, the structure is ignored and used - * as raw memory for a small String (no additional allocation). This changes - * the String's behavior drastically and requires that the accessor functions - * be used. - */ -typedef struct { - /* String flags: - * - * bit 1: small string. - * bit 2: frozen string. - * bit 3: static (non allocated) string (big strings only). - * bit 3-8: small string length (up to 64 bytes). - */ - uint8_t special; - uint8_t reserved[(sizeof(void *) * (1 + FIO_STR_OPTIMIZE_EMBEDDED)) - - (sizeof(uint8_t))]; /* padding length */ -#if !FIO_STR_OPTIMIZE4IMMUTABILITY - size_t capa; /* known capacity for longer Strings */ - size_t len; /* String length for longer Strings */ -#endif /* FIO_STR_OPTIMIZE4IMMUTABILITY */ - char *buf; /* pointer for longer Strings */ -} FIO_NAME(FIO_STR_NAME, s); +/* ***************************************************************************** +Module Cleanup +***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_CRYPTO_CORE +#endif /* FIO_CRYPTO_CORE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_CHACHA /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -#ifdef FIO_PTR_TAG_TYPE -#define FIO_STR_PTR FIO_PTR_TAG_TYPE -#else -#define FIO_STR_PTR FIO_NAME(FIO_STR_NAME, s) * -#endif -#ifndef FIO_STR_INIT -/** - * This value should be used for initialization. For example: - * - * // on the stack - * fio_str_s str = FIO_STR_INIT; - * - * // or on the heap - * fio_str_s *str = malloc(sizeof(*str)); - * *str = FIO_STR_INIT; - * - * Remember to cleanup: - * - * // on the stack - * fio_str_destroy(&str); - * - * // or on the heap - * fio_str_free(str); - * free(str); - */ -#define FIO_STR_INIT \ - { .special = 0 } -/** - * This macro allows the container to be initialized with existing data, as long - * as it's memory was allocated with the same allocator (`malloc` / - * `fio_malloc`). - * - * The `capacity` value should exclude the NUL character (if exists). - * - * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the - * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) - */ -#define FIO_STR_INIT_EXISTING(buffer, length, capacity) \ - { .capa = (capacity), .len = (length), .buf = (buffer) } -/** - * This macro allows the container to be initialized with existing static data, - * that shouldn't be freed. - * - * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the - * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) - */ -#define FIO_STR_INIT_STATIC(buffer) \ - { \ - .special = 4, .capa = FIO_STRLEN((buffer)), .len = FIO_STRLEN((buffer)), \ - .buf = (char *)(buffer) \ - } + ChaCha20 & Poly1305 -/** - * This macro allows the container to be initialized with existing static data, - * that shouldn't be freed. - * - * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the - * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) - */ -#define FIO_STR_INIT_STATIC2(buffer, length) \ - { .special = 4, .capa = (length), .len = (length), .buf = (char *)(buffer) } -#endif /* FIO_STR_INIT */ -#ifndef FIO_REF_CONSTRUCTOR_ONLY -/** Allocates a new String object on the heap. */ -FIO_IFUNC FIO_STR_PTR FIO_NAME(FIO_STR_NAME, new)(void); +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_CHACHA) && !defined(H___FIO_CHACHA___H) +#define H___FIO_CHACHA___H 1 -/** - * Destroys the string and frees the container (if allocated with `new`). - */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, free)(FIO_STR_PTR s); -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ +/* ***************************************************************************** +ChaCha20Poly1305 API +***************************************************************************** */ /** - * Initializes the container with the provided static / constant string. - * - * The string will be copied to the container **only** if it will fit in the - * container itself. Otherwise, the supplied pointer will be used as is and it - * should remain valid until the string is destroyed. + * Performs an in-place encryption of `data` using ChaCha20 with additional + * data, producing a 16 byte message authentication code (MAC) using Poly1305. * - * The final string can be safely be destroyed (using the `destroy` function). + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * * `nounce` MUST point to a 96 bit long memory address (12 Bytes). + * * `ad` MAY be omitted, will NOT be encrypted. + * * `data` MAY be omitted, WILL be encrypted. + * * `mac` MUST point to a buffer with (at least) 16 available bytes. */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_const)(FIO_STR_PTR s, - const char *str, - size_t len); +SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nounce); /** - * Initializes the container with a copy of the provided dynamic string. + * Performs an in-place decryption of `data` using ChaCha20 after authenticating + * the message authentication code (MAC) using Poly1305. * - * The string is always copied and the final string must be destroyed (using the - * `destroy` function). - */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy)(FIO_STR_PTR s, - const char *str, - size_t len); - -/** - * Initializes the container with a copy of an existing String object. + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * * `nounce` MUST point to a 96 bit long memory address (12 Bytes). + * * `ad` MAY be omitted ONLY IF originally omitted. + * * `data` MAY be omitted, WILL be decrypted. + * * `mac` MUST point to a buffer where the 16 byte MAC is placed. * - * The string is always copied and the final string must be destroyed (using the - * `destroy` function). + * Returns `-1` on error (authentication failed). */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_STR_PTR dest, - FIO_STR_PTR src); +SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nounce); + +/* ***************************************************************************** +Using ChaCha20 and Poly1305 separately +***************************************************************************** */ /** - * Frees the String's resources and re-initializes the container. + * Performs an in-place encryption/decryption of `data` using ChaCha20. * - * Note: if the container isn't allocated on the stack, it should be freed - * separately using the appropriate `free` function. + * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * * `nounce` MUST point to a 96 bit long memory address (12 Bytes). + * * `counter` is the block counter, usually 1 unless `data` is mid-cyphertext. */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, destroy)(FIO_STR_PTR s); +SFUNC void fio_chacha20(void *restrict data, + size_t len, + const void *key, + const void *nounce, + uint32_t counter); /** - * Returns a C string with the existing data, re-initializing the String. - * - * Note: the String data is removed from the container, but the container - * isn't freed. - * - * Returns NULL if there's no String data. + * Given a Poly1305 256bit (32 byte) key, writes the authentication code for the + * poly message and additional data into `mac_dest`. * - * NOTE: Returned string is ALWAYS dynamically allocated. Remember to free. + * * `key` MUST point to a 256 bit long memory address (32 Bytes). */ -FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, detach)(FIO_STR_PTR s); - -/** Frees the pointer returned by `detach`. */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, dealloc)(void *ptr); +SFUNC void fio_poly1305_auth(void *restrict mac_dest, + const void *key256bits, + void *restrict message, + size_t len, + const void *additional_data, + size_t additional_data_len); /* ***************************************************************************** -String API - String state (data pointers, length, capacity, etc') +ChaCha20Poly1305 Implementation ***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/** Returns the String's complete state (capacity, length and pointer). */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, info)(const FIO_STR_PTR s); +/* ***************************************************************************** +Poly1305 (authentication) +Prime 2^130-5 = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB +The math is mostly copied from: https://github.com/floodyberry/poly1305-donna +***************************************************************************** */ +/* + * Math copied from https://github.com/floodyberry/poly1305-donna + * + * With thanks to Andrew Moon. + */ +typedef struct { + /* r (cycle key addition) is 128 bits */ + uint64_t r[3]; + /* s (final key addition) is 128 bits */ + uint64_t s[2]; + /* Accumulator should not exceed 131 bits at the end of every cycle. */ + uint64_t a[3]; +} FIO_ALIGN(16) fio___poly_s; -/** Returns the String's partial state (length and pointer). */ -FIO_IFUNC fio_buf_info_s FIO_NAME(FIO_STR_NAME, buf)(const FIO_STR_PTR s); +FIO_IFUNC fio___poly_s fio___poly_init(const void *key256b) { + static const uint64_t defkey[4] = {0}; + if (!key256b) + key256b = (const void *)defkey; + uint64_t t0, t1; + /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ + t0 = fio_buf2u64_le((uint8_t *)key256b + 0); + t1 = fio_buf2u64_le((uint8_t *)key256b + 8); + fio___poly_s pl = { + .r = + { + ((t0)&0xffc0fffffff), + (((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff), + (((t1 >> 24)) & 0x00ffffffc0f), + }, + .s = + { + fio_buf2u64_le(((uint8_t *)key256b + 16)), + fio_buf2u64_le(((uint8_t *)key256b + 24)), + }, + }; + return pl; +} +FIO_IFUNC void fio___poly_consume128bit(fio___poly_s *pl, + const void *msg, + uint64_t is_full) { + uint64_t r0, r1, r2; + uint64_t s1, s2; + uint64_t a0, a1, a2; + uint64_t c; + uint64_t d0[2], d1[2], d2[2], d[2]; -/** Returns a pointer (`char *`) to the String's content. */ -FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, ptr)(FIO_STR_PTR s); + r0 = pl->r[0]; + r1 = pl->r[1]; + r2 = pl->r[2]; -/** Returns the String's length in bytes. */ -FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, len)(FIO_STR_PTR s); + a0 = pl->a[0]; + a1 = pl->a[1]; + a2 = pl->a[2]; -/** Returns the String's existing capacity (total used & available memory). */ -FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, capa)(FIO_STR_PTR s); + s1 = r1 * (5 << 2); + s2 = r2 * (5 << 2); -/** Prevents further manipulations to the String's content. */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, freeze)(FIO_STR_PTR s); + { + uint64_t t0, t1; + t0 = fio_buf2u64_le(msg); + t1 = fio_buf2u64_le(((uint8_t *)msg + 8)); + /* a += msg */ + a0 += ((t0)&0xFFFFFFFFFFF); + a1 += (((t0 >> 44) | (t1 << 20)) & 0xFFFFFFFFFFF); + a2 += (((t1 >> 24)) & 0x3FFFFFFFFFF) | (is_full << 40); + } -/** Returns true if the string is frozen. */ -FIO_IFUNC uint8_t FIO_NAME_BL(FIO_STR_NAME, frozen)(FIO_STR_PTR s); + /* a *= r */ + d0[0] = fio_math_mulc64(a0, r0, d0 + 1); + d[0] = fio_math_mulc64(a1, s2, d + 1); + d0[0] = fio_math_addc64(d0[0], d[0], 0, &c); + d0[1] += d[1] + c; -/** Returns 1 if memory was allocated (and the String must be destroyed). */ -FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, allocated)(const FIO_STR_PTR s); + d[0] = fio_math_mulc64(a2, s1, d + 1); + d0[0] = fio_math_addc64(d0[0], d[0], 0, &c); + d0[1] += d[1] + c; -/** Binary comparison returns `1` if both strings are equal and `0` if not. */ -FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, eq)(const FIO_STR_PTR str1, - const FIO_STR_PTR str2); + d1[0] = fio_math_mulc64(a0, r1, d1 + 1); + d[0] = fio_math_mulc64(a1, r0, d + 1); + d1[0] = fio_math_addc64(d1[0], d[0], 0, &c); + d1[1] += d[1] + c; -/** - * Returns the string's Risky Hash value. - * - * Note: Hash algorithm might change without notice. - */ -FIO_IFUNC uint64_t FIO_NAME(FIO_STR_NAME, hash)(const FIO_STR_PTR s, - uint64_t seed); + d[0] = fio_math_mulc64(a2, s2, d + 1); + d1[0] = fio_math_addc64(d1[0], d[0], 0, &c); + d1[1] += d[1] + c; -/* ***************************************************************************** -String API - Memory management -***************************************************************************** */ + d2[0] = fio_math_mulc64(a0, r2, d2 + 1); + d[0] = fio_math_mulc64(a1, r1, d + 1); + d2[0] = fio_math_addc64(d2[0], d[0], 0, &c); + d2[1] += d[1] + c; -/** - * Sets the new String size without reallocating any memory (limited by - * existing capacity). - * - * Returns the updated state of the String. - * - * Note: When shrinking, any existing data beyond the new size may be - * corrupted. - */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, resize)(FIO_STR_PTR s, - size_t size); - -/** - * Performs a best attempt at minimizing memory consumption. - * - * Actual effects depend on the underlying memory allocator and it's - * implementation. Not all allocators will free any memory. - */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, compact)(FIO_STR_PTR s); - -#if !FIO_STR_OPTIMIZE4IMMUTABILITY -/** - * Reserves (at least) `amount` of bytes for the string's data. - * - * The reserved count includes used data. If `amount` is less than the current - * string length, the string will be truncated(!). - * - * Note: When optimized for immutability (`FIO_STR_SMALL`), this may corrupt the - * string length data. - * - * Make sure to call `resize` with the updated information once the editing is - * done. - * - * Returns the updated state of the String. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, reserve)(FIO_STR_PTR s, - size_t amount); -#define FIO_STR_RESERVE_NAME reserve -#else -/** INTERNAL - DO NOT USE! */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, __reserve)(FIO_STR_PTR s, - size_t amount); -#define FIO_STR_RESERVE_NAME __reserve -#endif -/* ***************************************************************************** -String API - UTF-8 State -***************************************************************************** */ - -/** Returns 1 if the String is UTF-8 valid and 0 if not. */ -SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_valid)(FIO_STR_PTR s); - -/** Returns the String's length in UTF-8 characters. */ -SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_len)(FIO_STR_PTR s); - -/** - * Takes a UTF-8 character selection information (UTF-8 position and length) - * and updates the same variables so they reference the raw byte slice - * information. - * - * If the String isn't UTF-8 valid up to the requested selection, than `pos` - * will be updated to `-1` otherwise values are always positive. - * - * The returned `len` value may be shorter than the original if there wasn't - * enough data left to accommodate the requested length. When a `len` value of - * `0` is returned, this means that `pos` marks the end of the String. - * - * Returns -1 on error and 0 on success. - */ -SFUNC int FIO_NAME(FIO_STR_NAME, - utf8_select)(FIO_STR_PTR s, intptr_t *pos, size_t *len); + d[0] = fio_math_mulc64(a2, r0, d + 1); + d2[0] = fio_math_addc64(d2[0], d[0], 0, &c); + d2[1] += d[1] + c; -/* ***************************************************************************** -String API - Content Manipulation and Review -***************************************************************************** */ + /* (partial) a %= p */ + c = (d0[0] >> 44) | (d0[1] << 20); + a0 = d0[0] & 0xfffffffffff; + d1[0] = fio_math_addc64(d1[0], c, 0, &c); + d1[1] += c; -/** Writes data at the end of the String. */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write)(FIO_STR_PTR s, - const void *src, - size_t src_len); + c = (d1[0] >> 44) | (d1[1] << 20); + a1 = d1[0] & 0xfffffffffff; + d2[0] = fio_math_addc64(d2[0], c, 0, &c); + d2[1] += c; -/** - * Appends the `src` String to the end of the `dest` String. - * - * If `dest` is empty, the resulting Strings will be equal. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, concat)(FIO_STR_PTR dest, - FIO_STR_PTR const src); + c = (d2[0] >> 42) | (d2[1] << 22); + a2 = d2[0] & 0x3ffffffffff; + a0 += c * 5; + c = a0 >> 44; + a0 = a0 & 0xfffffffffff; + a1 += c; -/** Alias for fio_str_concat */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, join)(FIO_STR_PTR dest, - FIO_STR_PTR const src) { - return FIO_NAME(FIO_STR_NAME, concat)(dest, src); + pl->a[0] = a0; + pl->a[1] = a1; + pl->a[2] = a2; } -/** - * Replaces the data in the String - replacing `old_len` bytes starting at - * `start_pos`, with the data at `src` (`src_len` bytes long). - * - * Negative `start_pos` values are calculated backwards, `-1` == end of - * String. - * - * When `old_len` is zero, the function will insert the data at `start_pos`. - * - * If `src_len == 0` than `src` will be ignored and the data marked for - * replacement will be erased. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, replace)(FIO_STR_PTR s, - intptr_t start_pos, - size_t old_len, - const void *src, - size_t src_len); - -/** Writes data at the end of the String. See `fio_string_write2`. */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - __write2)(FIO_STR_PTR s, - const fio_string_write_s srcs[]); - -#ifndef FIO_STR_WRITE2 -#define FIO_STR_WRITE2(str_name, dest, ...) \ - FIO_NAME(str_name, __write2)(dest, (fio_string_write_s[]){__VA_ARGS__, {0}}) -#endif -/* ***************************************************************************** -String API - Numerals -***************************************************************************** */ +FIO_IFUNC void fio___poly_finilize(fio___poly_s *pl) { + uint64_t a0, a1, a2, c; + uint64_t g0, g1, g2; + uint64_t t0, t1; -/** Writes a number at the end of the String using normal base 10 notation. */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_i)(FIO_STR_PTR s, - int64_t num); + /* fully carry a */ + a0 = pl->a[0]; + a1 = pl->a[1]; + a2 = pl->a[2]; -/** Writes a number at the end of the String using Hex (base 16) notation. */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_hex)(FIO_STR_PTR s, - int64_t num); + c = (a1 >> 44); + a1 &= 0xFFFFFFFFFFF; + a2 += c; + c = (a2 >> 42); + a2 &= 0x3FFFFFFFFFF; + a0 += c * 5; + c = (a0 >> 44); + a0 &= 0xFFFFFFFFFFF; + a1 += c; + c = (a1 >> 44); + a1 &= 0xFFFFFFFFFFF; + a2 += c; + c = (a2 >> 42); + a2 &= 0x3FFFFFFFFFF; + a0 += c * 5; + c = (a0 >> 44); + a0 &= 0xFFFFFFFFFFF; + a1 += c; -/* Writes a binary representation of `i` to the String */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_bin)(FIO_STR_PTR s, - int64_t num); + /* compute a + -p */ + g0 = a0 + 5; + c = (g0 >> 44); + g0 &= 0xFFFFFFFFFFF; + g1 = a1 + c; + c = (g1 >> 44); + g1 &= 0xFFFFFFFFFFF; + g2 = a2 + c - ((uint64_t)1 << 42); -/* ***************************************************************************** -String API - printf style support -***************************************************************************** */ + /* select h if h < p, or h + -p if h >= p */ + c = (g2 >> ((sizeof(uint64_t) * 8) - 1)) - 1; + g0 &= c; + g1 &= c; + g2 &= c; + c = ~c; + a0 = (a0 & c) | g0; + a1 = (a1 & c) | g1; + a2 = (a2 & c) | g2; -/** - * Writes to the String using a vprintf like interface. - * - * Data is written to the end of the String. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, vprintf)(FIO_STR_PTR s, - const char *format, - va_list argv); + /* a = (a + Poly S key) */ + t0 = pl->s[0]; + t1 = pl->s[1]; -/** - * Writes to the String using a printf like interface. - * - * Data is written to the end of the String. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - printf)(FIO_STR_PTR s, const char *format, ...); + a0 += ((t0)&0xFFFFFFFFFFF); + c = (a0 >> 44); + a0 &= 0xFFFFFFFFFFF; + a1 += (((t0 >> 44) | (t1 << 20)) & 0xFFFFFFFFFFF) + c; + c = (a1 >> 44); + a1 &= 0xFFFFFFFFFFF; + a2 += (((t1 >> 24)) & 0x3FFFFFFFFFF) + c; + a2 &= 0x3FFFFFFFFFF; -/* ***************************************************************************** -String API - C / JSON escaping -***************************************************************************** */ + /* mac = a % (2^128) */ + a0 = ((a0) | (a1 << 44)); + a1 = ((a1 >> 20) | (a2 << 24)); + pl->a[0] = a0; + pl->a[1] = a1; +} -/** - * Writes data at the end of the String, escaping the data using JSON semantics. - * - * The JSON semantic are common to many programming languages, promising a UTF-8 - * String while making it easy to read and copy the string during debugging. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_escape)(FIO_STR_PTR s, - const void *data, - size_t data_len); +FIO_IFUNC void fio___poly_consume_msg(fio___poly_s *pl, + uint8_t *msg, + size_t len) { + /* read 16 byte blocks */ + uint64_t n[2]; + for (size_t i = 31; i < len; i += 32) { + fio___poly_consume128bit(pl, msg, 1); + fio___poly_consume128bit(pl, msg + 16, 1); + msg += 32; + } + if ((len & 16)) { + fio___poly_consume128bit(pl, msg, 1); + msg += 16; + } + if ((len & 15)) { + n[0] = 0; + n[1] = 0; + fio_memcpy15x(n, msg, len); + n[0] = fio_ltole64(n[0]); + n[1] = fio_ltole64(n[1]); + ((uint8_t *)n)[len & 15] = 0x01; + fio___poly_consume128bit(pl, (void *)n, 0); + } +} -/** - * Writes an escaped data into the string after unescaping the data. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_unescape)(FIO_STR_PTR s, - const void *escaped, - size_t len); +/* Given a Poly1305 key, writes a MAC into `mac_dest`. */ +SFUNC void fio_poly1305_auth(void *restrict mac, + const void *key, + void *restrict msg, + size_t len, + const void *ad, + size_t ad_len) { + fio___poly_s pl = fio___poly_init(key); + fio___poly_consume_msg(&pl, (uint8_t *)ad, ad_len); + fio___poly_consume_msg(&pl, (uint8_t *)msg, len); + fio___poly_finilize(&pl); + fio_u2buf64_le(mac, pl.a[0]); + fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); +} /* ***************************************************************************** -String API - Base64 support +ChaCha20 (encryption) ***************************************************************************** */ -/** - * Writes data at the end of the String, encoding the data as Base64 encoded - * data. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - write_base64enc)(FIO_STR_PTR s, - const void *data, - size_t data_len, - uint8_t url_encoded); - -/** - * Writes decoded base64 data to the end of the String. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - write_base64dec)(FIO_STR_PTR s, - const void *encoded, - size_t encoded_len); +#define FIO___CHACHA_VROUND(count, a, b, c, d) \ + for (size_t i = 0; i < count; ++i) { \ + a[i] += b[i]; \ + d[i] ^= a[i]; \ + d[i] = (d[i] << 16) | (d[i] >> (32 - 16)); \ + c[i] += d[i]; \ + b[i] ^= c[i]; \ + b[i] = (b[i] << 12) | (b[i] >> (32 - 12)); \ + a[i] += b[i]; \ + d[i] ^= a[i]; \ + d[i] = (d[i] << 8) | (d[i] >> (32 - 8)); \ + c[i] += d[i]; \ + b[i] ^= c[i]; \ + b[i] = (b[i] << 7) | (b[i] >> (32 - 7)); \ + } -/* ***************************************************************************** -String API - HTML escaping support -***************************************************************************** */ +FIO_IFUNC fio_u512 fio___chacha_init(const void *key, + const void *nounce, + uint32_t counter) { + fio_u512 o = { + .u32 = + { + // clang-format off + 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, + fio_buf2u32_le(key), + fio_buf2u32_le((uint8_t *)key + 4), + fio_buf2u32_le((uint8_t *)key + 8), + fio_buf2u32_le((uint8_t *)key + 12), + fio_buf2u32_le((uint8_t *)key + 16), + fio_buf2u32_le((uint8_t *)key + 20), + fio_buf2u32_le((uint8_t *)key + 24), + fio_buf2u32_le((uint8_t *)key + 28), + counter, + fio_buf2u32_le(nounce), + fio_buf2u32_le((uint8_t *)nounce + 4), + fio_buf2u32_le((uint8_t *)nounce + 8), + }, // clang-format on + }; + return o; +} -/** Writes HTML escaped data to a String. */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_html_escape)(FIO_STR_PTR s, - const void *raw, - size_t len); +FIO_SFUNC void fio___chacha_vround20(const fio_u512 c, uint8_t *restrict data) { + uint32_t v[16]; + for (size_t i = 0; i < 16; ++i) { + v[i] = c.u32[i]; + } + for (size_t round__ = 0; round__ < 10; ++round__) { /* 2 rounds per loop */ + FIO___CHACHA_VROUND(4, v, (v + 4), (v + 8), (v + 12)); + fio_u32x4_reshuffle((v + 4), 1, 2, 3, 0); + fio_u32x4_reshuffle((v + 8), 2, 3, 0, 1); + fio_u32x4_reshuffle((v + 12), 3, 0, 1, 2); + FIO___CHACHA_VROUND(4, v, (v + 4), (v + 8), (v + 12)); + fio_u32x4_reshuffle((v + 4), 3, 0, 1, 2); + fio_u32x4_reshuffle((v + 8), 2, 3, 0, 1); + fio_u32x4_reshuffle((v + 12), 1, 2, 3, 0); + } + for (size_t i = 0; i < 16; ++i) { + v[i] += c.u32[i]; + } -/** Writes HTML un-escaped data to a String - incomplete and minimal. */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - write_html_unescape)(FIO_STR_PTR s, - const void *escaped, - size_t len); +#if __BIG_ENDIAN__ + for (size_t i = 0; i < 16; ++i) { + v[i] = fio_bswap32(v[i]); + } +#endif + { + uint32_t d[16]; + fio_memcpy64(d, data); + for (size_t i = 0; i < 16; ++i) { + d[i] ^= v[i]; + } + fio_memcpy64(data, d); + } +} -/* ***************************************************************************** -String API - writing data from files to the String -***************************************************************************** */ +FIO_SFUNC void fio___chacha_vround20x2(fio_u512 c, uint8_t *restrict data) { + uint32_t v[32]; + for (size_t i = 0; i < 16; ++i) { + v[i + (i & (4 | 8))] = c.u32[i]; + v[i + 4 + (i & (4 | 8))] = c.u32[i]; + } + ++v[28]; + for (size_t round__ = 0; round__ < 10; ++round__) { /* 2 rounds per loop */ + FIO___CHACHA_VROUND(8, v, (v + 8), (v + 16), (v + 24)); + fio_u32x8_reshuffle((v + 8), 1, 2, 3, 0, 5, 6, 7, 4); + fio_u32x8_reshuffle((v + 16), 2, 3, 0, 1, 6, 7, 4, 5); + fio_u32x8_reshuffle((v + 24), 3, 0, 1, 2, 7, 4, 5, 6); + FIO___CHACHA_VROUND(8, v, (v + 8), (v + 16), (v + 24)); + fio_u32x8_reshuffle((v + 8), 3, 0, 1, 2, 7, 4, 5, 6); + fio_u32x8_reshuffle((v + 16), 2, 3, 0, 1, 6, 7, 4, 5); + fio_u32x8_reshuffle((v + 24), 1, 2, 3, 0, 5, 6, 7, 4); + } + for (size_t i = 0; i < 16; ++i) { + v[i + (i & (4 | 8))] += c.u32[i]; + v[i + 4 + (i & (4 | 8))] += c.u32[i]; + } + ++v[28]; -/** - * Reads data from a file descriptor `fd` at offset `start_at` and pastes it's - * contents (or a slice of it) at the end of the String. If `limit == 0`, than - * the data will be read until EOF. - * - * The file should be a regular file or the operation might fail (can't be used - * for sockets). - * - * The file descriptor will remain open and should be closed manually. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfd)(FIO_STR_PTR s, - int fd, - intptr_t start_at, - intptr_t limit); -/** - * Opens the file `filename` and pastes it's contents (or a slice ot it) at - * the end of the String. If `limit == 0`, than the data will be read until - * EOF. - * - * If the file can't be located, opened or read, or if `start_at` is beyond - * the EOF position, NULL is returned in the state's `data` field. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfile)(FIO_STR_PTR s, - const char *filename, - intptr_t start_at, - intptr_t limit); -/* ***************************************************************************** -String API - Testing -***************************************************************************** */ -#ifdef FIO_STR_WRITE_TEST_FUNC -/** - * Tests the fio_str functionality. - */ -SFUNC void FIO_NAME_TEST(stl, FIO_STR_NAME)(void); +#if __BIG_ENDIAN__ + for (size_t i = 0; i < 32; ++i) { + v[i] = fio_bswap32(v[i]); + } #endif -/* ***************************************************************************** + { + fio_u32x8_reshuffle((v + 4), 4, 5, 6, 7, 0, 1, 2, 3); + fio_u32x8_reshuffle((v + 20), 4, 5, 6, 7, 0, 1, 2, 3); + uint32_t d[8]; + fio_memcpy32(d, data); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[i]; + } + fio_memcpy32(data, d); + fio_memcpy32(d, data + 32); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[16 + i]; + } + fio_memcpy32(data + 32, d); - String Implementation + fio_memcpy32(d, data + 64); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[8 + i]; + } + fio_memcpy32(data + 64, d); - IMPLEMENTATION - INLINED + fio_memcpy32(d, data + 96); + for (size_t i = 0; i < 8; ++i) { + d[i] ^= v[24 + i]; + } + fio_memcpy32(data + 96, d); + } +} +SFUNC void fio_chacha20(void *restrict data, + size_t len, + const void *key, + const void *nounce, + uint32_t counter) { + fio_u512 c = fio___chacha_init(key, nounce, counter); + for (size_t pos = 127; pos < len; pos += 128) { + fio___chacha_vround20x2(c, (uint8_t *)data); + c.u32[12] += 2; /* block counter */ + data = (void *)((uint8_t *)data + 128); + } + if ((len & 64)) { + fio___chacha_vround20(c, (uint8_t *)data); + data = (void *)((uint8_t *)data + 64); + ++c.u32[12]; + } + if ((len & 63)) { + fio_u512 dest; /* no need to initialize, junk data disregarded. */ + fio_memcpy63x(dest.u64, data, len); + fio___chacha_vround20(c, dest.u8); + fio_memcpy63x(data, dest.u64, len); + } +} +/* ***************************************************************************** +ChaCha20Poly1305 Encryption with Authentication ***************************************************************************** */ -/* used here, but declared later (reference counter is static / global). */ - -SFUNC FIO_NAME(FIO_STR_NAME, s) * FIO_NAME(FIO_STR_NAME, __object_new)(void); -SFUNC void FIO_NAME(FIO_STR_NAME, __object_free)(FIO_NAME(FIO_STR_NAME, s) * s); -SFUNC int FIO_NAME(FIO_STR_NAME, __default_reallocate)(fio_str_info_s *dest, - size_t new_capa); -SFUNC int FIO_NAME(FIO_STR_NAME, - __default_copy_and_reallocate)(fio_str_info_s *dest, - size_t new_capa); -SFUNC void FIO_NAME(FIO_STR_NAME, __default_free)(void *ptr, size_t capa); - -/* ***************************************************************************** -String Macro Helpers -***************************************************************************** */ - -#define FIO_STR_IS_SMALL(s) ((((s)->special & 1) | !(s)->buf)) -#define FIO_STR_SMALL_LEN(s) ((size_t)((s)->special >> 2)) -#define FIO_STR_SMALL_LEN_SET(s, l) \ - ((s)->special = (((s)->special & 2) | ((uint8_t)(l) << 2) | 1)) -#define FIO_STR_SMALL_CAPA(s) ((sizeof(*(s)) - 2) & 63) -#define FIO_STR_SMALL_DATA(s) ((char *)((s)->reserved)) - -#define FIO_STR_BIG_DATA(s) ((s)->buf) -#define FIO_STR_BIG_IS_DYNAMIC(s) (!((s)->special & 4)) -#define FIO_STR_BIG_SET_STATIC(s) ((s)->special |= 4) -#define FIO_STR_BIG_FREE_BUF(s) \ - (FIO_NAME(FIO_STR_NAME, __default_free)((s)->buf, FIO_STR_BIG_CAPA((s)))) - -#define FIO_STR_IS_FROZEN(s) ((s)->special & 2) -#define FIO_STR_FREEZE_(s) ((s)->special |= 2) -#define FIO_STR_THAW_(s) ((s)->special ^= (uint8_t)2) - -#if FIO_STR_OPTIMIZE4IMMUTABILITY - -#define FIO_STR_BIG_LEN(s) \ - ((sizeof(void *) == 4) \ - ? (((uint32_t)(s)->reserved[0]) | ((uint32_t)(s)->reserved[1] << 8) | \ - ((uint32_t)(s)->reserved[2] << 16)) \ - : (((uint64_t)(s)->reserved[0]) | ((uint64_t)(s)->reserved[1] << 8) | \ - ((uint64_t)(s)->reserved[2] << 16) | \ - ((uint64_t)(s)->reserved[3] << 24) | \ - ((uint64_t)(s)->reserved[4] << 32) | \ - ((uint64_t)(s)->reserved[5] << 40) | \ - ((uint64_t)(s)->reserved[6] << 48))) -#define FIO_STR_BIG_LEN_SET(s, l) \ - do { \ - if (sizeof(void *) == 4) { \ - if (!((l) & ((~(uint32_t)0) << 24))) { \ - (s)->reserved[0] = (l)&0xFF; \ - (s)->reserved[1] = ((uint32_t)(l) >> 8) & 0xFF; \ - (s)->reserved[2] = ((uint32_t)(l) >> 16) & 0xFF; \ - } else { \ - FIO_LOG_ERROR("facil.io small string length error - too long"); \ - (s)->reserved[0] = 0xFF; \ - (s)->reserved[1] = 0xFF; \ - (s)->reserved[2] = 0xFF; \ - } \ - } else { \ - if (!((l) & ((~(uint64_t)0) << 56))) { \ - (s)->reserved[0] = (l)&0xFF; \ - (s)->reserved[1] = ((uint64_t)(l) >> 8) & 0xFF; \ - (s)->reserved[2] = ((uint64_t)(l) >> 16) & 0xFF; \ - (s)->reserved[3] = ((uint64_t)(l) >> 24) & 0xFF; \ - (s)->reserved[4] = ((uint64_t)(l) >> 32) & 0xFF; \ - (s)->reserved[5] = ((uint64_t)(l) >> 40) & 0xFF; \ - (s)->reserved[6] = ((uint64_t)(l) >> 48) & 0xFF; \ - } else { \ - FIO_LOG_ERROR("facil.io small string length error - too long"); \ - (s)->reserved[0] = 0xFF; \ - (s)->reserved[1] = 0xFF; \ - (s)->reserved[2] = 0xFF; \ - (s)->reserved[3] = 0xFF; \ - (s)->reserved[4] = 0xFF; \ - (s)->reserved[5] = 0xFF; \ - (s)->reserved[6] = 0xFF; \ - } \ - } \ - } while (0) -#define FIO_STR_BIG_CAPA(s) fio_string_capa4len(FIO_STR_BIG_LEN((s))) -#define FIO_STR_BIG_CAPA_SET(s, capa) -#else -#define FIO_STR_BIG_LEN(s) ((s)->len) -#define FIO_STR_BIG_LEN_SET(s, l) ((s)->len = (l)) -#define FIO_STR_BIG_CAPA(s) ((s)->capa) -#define FIO_STR_BIG_CAPA_SET(s, capa) (FIO_STR_BIG_CAPA(s) = (capa)) -#endif - -/* ***************************************************************************** -String Information Round-tripping -***************************************************************************** */ - -/** Returns the String's complete state (capacity, length and pointer). */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, info)(const FIO_STR_PTR s_) { - fio_str_info_s r = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(s_, r); - FIO_NAME(FIO_STR_NAME, s) *s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (FIO_STR_IS_SMALL(s)) - r = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), - FIO_STR_SMALL_LEN(s), - FIO_STR_SMALL_CAPA(s)); - else - r = FIO_STR_INFO3(FIO_STR_BIG_DATA(s), - FIO_STR_BIG_LEN(s), - FIO_STR_BIG_CAPA(s)); - r.capa &= ((size_t)0ULL - (!FIO_STR_IS_FROZEN(s))); - return r; -} - -/** Returns the String's partial state (length and pointer). */ -FIO_IFUNC fio_buf_info_s FIO_NAME(FIO_STR_NAME, buf)(const FIO_STR_PTR s_) { - fio_buf_info_s r = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(s_, r); - FIO_NAME(FIO_STR_NAME, s) *s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (FIO_STR_IS_SMALL(s)) - r = FIO_BUF_INFO2(FIO_STR_SMALL_DATA(s), FIO_STR_SMALL_LEN(s)); - else - r = FIO_BUF_INFO2(FIO_STR_BIG_DATA(s), FIO_STR_BIG_LEN(s)); - return r; +FIO_IFUNC fio_u512 fio___chacha20_mixround(fio_u512 c) { + fio_u512 k = {.u64 = {0}}; + fio___chacha_vround20(c, k.u8); + return k; } - -/* Internal(!): updated String data according to `info`. */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, __info_update)(const FIO_STR_PTR s_, - fio_str_info_s info) { - /* internally used function, tagging already validated. */ - FIO_NAME(FIO_STR_NAME, s) *s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (info.buf == FIO_STR_SMALL_DATA(s)) { - s->special |= 1; - FIO_STR_SMALL_LEN_SET(s, info.len); - return; +SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nounce) { + fio_u512 c = fio___chacha_init(key, nounce, 0); + fio___poly_s pl; + { + fio_u512 c2 = fio___chacha20_mixround(c); + pl = fio___poly_init(&c2); } - s->special = 0; - FIO_STR_BIG_LEN_SET(s, info.len); - FIO_STR_BIG_CAPA_SET(s, info.capa); - s->buf = info.buf; -} - -/* Internal(!): updated String data according to `info`. */ -FIO_IFUNC fio_string_realloc_fn FIO_NAME(FIO_STR_NAME, - __realloc_func)(const FIO_STR_PTR s_) { - fio_string_realloc_fn options[] = { - FIO_NAME(FIO_STR_NAME, __default_reallocate), - FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate), - }; - /* internally used function, tagging already validated. */ - FIO_NAME(FIO_STR_NAME, s) *s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - return options[FIO_STR_IS_SMALL(s) | !FIO_STR_BIG_IS_DYNAMIC(s)]; + ++c.u32[12]; /* block counter */ + for (size_t i = 31; i < adlen; i += 32) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + fio___poly_consume128bit(&pl, (uint8_t *)ad + 16, 1); + ad = (void *)((uint8_t *)ad + 32); + } + if (adlen & 16) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + ad = (void *)((uint8_t *)ad + 16); + } + if (adlen & 15) { + uint64_t tmp[2] = {0}; /* 16 byte pad */ + fio_memcpy15x(tmp, ad, adlen); + fio___poly_consume128bit(&pl, (uint8_t *)tmp, 1); + } + for (size_t i = 127; i < len; i += 128) { + fio___chacha_vround20x2(c, (uint8_t *)data); + fio___poly_consume128bit(&pl, data, 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 16), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 32), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 48), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 64), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 80), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 96), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 112), 1); + c.u32[12] += 2; /* block counter */ + data = (void *)((uint8_t *)data + 128); + } + if ((len & 64)) { + fio___chacha_vround20(c, (uint8_t *)data); + fio___poly_consume128bit(&pl, data, 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 16), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 32), 1); + fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 48), 1); + ++c.u32[12]; /* block counter */ + data = (void *)((uint8_t *)data + 64); + } + if ((len & 63)) { + fio_u512 dest; + fio_memcpy63x(dest.u8, data, len); + fio___chacha_vround20(c, dest.u8); + fio_memcpy63x(data, dest.u8, len); + uint8_t *p = dest.u8; + if ((len & 32)) { + fio___poly_consume128bit(&pl, p, 1); + fio___poly_consume128bit(&pl, (p + 16), 1); + p += 32; + } + if ((len & 16)) { + fio___poly_consume128bit(&pl, p, 1); + p += 16; + } + if ((len & 15)) { + /* zero out poly padding */ + for (size_t i = (len & 15UL); i < 16; i++) + p[i] = 0; + fio___poly_consume128bit(&pl, p, 1); + } + } + { + uint64_t mac_data[2] = {fio_ltole64(adlen), fio_ltole64(len)}; + fio___poly_consume128bit(&pl, (uint8_t *)mac_data, 1); + } + fio___poly_finilize(&pl); + fio_u2buf64_le(mac, pl.a[0]); + fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); } -/* ***************************************************************************** -String Constructors (inline) -***************************************************************************** */ -#ifndef FIO_REF_CONSTRUCTOR_ONLY - -/** Allocates a new String object on the heap. */ -FIO_IFUNC FIO_STR_PTR FIO_NAME(FIO_STR_NAME, new)(void) { - FIO_NAME(FIO_STR_NAME, s) *const s = FIO_NAME(FIO_STR_NAME, __object_new)(); - if (!FIO_MEM_REALLOC_IS_SAFE_ && s) { - *s = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT; +SFUNC void fio_chacha20_poly1305_auth(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nounce) { + fio___poly_s pl; + { + fio_u512 c = fio___chacha_init(key, nounce, 0); + c = fio___chacha20_mixround(c); /* computes poly1305 key */ + pl = fio___poly_init(&c); + } + for (size_t i = 31; i < adlen; i += 32) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + fio___poly_consume128bit(&pl, (uint8_t *)ad + 16, 1); + ad = (void *)((uint8_t *)ad + 32); + } + if (adlen & 16) { + fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); + ad = (void *)((uint8_t *)ad + 16); + } + if (adlen & 15) { + uint64_t tmp[2] = {0}; /* 16 byte pad */ + fio_memcpy15x(tmp, ad, adlen); + fio___poly_consume128bit(&pl, (uint8_t *)tmp, 1); + } + fio___poly_consume_msg(&pl, (uint8_t *)data, (len & (~15ULL))); + if ((len & 15)) { + fio_u128 dest = {0}; /* 16 byte pad */ + fio_memcpy15x(dest.u64, (uint8_t *)data + (len & (~15ULL)), len); + fio___poly_consume128bit(&pl, (uint8_t *)(dest.u64), 1); } -#ifdef DEBUG { - FIO_NAME(FIO_STR_NAME, s) tmp = {0}; - FIO_ASSERT(!FIO_MEMCMP(&tmp, s, sizeof(tmp)), - "new " FIO_MACRO2STR( - FIO_NAME(FIO_STR_NAME, s)) " object not initialized!"); + uint64_t mac_data[2] = {fio_ltole64(adlen), fio_ltole64(len)}; + fio___poly_consume128bit(&pl, (uint8_t *)mac_data, 1); } -#endif - return (FIO_STR_PTR)FIO_PTR_TAG(s); + fio___poly_finilize(&pl); + fio_u2buf64_le(mac, pl.a[0]); + fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); } -/** Destroys the string and frees the container (if allocated with `new`). */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, free)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (!FIO_STR_IS_SMALL(s) && FIO_STR_BIG_IS_DYNAMIC(s)) { - FIO_STR_BIG_FREE_BUF(s); - } - FIO_NAME(FIO_STR_NAME, __object_free)(s); +SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, + void *restrict data, + size_t len, + const void *ad, /* additional data */ + size_t adlen, + const void *key, + const void *nounce) { + uint64_t auth[2]; + fio_chacha20_poly1305_auth(&auth, data, len, ad, adlen, key, nounce); + if (((auth[0] ^ fio_buf2u64u(mac)) | + (auth[1] ^ fio_buf2u64u(((char *)mac + 8))))) + return -1; + fio_chacha20(data, len, key, nounce, 1); + return 0; } +/* ***************************************************************************** +Module Cleanup +***************************************************************************** +*/ -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_CHACHA +#endif /* FIO_CHACHA */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SHA1 /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -/** - * Frees the String's resources and reinitializes the container. - * - * Note: if the container isn't allocated on the stack, it should be freed - * separately using the appropriate `free` function. - */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, destroy)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (!FIO_STR_IS_SMALL(s) && FIO_STR_BIG_IS_DYNAMIC(s)) { - FIO_STR_BIG_FREE_BUF(s); - } - *s = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT; -} -/** - * Returns a C string with the existing data, re-initializing the String. - * - * Note: the String data is removed from the container, but the container - * isn't freed. - * - * Returns NULL if there's no String data. - */ -FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, detach)(FIO_STR_PTR s_) { - char *data = NULL; - FIO_PTR_TAG_VALID_OR_RETURN(s_, data); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (FIO_STR_IS_SMALL(s)) { - if (FIO_STR_SMALL_LEN(s)) { /* keep these ifs apart */ - fio_str_info_s cpy = - FIO_STR_INFO2(FIO_STR_SMALL_DATA(s), FIO_STR_SMALL_LEN(s)); - FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&cpy, cpy.len); - data = cpy.buf; - } - } else { - if (FIO_STR_BIG_IS_DYNAMIC(s)) { - data = FIO_STR_BIG_DATA(s); - } else if (FIO_STR_BIG_LEN(s)) { - fio_str_info_s cpy = - FIO_STR_INFO2(FIO_STR_BIG_DATA(s), FIO_STR_BIG_LEN(s)); - FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&cpy, cpy.len); - data = cpy.buf; - } - } - *s = (FIO_NAME(FIO_STR_NAME, s)){0}; - return data; -} -/** - * Performs a best attempt at minimizing memory consumption. - * - * Actual effects depend on the underlying memory allocator and it's - * implementation. Not all allocators will free any memory. - */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, compact)(FIO_STR_PTR s_) { -#if FIO_STR_OPTIMIZE4IMMUTABILITY - (void)s_; -#else - FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (FIO_STR_IS_SMALL(s) || !FIO_STR_BIG_IS_DYNAMIC(s) || - fio_string_capa4len(FIO_NAME(FIO_STR_NAME, len)(s_)) >= - FIO_NAME(FIO_STR_NAME, capa)(s_)) - return; - FIO_NAME(FIO_STR_NAME, s) tmp = FIO_STR_INIT; - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - FIO_NAME(FIO_STR_NAME, init_copy) - ((FIO_STR_PTR)FIO_PTR_TAG(&tmp), i.buf, i.len); - FIO_NAME(FIO_STR_NAME, destroy)(s_); - *s = tmp; -#endif -} + SHA 1 + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_SHA1) && !defined(H___FIO_SHA1___H) +#define H___FIO_SHA1___H /* ***************************************************************************** -String Initialization (inline) +SHA 1 ***************************************************************************** */ -/** - * Initializes the container with the provided static / constant string. - * - * The string will be copied to the container **only** if it will fit in the - * container itself. Otherwise, the supplied pointer will be used as is and it - * should remain valid until the string is destroyed. - * - * The final string can be safely be destroyed (using the `destroy` function). - */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_const)(FIO_STR_PTR s_, - const char *str, - size_t len) { - fio_str_info_s i = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(s_, i); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - *s = (FIO_NAME(FIO_STR_NAME, s)){0}; - if (len < FIO_STR_SMALL_CAPA(s)) { - FIO_STR_SMALL_LEN_SET(s, len); - if (len && str) - FIO_MEMCPY(FIO_STR_SMALL_DATA(s), str, len); - FIO_STR_SMALL_DATA(s)[len] = 0; - - i = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), len, FIO_STR_SMALL_CAPA(s)); - return i; - } - FIO_STR_BIG_DATA(s) = (char *)str; - FIO_STR_BIG_LEN_SET(s, len); - FIO_STR_BIG_CAPA_SET(s, len); - FIO_STR_BIG_SET_STATIC(s); - i = FIO_STR_INFO3(FIO_STR_BIG_DATA(s), len, 0); - return i; -} +/** The data type containing the SHA1 digest (result). */ +typedef union { +#ifdef __SIZEOF_INT128__ + __uint128_t align__; +#else + uint64_t align__; +#endif + uint32_t v[5]; + uint8_t digest[20]; +} fio_sha1_s; /** - * Initializes the container with the provided dynamic string. + * A simple, non streaming, implementation of the SHA1 hashing algorithm. * - * The string is always copied and the final string must be destroyed (using the - * `destroy` function). + * Do NOT use - SHA1 is broken... but for some reason some protocols still + * require it's use (i.e., WebSockets), so it's here for your convenience. */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy)(FIO_STR_PTR s_, - const char *str, - size_t len) { - fio_str_info_s i = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(s_, i); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - *s = (FIO_NAME(FIO_STR_NAME, s)){0}; - if (len < FIO_STR_SMALL_CAPA(s)) { - FIO_STR_SMALL_LEN_SET(s, len); - if (len && str) - FIO_MEMCPY(FIO_STR_SMALL_DATA(s), str, len); - FIO_STR_SMALL_DATA(s)[len] = 0; +SFUNC fio_sha1_s fio_sha1(const void *data, uint64_t len); - i = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), len, FIO_STR_SMALL_CAPA(s)); - return i; - } - i = FIO_STR_INFO2((char *)str, len); - FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&i, len); - FIO_STR_BIG_CAPA_SET(s, i.capa); - FIO_STR_BIG_DATA(s) = i.buf; - FIO_STR_BIG_LEN_SET(s, len); - return i; -} +/** Returns the digest length of SHA1 in bytes (20 bytes) */ +FIO_IFUNC size_t fio_sha1_len(void); -/** - * Initializes the container with a copy of an existing String object. - * - * The string is always copied and the final string must be destroyed (using the - * `destroy` function). - */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_STR_PTR dest, - FIO_STR_PTR src) { - fio_str_info_s i; - i = FIO_NAME(FIO_STR_NAME, info)(src); - i = FIO_NAME(FIO_STR_NAME, init_copy)(dest, i.buf, i.len); - return i; -} +/** Returns the 20 Byte long digest of a SHA1 object. */ +FIO_IFUNC uint8_t *fio_sha1_digest(fio_sha1_s *s); /* ***************************************************************************** -String Information (inline) +SHA 1 Implementation - inlined static functions ***************************************************************************** */ -/** Returns a pointer (`char *`) to the String's content. */ -FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, ptr)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, NULL); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - char *results[] = {(FIO_STR_BIG_DATA(s)), (FIO_STR_SMALL_DATA(s))}; - return results[FIO_STR_IS_SMALL(s)]; -} +/** returns the digest length of SHA1 in bytes */ +FIO_IFUNC size_t fio_sha1_len(void) { return 20; } -/** Returns the String's length in bytes. */ -FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, len)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - size_t results[] = {(FIO_STR_BIG_LEN(s)), (FIO_STR_SMALL_LEN(s))}; - return results[FIO_STR_IS_SMALL(s)]; -} +/** returns the digest of a SHA1 object. */ +FIO_IFUNC uint8_t *fio_sha1_digest(fio_sha1_s *s) { return s->digest; } -/** Returns the String's existing capacity (total used & available memory). */ -FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, capa)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - if (FIO_STR_IS_SMALL(s)) - return FIO_STR_SMALL_CAPA(s); - if (FIO_STR_BIG_IS_DYNAMIC(s)) - return FIO_STR_BIG_CAPA(s); - return 0; -} - -/** - * Sets the new String size without reallocating any memory (limited by - * existing capacity). - */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, resize)(FIO_STR_PTR s_, - size_t size) { - fio_str_info_s i = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(s_, i); - i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) { - return i; - } - /* resize may be used to reserve memory in advance while setting size */ - if (i.capa > size) { - i.len = size; - i.buf[i.len] = 0; - } else { - fio_string_write(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - NULL, - size - i.len); - } - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - return i; -} +FIO_IFUNC void fio___sha1_round512(uint32_t *old, /* state */ + uint32_t *w /* 16 words */) { +#if FIO___HAS_ARM_INTRIN + /* Code adjusted from: + * https://github.com/noloader/SHA-Intrinsics/blob/master/sha1-arm.c + * Credit to Jeffrey Walton. + */ + uint32x4_t w0, w1, w2, w3; + uint32x4_t t0, t1, v0, v_old; + uint32_t e0, e1, e_old; + e0 = e_old = old[4]; + v_old = vld1q_u32(old); + v0 = v_old; -/** - * Prevents further manipulations to the String's content. - */ -FIO_IFUNC void FIO_NAME(FIO_STR_NAME, freeze)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - FIO_STR_FREEZE_(s); -} + /* load to vectors */ + w0 = vld1q_u32(w); + w1 = vld1q_u32(w + 4); + w2 = vld1q_u32(w + 8); + w3 = vld1q_u32(w + 12); + /* make little endian */ + w0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w0))); + w1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w1))); + w2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w2))); + w3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w3))); -/** - * Returns true if the string is frozen. - */ -FIO_IFUNC uint8_t FIO_NAME_BL(FIO_STR_NAME, frozen)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, 1); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - return FIO_STR_IS_FROZEN(s); -} + t0 = vaddq_u32(w0, vdupq_n_u32(0x5A827999)); + t1 = vaddq_u32(w1, vdupq_n_u32(0x5A827999)); -/** Returns 1 if memory was allocated and (the String must be destroyed). */ -FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, allocated)(const FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - return (!FIO_STR_IS_SMALL(s) & FIO_STR_BIG_IS_DYNAMIC(s)); -} + /* round: 0-3 */ + e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1cq_u32(v0, e0, t0); + t0 = vaddq_u32(w2, vdupq_n_u32(0x5A827999)); + w0 = vsha1su0q_u32(w0, w1, w2); -/** - * Binary comparison returns `1` if both strings are equal and `0` if not. - */ -FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, eq)(const FIO_STR_PTR str1_, - const FIO_STR_PTR str2_) { - if (str1_ == str2_) - return 1; - FIO_PTR_TAG_VALID_OR_RETURN(str1_, 0); - FIO_PTR_TAG_VALID_OR_RETURN(str2_, 0); - fio_buf_info_s s1 = FIO_NAME(FIO_STR_NAME, buf)(str1_); - fio_buf_info_s s2 = FIO_NAME(FIO_STR_NAME, buf)(str2_); - return FIO_BUF_INFO_IS_EQ(s1, s2); -} + /* round: 4-7 */ + e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1cq_u32(v0, e1, t1); + t1 = vaddq_u32(w3, vdupq_n_u32(0x5A827999)); + w0 = vsha1su1q_u32(w0, w3); + w1 = vsha1su0q_u32(w1, w2, w3); -/** - * Returns the string's Risky Hash value. - * - * Note: Hash algorithm might change without notice. - */ -FIO_IFUNC uint64_t FIO_NAME(FIO_STR_NAME, hash)(const FIO_STR_PTR s_, - uint64_t seed) { - fio_buf_info_s i = FIO_NAME(FIO_STR_NAME, buf)(s_); - return fio_risky_hash((void *)i.buf, i.len, seed); -} + /* round: 8-11 */ + e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1cq_u32(v0, e0, t0); + t0 = vaddq_u32(w0, vdupq_n_u32(0x5A827999)); + w1 = vsha1su1q_u32(w1, w0); + w2 = vsha1su0q_u32(w2, w3, w0); -/* ***************************************************************************** -String API - Content Manipulation and Review (inline) -***************************************************************************** */ +#define FIO_SHA1_ROUND_(K, rn_fn, n, ni, n0, n1, n2, n3) \ + e##n = vsha1h_u32(vgetq_lane_u32(v0, 0)); \ + v0 = rn_fn(v0, e##ni, t##ni); \ + t##ni = vaddq_u32(w##n1, vdupq_n_u32(K)); \ + w##n2 = vsha1su1q_u32(w##n2, w##n1); \ + w##n3 = vsha1su0q_u32(w##n3, w##n0, w##n1); + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1cq_u32, 0, 1, 0, 1, 2, 3) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1cq_u32, 1, 0, 1, 2, 3, 0) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 0, 1, 2, 3, 0, 1) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 1, 0, 3, 0, 1, 2) + FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 0, 1, 0, 1, 2, 3) -/** Writes data at the end of the String. */ -FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write)(FIO_STR_PTR s_, - const void *src, - size_t len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), src, len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1pq_u32, 1, 0, 1, 2, 3, 0) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1pq_u32, 0, 1, 2, 3, 0, 1) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 1, 0, 3, 0, 1, 2) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 0, 1, 0, 1, 2, 3) + FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 1, 0, 1, 2, 3, 0) -/* ***************************************************************************** + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1mq_u32, 0, 1, 2, 3, 0, 1) + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1mq_u32, 1, 0, 3, 0, 1, 2) + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1pq_u32, 0, 1, 0, 1, 2, 3) + FIO_SHA1_ROUND_(0xCA62C1D6, vsha1pq_u32, 1, 0, 1, 2, 3, 0) +#undef FIO_SHA1_ROUND_ + /* round: 68-71 */ + e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1pq_u32(v0, e1, t1); + t1 = vaddq_u32(w3, vdupq_n_u32(0xCA62C1D6)); + w0 = vsha1su1q_u32(w0, w3); + /* round: 72-75 */ + e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1pq_u32(v0, e0, t0); - String Implementation + /* round: 76-79 */ + e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); + v0 = vsha1pq_u32(v0, e1, t1); - IMPLEMENTATION + /* combine and store */ + e0 += e_old; + v0 = vaddq_u32(v_old, v0); + vst1q_u32(old, v0); + old[4] = e0; +#else /* !FIO___HAS_ARM_INTRIN portable implementation */ -***************************************************************************** */ + uint32_t v[8] = {0}; /* copy old state to new + reserve registers (8 not 6) */ + for (size_t i = 0; i < 5; ++i) + v[i] = old[i]; -/* ***************************************************************************** -External functions -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + for (size_t i = 0; i < 16; ++i) /* convert read buffer to Big Endian */ + w[i] = fio_ntol32(w[i]); -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_STR_NAME, s)) -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_STR_NAME, destroy)) +#define FIO___SHA1_ROUND4(K, F, i) \ + FIO___SHA1_ROUND((K), (F), i); \ + FIO___SHA1_ROUND((K), (F), i + 1); \ + FIO___SHA1_ROUND((K), (F), i + 2); \ + FIO___SHA1_ROUND((K), (F), i + 3); +#define FIO___SHA1_ROUND16(K, F, i) \ + FIO___SHA1_ROUND4((K), (F), i); \ + FIO___SHA1_ROUND4((K), (F), i + 4); \ + FIO___SHA1_ROUND4((K), (F), i + 8); \ + FIO___SHA1_ROUND4((K), (F), i + 12); +#define FIO___SHA1_ROUND20(K, F, i) \ + FIO___SHA1_ROUND16(K, F, i); \ + FIO___SHA1_ROUND4((K), (F), i + 16); -/* ***************************************************************************** -String Core Callbacks - Memory management -***************************************************************************** */ -SFUNC FIO_NAME(FIO_STR_NAME, s) * FIO_NAME(FIO_STR_NAME, __object_new)(void) { - FIO_NAME(FIO_STR_NAME, s) *r = - (FIO_NAME(FIO_STR_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, (sizeof(*r)), 0); - if (r) - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, s)); - return r; -} -SFUNC void FIO_NAME(FIO_STR_NAME, - __object_free)(FIO_NAME(FIO_STR_NAME, s) * s) { - if (!s) - return; - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, s)); - FIO_MEM_FREE_(s, sizeof(*s)); -} +#define FIO___SHA1_ROTATE_OLD(K, F, i) \ + v[5] = fio_lrot32(v[0], 5) + v[4] + F + (uint32_t)K + w[(i)&15]; \ + v[4] = v[3]; \ + v[3] = v[2]; \ + v[2] = fio_lrot32(v[1], 30); \ + v[1] = v[0]; \ + v[0] = v[5]; -SFUNC int FIO_NAME(FIO_STR_NAME, __default_reallocate)(fio_str_info_s *dest, - size_t new_capa) { - new_capa = fio_string_capa4len(new_capa); - void *tmp = FIO_MEM_REALLOC_(dest->buf, dest->capa, new_capa, dest->len); - if (!tmp) - return -1; - if (!dest->buf) - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, destroy)); - dest->capa = new_capa; - dest->buf = (char *)tmp; - return 0; -} -SFUNC int FIO_NAME(FIO_STR_NAME, - __default_copy_and_reallocate)(fio_str_info_s *dest, - size_t new_capa) { - if (dest->len && new_capa < dest->len) - new_capa = dest->len; - new_capa = fio_string_capa4len(new_capa); - void *tmp = FIO_MEM_REALLOC_(NULL, 0, new_capa, 0); - if (!tmp) - return -1; - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, destroy)); - if (dest->len) - FIO_MEMCPY(tmp, dest->buf, dest->len); - ((char *)tmp)[dest->len] = 0; - dest->capa = new_capa; - dest->buf = (char *)tmp; - return 0; -} -SFUNC void FIO_NAME(FIO_STR_NAME, __default_free)(void *ptr, size_t capa) { - if (!ptr) - return; - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, destroy)); - FIO_MEM_FREE_(ptr, capa); - (void)capa; /* if unused */ -} -SFUNC void FIO_NAME(FIO_STR_NAME, __default_free_noop)(void *str) { (void)str; } -SFUNC void FIO_NAME(FIO_STR_NAME, __default_free_noop2)(fio_str_info_s str) { - (void)str; -} +#define FIO___SHA1_ROTATE(K, F, i) \ + v[5] = fio_lrot32(v[0], 5) + v[4] + F + (uint32_t)K + w[(i)&15]; \ + v[1] = fio_lrot32(v[1], 30); \ + fio_u32x8_reshuffle(v, 5, 0, 1, 2, 3, 5, 6, 7); -/* ***************************************************************************** -String Implementation - Memory management -***************************************************************************** */ +#define FIO___SHA1_CALC_WORD(i) \ + fio_lrot32( \ + (w[(i + 13) & 15] ^ w[(i + 8) & 15] ^ w[(i + 2) & 15] ^ w[(i)&15]), \ + 1); -/** Frees the pointer returned by `detach`. */ -SFUNC void FIO_NAME(FIO_STR_NAME, dealloc)(void *ptr) { - if (!ptr) - return; - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, destroy)); - FIO_MEM_FREE_(ptr, -1); -} +#define FIO___SHA1_ROUND(K, F, i) FIO___SHA1_ROTATE(K, F, i); + /* perform first 16 rounds with simple words as copied from data */ + FIO___SHA1_ROUND16(0x5A827999, ((v[1] & v[2]) | ((~v[1]) & (v[3]))), 0); -/** - * Reserves at least `amount` of bytes for the string's data. - * - * Returns the current state of the String. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - FIO_STR_RESERVE_NAME)(FIO_STR_PTR s_, - size_t amount) { - fio_str_info_s state = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(s_, state); - FIO_NAME(FIO_STR_NAME, s) *const s = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); - state = FIO_NAME(FIO_STR_NAME, info)(s_); - if (FIO_STR_IS_FROZEN(s)) - return state; - amount += state.len; - if (state.capa <= amount) { - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_)(&state, amount); - state.buf[state.len] = 0; - FIO_NAME(FIO_STR_NAME, __info_update)(s_, state); - } else if (state.capa > FIO_STR_SMALL_CAPA(s) && - amount <= FIO_STR_SMALL_CAPA(s) && - state.len <= FIO_STR_SMALL_CAPA(s)) { - FIO_NAME(FIO_STR_NAME, s) tmp; - state = FIO_NAME(FIO_STR_NAME, init_copy)((FIO_STR_PTR)FIO_PTR_TAG(&tmp), - state.buf, - state.len); - FIO_NAME(FIO_STR_NAME, destroy)(s_); - *s = tmp; - } - return state; -} +/* change round definition so now we compute the word's value per round */ +#undef FIO___SHA1_ROUND +#define FIO___SHA1_ROUND(K, F, i) \ + w[(i)&15] = FIO___SHA1_CALC_WORD(i); \ + FIO___SHA1_ROTATE(K, F, i); -/* ***************************************************************************** -String Implementation - UTF-8 State -***************************************************************************** */ + /* complete last 4 round from the first 20 round group */ + FIO___SHA1_ROUND4(0x5A827999, ((v[1] & v[2]) | ((~v[1]) & (v[3]))), 16); -/** Returns 1 if the String is UTF-8 valid and 0 if not. */ -SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_valid)(FIO_STR_PTR s_) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); - fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); - return fio_string_utf8_len(state); -} + /* remaining 20 round groups */ + FIO___SHA1_ROUND20(0x6ED9EBA1, (v[1] ^ v[2] ^ v[3]), 20); + FIO___SHA1_ROUND20(0x8F1BBCDC, ((v[1] & (v[2] | v[3])) | (v[2] & v[3])), 40); + FIO___SHA1_ROUND20(0xCA62C1D6, (v[1] ^ v[2] ^ v[3]), 60); + /* sum and store */ + for (size_t i = 0; i < 5; ++i) + old[i] += v[i]; -/** Returns the String's length in UTF-8 characters. */ -SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_len)(FIO_STR_PTR s_) { - fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); - return fio_string_utf8_len(state); +#undef FIO___SHA1_ROTATE +#undef FIO___SHA1_ROTATE_OLD +#undef FIO___SHA1_CALC_WORD +#undef FIO___SHA1_ROUND +#undef FIO___SHA1_ROUND4 +#undef FIO___SHA1_ROUND16 +#undef FIO___SHA1_ROUND20 +#endif /* FIO___HAS_ARM_INTRIN */ } - /** - * Takes a UTF-8 character selection information (UTF-8 position and length) - * and updates the same variables so they reference the raw byte slice - * information. - * - * If the String isn't UTF-8 valid up to the requested selection, than `pos` - * will be updated to `-1` otherwise values are always positive. - * - * The returned `len` value may be shorter than the original if there wasn't - * enough data left to accommodate the requested length. When a `len` value of - * `0` is returned, this means that `pos` marks the end of the String. + * A simple, non streaming, implementation of the SHA1 hashing algorithm. * - * Returns -1 on error and 0 on success. + * Do NOT use - SHA1 is broken... but for some reason some protocols still + * require it's use (i.e., WebSockets), so it's here for your convinience. */ -SFUNC int FIO_NAME(FIO_STR_NAME, - utf8_select)(FIO_STR_PTR s_, intptr_t *pos, size_t *len) { - FIO_PTR_TAG_VALID_OR_RETURN(s_, -1); - fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); - return fio_string_utf8_select(state, pos, len); -} +SFUNC fio_sha1_s fio_sha1(const void *data, uint64_t len) { + fio_sha1_s s FIO_ALIGN(16) = {.v = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }}; + uint32_t vec[16] FIO_ALIGN(16); -/* ***************************************************************************** -String Implementation - Content Manipulation and Review -***************************************************************************** */ + const uint8_t *buf = (const uint8_t *)data; -/** - * Writes a number at the end of the String using normal base 10 notation. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_i)(FIO_STR_PTR s_, - int64_t num) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_i(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + for (size_t i = 63; i < len; i += 64) { + fio_memcpy64(vec, buf); + fio___sha1_round512(s.v, vec); + buf += 64; + } + for (size_t i = 0; i < 16; ++i) { + vec[i] = 0; + } + if ((len & 63)) { + uint32_t tbuf[16] = {0}; + fio_memcpy63x(tbuf, buf, len); + fio_memcpy64(vec, tbuf); + } + ((uint8_t *)vec)[(len & 63)] = 0x80; -/** - * Writes a number at the end of the String using Hex (base 16) notation. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_hex)(FIO_STR_PTR s_, - int64_t num) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_hex(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; + if ((len & 63) > 55) { + fio___sha1_round512(s.v, vec); + for (size_t i = 0; i < 16; ++i) { + vec[i] = 0; + } + } + len <<= 3; + len = fio_lton64(len); + vec[14] = (uint32_t)(len & 0xFFFFFFFF); + vec[15] = (uint32_t)(len >> 32); + fio___sha1_round512(s.v, vec); + for (size_t i = 0; i < 5; ++i) { + s.v[i] = fio_ntol32(s.v[i]); + } + return s; } -/** - * Writes a number at the end of the String using binary notation. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_bin)(FIO_STR_PTR s_, - int64_t num) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_bin(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} +/** HMAC-SHA1, resulting in a 20 byte authentication code. */ +SFUNC fio_sha1_s fio_sha1_hmac(const void *key, + uint64_t key_len, + const void *msg, + uint64_t msg_len) { + fio_sha1_s inner FIO_ALIGN(16) = {.v = + { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }}, + outer FIO_ALIGN(16) = {.v = { + 0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0, + }}; + fio_u512 v = fio_u512_init64(0), k = fio_u512_init64(0); + const uint8_t *buf = (const uint8_t *)msg; -/** - * Appends the `src` String to the end of the `dest` String. - * - * If `dest` is empty, the resulting Strings will be equal. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, concat)(FIO_STR_PTR dest_, - FIO_STR_PTR const src_) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(dest_); - if (!i.capa) - return i; - FIO_PTR_TAG_VALID_OR_RETURN(src_, i); - fio_str_info_s src = FIO_NAME(FIO_STR_NAME, info)(src_); - if (!src.len) - return i; - fio_string_write(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(dest_), - src.buf, - src.len); - FIO_NAME(FIO_STR_NAME, __info_update)(dest_, i); - return i; -} + /* copy key */ + if (key_len > 64) + goto key_too_long; + if (key_len == 64) + fio_memcpy64(k.u8, key); + else + fio_memcpy63x(k.u8, key, key_len); + /* prepare inner key */ + for (size_t i = 0; i < 8; ++i) + k.u64[i] ^= (uint64_t)0x3636363636363636ULL; -/** - * Replaces the data in the String - replacing `old_len` bytes starting at - * `start_pos`, with the data at `src` (`src_len` bytes long). - * - * Negative `start_pos` values are calculated backwards, `-1` == end of - * String. - * - * When `old_len` is zero, the function will insert the data at `start_pos`. - * - * If `src_len == 0` than `src` will be ignored and the data marked for - * replacement will be erased. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, replace)(FIO_STR_PTR s_, - intptr_t start_pos, - size_t old_len, - const void *src, - size_t src_len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_replace(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - start_pos, - old_len, - src, - src_len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + /* hash inner key block */ + fio___sha1_round512(inner.v, k.u32); + /* consume data */ + for (size_t i = 63; i < msg_len; i += 64) { + fio_memcpy64(v.u8, buf); + fio___sha1_round512(inner.v, v.u32); + buf += 64; + } + /* finalize temporary hash */ + if ((msg_len & 63)) { + v = fio_u512_init64(0); + fio_memcpy63x(v.u8, buf, msg_len); + } + v.u8[(msg_len & 63)] = 0x80; + if ((msg_len & 63) > 55) { + fio___sha1_round512(inner.v, v.u32); + v = fio_u512_init64(0); + } + msg_len += 64; /* add the 64 byte inner key to the length count */ + msg_len <<= 3; + msg_len = fio_lton64(msg_len); + v.u32[14] = (uint32_t)(msg_len & 0xFFFFFFFFUL); + v.u32[15] = (uint32_t)(msg_len >> 32); + fio___sha1_round512(inner.v, v.u32); + for (size_t i = 0; i < 5; ++i) + inner.v[i] = fio_ntol32(inner.v[i]); -/** - * Writes a number at the end of the String using binary notation. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - __write2)(FIO_STR_PTR s_, - const fio_string_write_s srcs[]) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write2 FIO_NOOP(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - srcs); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + /* switch key to outer key */ + for (size_t i = 0; i < 8; ++i) + k.u64[i] ^= + ((uint64_t)0x3636363636363636ULL ^ (uint64_t)0x5C5C5C5C5C5C5C5CULL); -/** - * Writes to the String using a vprintf like interface. - * - * Data is written to the end of the String. - */ -SFUNC fio_str_info_s FIO___PRINTF_STYLE(2, 0) - FIO_NAME(FIO_STR_NAME, - vprintf)(FIO_STR_PTR s_, const char *format, va_list argv) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_vprintf(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - format, - argv); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + /* hash outer key block */ + fio___sha1_round512(outer.v, k.u32); + /* hash inner (temporary) hash result and finalize */ + v = fio_u512_init64(0); + for (size_t i = 0; i < 5; ++i) + v.u32[i] = inner.v[i]; + v.u8[20] = 0x80; + msg_len = ((64U + 20U) << 3); + msg_len = fio_lton64(msg_len); + v.u32[14] = (uint32_t)(msg_len & 0xFFFFFFFF); + v.u32[15] = (uint32_t)(msg_len >> 32); + fio___sha1_round512(outer.v, v.u32); + for (size_t i = 0; i < 5; ++i) + outer.v[i] = fio_ntol32(outer.v[i]); -/** - * Writes to the String using a printf like interface. - * - * Data is written to the end of the String. - */ -SFUNC fio_str_info_s FIO___PRINTF_STYLE(2, 3) - FIO_NAME(FIO_STR_NAME, printf)(FIO_STR_PTR s_, const char *format, ...) { - va_list argv; - va_start(argv, format); - fio_str_info_s state = FIO_NAME(FIO_STR_NAME, vprintf)(s_, format, argv); - va_end(argv); - return state; -} + return outer; +key_too_long: + inner = fio_sha1(key, key_len); + return fio_sha1_hmac(inner.digest, 20, msg, msg_len); +} /* ***************************************************************************** -String API - C / JSON escaping +Module Cleanup ***************************************************************************** */ -/** - * Writes data at the end of the String, escaping the data using JSON semantics. - * - * The JSON semantic are common to many programming languages, promising a UTF-8 - * String while making it easy to read and copy the string during debugging. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_escape)(FIO_STR_PTR s_, - const void *src, - size_t len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_escape(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - src, - len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} - -/** - * Writes an escaped data into the string after unescaping the data. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_unescape)(FIO_STR_PTR s_, - const void *src, - size_t len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_unescape(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - src, - len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} - +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_SHA1 */ +#undef FIO_SHA1 +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_SHA2 /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ /* ***************************************************************************** -String - Base64 support -***************************************************************************** */ -/** - * Writes data at the end of the String, encoding the data as Base64 encoded - * data. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - write_base64enc)(FIO_STR_PTR s_, - const void *data, - size_t len, - uint8_t url_encoded) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_base64enc(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - data, - len, - url_encoded); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} -/** - * Writes decoded base64 data to the end of the String. - */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - write_base64dec)(FIO_STR_PTR s_, - const void *encoded_, - size_t len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_base64dec(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - encoded_, - len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} -/* ***************************************************************************** -String API - HTML escaping support -***************************************************************************** */ -/** Writes HTML escaped data to a String. */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_html_escape)(FIO_STR_PTR s_, - const void *data, - size_t len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_html_escape(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - data, - len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + SHA 2 + SHA-256 / SHA-512 and variations -/** Writes HTML un-escaped data to a String - incomplete and minimal. */ -IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, - write_html_unescape)(FIO_STR_PTR s_, - const void *data, - size_t len) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_write_html_unescape(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - data, - len); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_SHA2) && !defined(H___FIO_SHA2___H) +#define H___FIO_SHA2___H /* ***************************************************************************** -String - read file +SHA 2 API ***************************************************************************** */ -/** - * Reads data from a file descriptor `fd` at offset `start_at` and pastes it's - * contents (or a slice of it) at the end of the String. If `limit == 0`, than - * the data will be read until EOF. - * - * The file should be a regular file or the operation might fail (can't be used - * for sockets). - * - * The file descriptor will remain open and should be closed manually. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfd)(FIO_STR_PTR s_, - int fd, - intptr_t start_at, - intptr_t limit) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_readfd(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - fd, - start_at, - limit); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} +/** Streaming SHA-256 type. */ +typedef struct { + fio_u256 hash; + fio_u512 cache; + uint64_t total_len; +} fio_sha256_s; -/** - * Opens the file `filename` and pastes it's contents (or a slice ot it) at - * the end of the String. If `limit == 0`, than the data will be read until - * EOF. - * - * If the file can't be located, opened or read, or if `start_at` is beyond - * the EOF position, NULL is returned in the state's `data` field. - */ -SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfile)(FIO_STR_PTR s_, - const char *filename, - intptr_t start_at, - intptr_t limit) { - fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); - if (!i.capa) - return i; - fio_string_readfile(&i, - FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), - filename, - start_at, - limit); - FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - return i; -} +/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ +FIO_IFUNC fio_u256 fio_sha256(const void *data, uint64_t len); -/* ***************************************************************************** +/** initializes a fio_u256 so the hash can consume streaming data. */ +FIO_IFUNC fio_sha256_s fio_sha256_init(void); +/** Feed data into the hash */ +SFUNC void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len); +/** finalizes a fio_u256 with the SHA 256 hash. */ +SFUNC fio_u256 fio_sha256_finalize(fio_sha256_s *h); +/** Streaming SHA-512 type. */ +typedef struct { + fio_u512 hash; + fio_u1024 cache; + uint64_t total_len; +} fio_sha512_s; - String Test +/** A simple, non streaming, implementation of the SHA-512 hashing algorithm. */ +FIO_IFUNC fio_u512 fio_sha512(const void *data, uint64_t len); +/** initializes a fio_u512 so the hash can consume streaming data. */ +FIO_IFUNC fio_sha512_s fio_sha512_init(void); +/** Feed data into the hash */ +SFUNC void fio_sha512_consume(fio_sha512_s *h, const void *data, uint64_t len); +/** finalizes a fio_u512 with the SHA 512 hash. */ +SFUNC fio_u512 fio_sha512_finalize(fio_sha512_s *h); +/* ***************************************************************************** +Implementation - static / inline functions. ***************************************************************************** */ -#ifdef FIO_STR_WRITE_TEST_FUNC - -/** - * Tests the fio_str functionality. - */ -SFUNC void FIO_NAME_TEST(stl, FIO_STR_NAME)(void) { - FIO_NAME(FIO_STR_NAME, s) str = {0}; /* test zeroed out memory */ -#define FIO__STR_SMALL_CAPA FIO_STR_SMALL_CAPA(&str) - FIO_STR_PTR pstr = FIO_PTR_TAG((&str)); - fprintf( - stderr, - "* Testing core string features for " FIO_MACRO2STR(FIO_STR_NAME) ".\n"); - fprintf(stderr, - "* String container size (without wrapper): %zu\n", - sizeof(FIO_NAME(FIO_STR_NAME, s))); - fprintf(stderr, - "* Self-contained capacity (FIO_STR_SMALL_CAPA): %zu\n", - FIO__STR_SMALL_CAPA); - FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, frozen)(pstr), "new string is frozen"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == FIO__STR_SMALL_CAPA, - "small string capacity returned %zu", - FIO_NAME(FIO_STR_NAME, capa)(pstr)); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 0, - "small string length reporting error!"); - FIO_ASSERT( - FIO_NAME(FIO_STR_NAME, ptr)(pstr) == ((char *)(&str) + 1), - "small string pointer reporting error (%zd offset)!", - (ssize_t)(((char *)(&str) + 1) - FIO_NAME(FIO_STR_NAME, ptr)(pstr))); - FIO_NAME(FIO_STR_NAME, write)(pstr, "World", 4); - FIO_ASSERT(FIO_STR_IS_SMALL(&str), - "small string writing error - not small on small write!"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == FIO__STR_SMALL_CAPA, - "Small string capacity reporting error after write!"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 4, - "small string length reporting error after write!"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == (char *)&str + 1, - "small string pointer reporting error after write!"); - FIO_ASSERT(!FIO_NAME(FIO_STR_NAME, ptr)(pstr)[4] && - FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr)) == 4, - "small string NUL missing after write (%zu)!", - FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr))); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Worl"), - "small string write error (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == - FIO_NAME(FIO_STR_NAME, info)(pstr).buf, - "small string `data` != `info.buf` (%p != %p)", - (void *)FIO_NAME(FIO_STR_NAME, ptr)(pstr), - (void *)FIO_NAME(FIO_STR_NAME, info)(pstr).buf); - - FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME) - (pstr, sizeof(FIO_NAME(FIO_STR_NAME, s))); - FIO_ASSERT(!FIO_STR_IS_SMALL(&str), - "Long String reporting as small after capacity update!"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) >= - sizeof(FIO_NAME(FIO_STR_NAME, s)) - 1, - "Long String capacity update error (%zu != %zu)!", - FIO_NAME(FIO_STR_NAME, capa)(pstr), - FIO_STR_SMALL_CAPA(&str)); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == - FIO_NAME(FIO_STR_NAME, info)(pstr).buf, - "Long String `ptr` !>= " - "`cstr(s).buf` (%p != %p)", - (void *)FIO_NAME(FIO_STR_NAME, ptr)(pstr), - (void *)FIO_NAME(FIO_STR_NAME, info)(pstr).buf); +/** initializes a fio_u256 so the hash can be consumed. */ +FIO_IFUNC fio_sha256_s fio_sha256_init(void) { + fio_sha256_s h = {.hash.u32 = {0x6A09E667ULL, + 0xBB67AE85ULL, + 0x3C6EF372ULL, + 0xA54FF53AULL, + 0x510E527FULL, + 0x9B05688CULL, + 0x1F83D9ABULL, + 0x5BE0CD19ULL}}; + return h; +} -#if FIO_STR_OPTIMIZE4IMMUTABILITY - /* immutable string length is updated after `reserve` to reflect new capa */ - FIO_NAME(FIO_STR_NAME, resize)(pstr, 4); -#endif - FIO_ASSERT( - FIO_NAME(FIO_STR_NAME, len)(pstr) == 4, - "Long String length changed during conversion from small string (%zu)!", - FIO_NAME(FIO_STR_NAME, len)(pstr)); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == str.buf, - "Long String pointer reporting error after capacity update!"); - FIO_ASSERT(FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr)) == 4, - "Long String NUL missing after capacity update (%zu)!", - FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr))); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Worl"), - "Long String value changed after capacity update (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); +/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ +FIO_IFUNC fio_u256 fio_sha256(const void *data, uint64_t len) { + fio_sha256_s h = fio_sha256_init(); + fio_sha256_consume(&h, data, len); + return fio_sha256_finalize(&h); +} - FIO_NAME(FIO_STR_NAME, write)(pstr, "d!", 2); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "World!"), - "Long String `write` error (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); +/** initializes a fio_u256 so the hash can be consumed. */ +FIO_IFUNC fio_sha512_s fio_sha512_init(void) { + fio_sha512_s h = {.hash.u64 = {0x6A09E667F3BCC908ULL, + 0xBB67AE8584CAA73BULL, + 0x3C6EF372FE94F82BULL, + 0xA54FF53A5F1D36F1ULL, + 0x510E527FADE682D1ULL, + 0x9B05688C2B3E6C1FULL, + 0x1F83D9ABFB41BD6BULL, + 0x5BE0CD19137E2179ULL}}; + return h; +} - FIO_NAME(FIO_STR_NAME, replace)(pstr, 0, 0, "Hello ", 6); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello World!"), - "Long String `insert` error (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); +/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ +FIO_IFUNC fio_u512 fio_sha512(const void *data, uint64_t len) { + fio_sha512_s h = fio_sha512_init(); + fio_sha512_consume(&h, data, len); + return fio_sha512_finalize(&h); +} - FIO_NAME(FIO_STR_NAME, resize)(pstr, 6); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello "), - "Long String `resize` clipping error (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - FIO_NAME(FIO_STR_NAME, replace)(pstr, 6, 0, "My World!", 9); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello My World!"), - "Long String `replace` error when testing overflow (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); +/* ***************************************************************************** +Implementation - SHA-256 +***************************************************************************** */ - FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME) - (pstr, FIO_NAME(FIO_STR_NAME, len)(pstr)); /* may truncate */ +FIO_IFUNC void fio___sha256_round(fio_u256 *h, const uint8_t *block) { + const uint32_t sha256_consts[64] = { + 0x428A2F98ULL, 0x71374491ULL, 0xB5C0FBCFULL, 0xE9B5DBA5ULL, 0x3956C25BULL, + 0x59F111F1ULL, 0x923F82A4ULL, 0xAB1C5ED5ULL, 0xD807AA98ULL, 0x12835B01ULL, + 0x243185BEULL, 0x550C7DC3ULL, 0x72BE5D74ULL, 0x80DEB1FEULL, 0x9BDC06A7ULL, + 0xC19BF174ULL, 0xE49B69C1ULL, 0xEFBE4786ULL, 0x0FC19DC6ULL, 0x240CA1CCULL, + 0x2DE92C6FULL, 0x4A7484AAULL, 0x5CB0A9DCULL, 0x76F988DAULL, 0x983E5152ULL, + 0xA831C66DULL, 0xB00327C8ULL, 0xBF597FC7ULL, 0xC6E00BF3ULL, 0xD5A79147ULL, + 0x06CA6351ULL, 0x14292967ULL, 0x27B70A85ULL, 0x2E1B2138ULL, 0x4D2C6DFCULL, + 0x53380D13ULL, 0x650A7354ULL, 0x766A0ABBULL, 0x81C2C92EULL, 0x92722C85ULL, + 0xA2BFE8A1ULL, 0xA81A664BULL, 0xC24B8B70ULL, 0xC76C51A3ULL, 0xD192E819ULL, + 0xD6990624ULL, 0xF40E3585ULL, 0x106AA070ULL, 0x19A4C116ULL, 0x1E376C08ULL, + 0x2748774CULL, 0x34B0BCB5ULL, 0x391C0CB3ULL, 0x4ED8AA4AULL, 0x5B9CCA4FULL, + 0x682E6FF3ULL, 0x748F82EEULL, 0x78A5636FULL, 0x84C87814ULL, 0x8CC70208ULL, + 0x90BEFFFAULL, 0xA4506CEBULL, 0xBEF9A3F7ULL, 0xC67178F2ULL}; - FIO_NAME(FIO_STR_NAME, replace)(pstr, -10, 2, "Big", 3); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World!"), - "Long String `replace` error when testing splicing (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + uint32_t v[8]; + for (size_t i = 0; i < 8; ++i) { + v[i] = h->u32[i]; + } + /* read data as an array of 16 big endian 32 bit integers. */ + uint32_t w[16] FIO_ALIGN(16); + fio_memcpy64(w, block); + for (size_t i = 0; i < 16; ++i) { + w[i] = fio_lton32(w[i]); /* no-op on big endien systems */ + } - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == - fio_string_capa4len(FIO_STRLEN("Hello Big World!")) || - !FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), - "Long String `replace` capacity update error " - "(%zu >=? %zu)!", - FIO_NAME(FIO_STR_NAME, capa)(pstr), - fio_string_capa4len(FIO_STRLEN("Hello Big World!"))); +#define FIO___SHA256_ROUND_INNER_COMMON() \ + uint32_t t2 = \ + ((v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2])) + \ + (fio_rrot32(v[0], 2) ^ fio_rrot32(v[0], 13) ^ fio_rrot32(v[0], 22)); \ + fio_u32x8_reshuffle(v, 7, 0, 1, 2, 3, 4, 5, 6); \ + v[4] += t1; \ + v[0] = t1 + t2; - if (FIO_NAME(FIO_STR_NAME, len)(pstr) < (sizeof(str) - 2)) { - FIO_NAME(FIO_STR_NAME, compact)(pstr); - FIO_ASSERT(FIO_STR_IS_SMALL(&str), - "Compacting didn't change String to small!"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == - FIO_STRLEN("Hello Big World!"), - "Compacting altered String length! (%zu != %zu)!", - FIO_NAME(FIO_STR_NAME, len)(pstr), - FIO_STRLEN("Hello Big World!")); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World!"), - "Compact data error (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == sizeof(str) - 2, - "Compacted String capacity reporting error!"); - } else { - FIO_LOG_DEBUG2("* Skipped `compact` test (irrelevant for type)."); + for (size_t i = 0; i < 16; ++i) { + const uint32_t t1 = + v[7] + sha256_consts[i] + w[i] + ((v[4] & v[5]) ^ ((~v[4]) & v[6])) + + (fio_rrot32(v[4], 6) ^ fio_rrot32(v[4], 11) ^ fio_rrot32(v[4], 25)); + FIO___SHA256_ROUND_INNER_COMMON(); } - - { - FIO_NAME(FIO_STR_NAME, freeze)(pstr); - FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, frozen)(pstr), - "Frozen String not flagged as frozen."); - fio_str_info_s old_state = FIO_NAME(FIO_STR_NAME, info)(pstr); - FIO_NAME(FIO_STR_NAME, write)(pstr, "more data to be written here", 28); - FIO_NAME(FIO_STR_NAME, replace) - (pstr, 2, 1, "more data to be written here", 28); - fio_str_info_s new_state = FIO_NAME(FIO_STR_NAME, info)(pstr); - FIO_ASSERT(old_state.len == new_state.len, "Frozen String length changed!"); - FIO_ASSERT(old_state.buf == new_state.buf, - "Frozen String pointer changed!"); - FIO_ASSERT( - old_state.capa == new_state.capa, - "Frozen String capacity changed (allowed, but shouldn't happen)!"); - FIO_STR_THAW_(&str); + for (size_t i = 0; i < 48; ++i) { /* expand block */ + w[(i & 15)] = + (fio_rrot32(w[((i + 14) & 15)], 17) ^ + fio_rrot32(w[((i + 14) & 15)], 19) ^ (w[((i + 14) & 15)] >> 10)) + + w[((i + 9) & 15)] + w[(i & 15)] + + (fio_rrot32(w[((i + 1) & 15)], 7) ^ fio_rrot32(w[((i + 1) & 15)], 18) ^ + (w[((i + 1) & 15)] >> 3)); + const uint32_t t1 = + v[7] + sha256_consts[i + 16] + w[(i & 15)] + + ((v[4] & v[5]) ^ ((~v[4]) & v[6])) + + (fio_rrot32(v[4], 6) ^ fio_rrot32(v[4], 11) ^ fio_rrot32(v[4], 25)); + FIO___SHA256_ROUND_INNER_COMMON(); } - FIO_NAME(FIO_STR_NAME, printf)(pstr, " %u", 42); - FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World! 42"), - "`printf` data error (%s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + for (size_t i = 0; i < 8; ++i) + h->u32[i] += v[i]; /* compress block with previous state */ - { - FIO_NAME(FIO_STR_NAME, s) str2 = FIO_STR_INIT; - FIO_STR_PTR pstr2 = FIO_PTR_TAG(&str2); - FIO_NAME(FIO_STR_NAME, concat)(pstr2, pstr); - FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, eq)(pstr, pstr2), - "`concat` error, strings not equal (%s != %s)!", - FIO_NAME(FIO_STR_NAME, ptr)(pstr), - FIO_NAME(FIO_STR_NAME, ptr)(pstr2)); - FIO_NAME(FIO_STR_NAME, write)(pstr2, ":extra data", 11); - FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, eq)(pstr, pstr2), - "`write` error after copy, strings equal " - "((%zu)%s == (%zu)%s)!", - FIO_NAME(FIO_STR_NAME, len)(pstr), - FIO_NAME(FIO_STR_NAME, ptr)(pstr), - FIO_NAME(FIO_STR_NAME, len)(pstr2), - FIO_NAME(FIO_STR_NAME, ptr)(pstr2)); +#undef FIO___SHA256_ROUND_INNER_COMMON +} - FIO_NAME(FIO_STR_NAME, destroy)(pstr2); +/** consume data and feed it to hash. */ +SFUNC void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len) { + const uint8_t *r = (const uint8_t *)data; + const size_t old_total = h->total_len; + const size_t new_total = len + h->total_len; + h->total_len = new_total; + /* manage cache */ + if (old_total & 63) { + const size_t offset = (old_total & 63); + if (len + offset < 64) { /* not enough - copy to cache */ + fio_memcpy63x((h->cache.u8 + offset), r, len); + return; + } + /* consume cache */ + const size_t byte2copy = 64UL - offset; + fio_memcpy63x(h->cache.u8 + offset, r, byte2copy); + fio___sha256_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 64); + r += byte2copy; + len -= byte2copy; + } + const uint8_t *end = r + (len & (~(uint64_t)63ULL)); + while ((uintptr_t)r < (uintptr_t)end) { + fio___sha256_round(&h->hash, r); + r += 64; } + fio_memcpy63x(h->cache.u64, r, len); +} - FIO_NAME(FIO_STR_NAME, destroy)(pstr); +SFUNC fio_u256 fio_sha256_finalize(fio_sha256_s *h) { + if (h->total_len == ((uint64_t)0ULL - 1ULL)) + return h->hash; + const size_t total = h->total_len; + const size_t remainder = total & 63; + h->cache.u8[remainder] = 0x80U; /* set the 1 bit at the left most position */ + if ((remainder) > 47) { /* make sure there's room to attach `total_len` */ + fio___sha256_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 64); + } + h->cache.u64[7] = fio_lton64((total << 3)); + fio___sha256_round(&h->hash, h->cache.u8); + for (size_t i = 0; i < 8; ++i) + h->hash.u32[i] = fio_ntol32(h->hash.u32[i]); /* back to big endien */ + h->total_len = ((uint64_t)0ULL - 1ULL); + return h->hash; +} - FIO_NAME(FIO_STR_NAME, write_i)(pstr, -42); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 3 && - !memcmp("-42", FIO_NAME(FIO_STR_NAME, ptr)(pstr), 3), - "write_i output error ((%zu) %s != -42)", - FIO_NAME(FIO_STR_NAME, len)(pstr), - FIO_NAME(FIO_STR_NAME, ptr)(pstr)); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); - { - fprintf(stderr, "* Testing string `readfile`.\n"); - FIO_NAME(FIO_STR_NAME, s) *s = FIO_NAME(FIO_STR_NAME, new)(); - FIO_ASSERT(FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s), - "error, string not allocated (%p)!", - (void *)s); - fio_str_info_s state = FIO_NAME(FIO_STR_NAME, readfile)(s, __FILE__, 0, 0); +/* ***************************************************************************** +Implementation - SHA-512 +***************************************************************************** */ - FIO_ASSERT(state.len && state.buf, - "error, no data was read for file %s!", - __FILE__); -#if defined(H___FIO_CSTL_COMBINED___H) - FIO_ASSERT(!memcmp(state.buf, - "/* " - "******************************************************" - "***********************", - 80), - "content error, header mismatch!\n %s", - state.buf); -#endif /* H___FIO_CSTL_COMBINED___H */ - fprintf(stderr, "* Testing UTF-8 validation and length.\n"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_valid)(s), - "`utf8_valid` error, code in this file " - "should be valid!"); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_len)(s) && - (FIO_NAME(FIO_STR_NAME, utf8_len)(s) <= - FIO_NAME(FIO_STR_NAME, len)(s)) && - (FIO_NAME(FIO_STR_NAME, utf8_len)(s) >= - (FIO_NAME(FIO_STR_NAME, len)(s)) >> 1), - "`utf8_len` error, invalid value (%zu / %zu!", - FIO_NAME(FIO_STR_NAME, utf8_len)(s), - FIO_NAME(FIO_STR_NAME, len)(s)); +FIO_IFUNC void fio___sha512_round(fio_u512 *h, const uint8_t *block) { + const uint64_t sha512_consts[80] = { + 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, + 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, 0x59F111F1B605D019, + 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, 0xD807AA98A3030242, + 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, + 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, + 0xC19BF174CF692694, 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, + 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, 0x2DE92C6F592B0275, + 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, + 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, + 0xBF597FC7BEEF0EE4, 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, + 0x06CA6351E003826F, 0x142929670A0E6E70, 0x27B70A8546D22FFC, + 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, + 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, + 0x92722C851482353B, 0xA2BFE8A14CF10364, 0xA81A664BBC423001, + 0xC24B8B70D0F89791, 0xC76C51A30654BE30, 0xD192E819D6EF5218, + 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, + 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, + 0x34B0BCB5E19B48A8, 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, + 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, 0x748F82EE5DEFB2FC, + 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, + 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, + 0xC67178F2E372532B, 0xCA273ECEEA26619C, 0xD186B8C721C0C207, + 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, 0x06F067AA72176FBA, + 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, + 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, + 0x431D67C49C100D4C, 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, + 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817}; - if (1) { - /* String content == whole file (this file) */ - intptr_t pos = -10; - size_t len = 20; - fprintf(stderr, "* Testing UTF-8 positioning.\n"); + uint64_t t1, t2; /* used often... */ + /* copy original state */ + uint64_t v[8] FIO_ALIGN(16); + for (size_t i = 0; i < 8; ++i) + v[i] = h->u64[i]; - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_select)(s, &pos, &len) == 0, - "`select` returned error for negative " - "pos! (%zd, %zu)", - (ssize_t)pos, - len); - FIO_ASSERT(pos == - (intptr_t)state.len - 10, /* no UTF-8 bytes in this file */ - "`utf8_select` error, negative position " - "invalid! (%zd)", - (ssize_t)pos); - FIO_ASSERT(len == 10, - "`utf8_select` error, trancated length " - "invalid! (%zd)", - (ssize_t)len); - pos = 10; - len = 20; - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_select)(s, &pos, &len) == 0, - "`utf8_select` returned error! (%zd, %zu)", - (ssize_t)pos, - len); - FIO_ASSERT(pos == 10, - "`utf8_select` error, position invalid! (%zd)", - (ssize_t)pos); - FIO_ASSERT(len == 20, - "`utf8_select` error, length invalid! (%zd)", - (ssize_t)len); - } - FIO_NAME(FIO_STR_NAME, free)(s); - } - FIO_NAME(FIO_STR_NAME, destroy)(pstr); - if (1) { - /* Testing Static initialization and writing */ -#if FIO_STR_OPTIMIZE4IMMUTABILITY - FIO_NAME(FIO_STR_NAME, init_const)(pstr, "Welcome", 7); -#else - str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC("Welcome"); -#endif - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == 0 || - FIO_STR_IS_SMALL(&str), - "Static string capacity non-zero."); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) > 0, - "Static string length should be automatically calculated."); - FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), - "Static strings shouldn't be dynamic."); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); + /* read data as an array of 16 big endian 64 bit integers. */ + uint64_t w[16] FIO_ALIGN(16); + fio_memcpy128(w, block); + for (size_t i = 0; i < 16; ++i) + w[i] = fio_lton64(w[i]); /* no-op on big endien systems */ -#if FIO_STR_OPTIMIZE4IMMUTABILITY - FIO_NAME(FIO_STR_NAME, init_const) - (pstr, - "Welcome to a very long static string that should not fit within a " - "containing struct... hopefuly", - 95); -#else - str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC( - "Welcome to a very long static string that should not fit within a " - "containing struct... hopefuly"); -#endif - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == 0 || - FIO_STR_IS_SMALL(&str), - "Static string capacity non-zero."); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) > 0, - "Static string length should be automatically calculated."); - FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), - "Static strings shouldn't be dynamic."); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); +#define FIO___SHA512_ROUND_UNROLL(s) \ + t1 = v[(7 - s) & 7] + sha512_consts[i + s] + w[(i + s) & 15] + \ + (fio_rrot64(v[(4 - s) & 7], 14) ^ fio_rrot64(v[(4 - s) & 7], 18) ^ \ + fio_rrot64(v[(4 - s) & 7], 41)) + \ + ((v[(4 - s) & 7] & v[(5 - s) & 7]) ^ \ + ((~v[(4 - s) & 7]) & v[(6 - s) & 7])); \ + t2 = \ + (fio_rrot64(v[(0 - s) & 7], 28) ^ fio_rrot64(v[(0 - s) & 7], 34) ^ \ + fio_rrot64(v[(0 - s) & 7], 39)) + \ + ((v[(0 - s) & 7] & v[(1 - s) & 7]) ^ (v[(0 - s) & 7] & v[(2 - s) & 7]) ^ \ + (v[(1 - s) & 7] & v[(2 - s) & 7])); \ + v[(3 - s) & 7] += t1; \ + v[(7 - s) & 7] = t1 + t2 -#if FIO_STR_OPTIMIZE4IMMUTABILITY - FIO_NAME(FIO_STR_NAME, init_const)(pstr, "Welcome", 7); -#else - str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC("Welcome"); -#endif - fio_str_info_s state = FIO_NAME(FIO_STR_NAME, write)(pstr, " Home", 5); - FIO_ASSERT(state.capa > 0, "Static string not converted to non-static."); - FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr) || - FIO_STR_IS_SMALL(&str), - "String should be dynamic after `write`."); + /* perform 80 "shuffle" rounds */ + for (size_t i = 0; i < 16; i += 8) { + FIO___SHA512_ROUND_UNROLL(0); + FIO___SHA512_ROUND_UNROLL(1); + FIO___SHA512_ROUND_UNROLL(2); + FIO___SHA512_ROUND_UNROLL(3); + FIO___SHA512_ROUND_UNROLL(4); + FIO___SHA512_ROUND_UNROLL(5); + FIO___SHA512_ROUND_UNROLL(6); + FIO___SHA512_ROUND_UNROLL(7); + } +#undef FIO___SHA512_ROUND_UNROLL +#define FIO___SHA512_ROUND_UNROLL(s) \ + t1 = (i + s + 14) & 15; \ + t2 = (i + s + 1) & 15; \ + t1 = fio_rrot64(w[t1], 19) ^ fio_rrot64(w[t1], 61) ^ (w[t1] >> 6); \ + t2 = fio_rrot64(w[t2], 1) ^ fio_rrot64(w[t2], 8) ^ (w[t2] >> 7); \ + w[(i + s) & 15] = t1 + t2 + w[(i + s + 9) & 15] + w[(i + s) & 15]; \ + t1 = v[(7 - s) & 7] + sha512_consts[i + s] + w[(i + s) & 15] + \ + (fio_rrot64(v[(4 - s) & 7], 14) ^ fio_rrot64(v[(4 - s) & 7], 18) ^ \ + fio_rrot64(v[(4 - s) & 7], 41)) + \ + ((v[(4 - s) & 7] & v[(5 - s) & 7]) ^ \ + ((~v[(4 - s) & 7]) & v[(6 - s) & 7])); \ + t2 = \ + (fio_rrot64(v[(0 - s) & 7], 28) ^ fio_rrot64(v[(0 - s) & 7], 34) ^ \ + fio_rrot64(v[(0 - s) & 7], 39)) + \ + ((v[(0 - s) & 7] & v[(1 - s) & 7]) ^ (v[(0 - s) & 7] & v[(2 - s) & 7]) ^ \ + (v[(1 - s) & 7] & v[(2 - s) & 7])); \ + v[(3 - s) & 7] += t1; \ + v[(7 - s) & 7] = t1 + t2 - char *cstr = FIO_NAME(FIO_STR_NAME, detach)(pstr); - FIO_ASSERT(cstr, "`detach` returned NULL"); - FIO_ASSERT(!memcmp(cstr, "Welcome Home\0", 13), - "`detach` string error: %s", - cstr); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 0, - "`detach` data wasn't cleared."); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); /*not really needed... detached... */ - FIO_NAME(FIO_STR_NAME, dealloc)(cstr); + for (size_t i = 16; i < 80; i += 8) { + FIO___SHA512_ROUND_UNROLL(0); + FIO___SHA512_ROUND_UNROLL(1); + FIO___SHA512_ROUND_UNROLL(2); + FIO___SHA512_ROUND_UNROLL(3); + FIO___SHA512_ROUND_UNROLL(4); + FIO___SHA512_ROUND_UNROLL(5); + FIO___SHA512_ROUND_UNROLL(6); + FIO___SHA512_ROUND_UNROLL(7); } - { - fprintf(stderr, "* Testing Base64 encoding / decoding.\n"); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); /* does nothing, but why not... */ + /* sum/store state */ + for (size_t i = 0; i < 8; ++i) + h->u64[i] += v[i]; +} - FIO_NAME(FIO_STR_NAME, s) b64message = FIO_STR_INIT; - fio_str_info_s b64i = FIO_NAME(FIO_STR_NAME, write)( - FIO_PTR_TAG(&b64message), - "Hello World, this is the voice of peace:)", - 41); - for (int i = 0; i < 256; ++i) { - uint8_t c = i; - b64i = FIO_NAME(FIO_STR_NAME, write)(FIO_PTR_TAG(&b64message), &c, 1); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&b64message)) == - (size_t)(42 + i), - "Base64 message length error (%zu != %zu)", - FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&b64message)), - (size_t)(42 + i)); - FIO_ASSERT(FIO_NAME(FIO_STR_NAME, - ptr)(FIO_PTR_TAG(&b64message))[41 + i] == (char)c, - "Base64 message data error"); - } - fio_str_info_s encoded = - FIO_NAME(FIO_STR_NAME, write_base64enc)(pstr, b64i.buf, b64i.len, 1); - /* prevent encoded data from being deallocated during unencoding */ - encoded = FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME)( - pstr, - encoded.len + ((encoded.len >> 2) * 3) + 8); - fio_str_info_s decoded; - { - FIO_NAME(FIO_STR_NAME, s) tmps; - FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_PTR_TAG(&tmps), pstr); - decoded = FIO_NAME(FIO_STR_NAME, write_base64dec)( - pstr, - FIO_NAME(FIO_STR_NAME, ptr)(FIO_PTR_TAG(&tmps)), - FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&tmps))); - FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&tmps)); - encoded.buf = decoded.buf; +/** Feed data into the hash */ +SFUNC void fio_sha512_consume(fio_sha512_s *restrict h, + const void *restrict data, + uint64_t len) { + const uint8_t *r = (const uint8_t *)data; + const size_t old_total = h->total_len; + const size_t new_total = len + h->total_len; + h->total_len = new_total; + /* manage cache */ + if (old_total & 127) { + const size_t offset = (old_total & 127); + if (len + offset < 128) { /* not enough - copy to cache */ + fio_memcpy127x((h->cache.u8 + offset), r, len); + return; } - FIO_ASSERT(encoded.len, "Base64 encoding failed"); - FIO_ASSERT(decoded.len > encoded.len, - "Base64 decoding failed:\n%s", - encoded.buf); - FIO_ASSERT(b64i.len == decoded.len - encoded.len, - "Base 64 roundtrip length error, %zu != %zu (%zu - %zu):\n %s", - b64i.len, - decoded.len - encoded.len, - decoded.len, - encoded.len, - decoded.buf); - - FIO_ASSERT(!memcmp(b64i.buf, decoded.buf + encoded.len, b64i.len), - "Base 64 roundtrip failed:\n %s", - decoded.buf); - FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&b64message)); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); + /* consume cache */ + const size_t byte2copy = 128UL - offset; + fio_memcpy127x(h->cache.u8 + offset, r, byte2copy); + fio___sha512_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 128); + r += byte2copy; + len -= byte2copy; } - { - fprintf(stderr, "* Testing JSON style character escaping / unescaping.\n"); - FIO_NAME(FIO_STR_NAME, s) unescaped = FIO_STR_INIT; - fio_str_info_s ue; - const char *utf8_sample = /* three hearts, small-big-small*/ - "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95"; - FIO_NAME(FIO_STR_NAME, write) - (FIO_PTR_TAG(&unescaped), utf8_sample, FIO_STRLEN(utf8_sample)); - for (int i = 0; i < 256; ++i) { - uint8_t c = i; - ue = FIO_NAME(FIO_STR_NAME, write)(FIO_PTR_TAG(&unescaped), &c, 1); - } - fio_str_info_s encoded = - FIO_NAME(FIO_STR_NAME, write_escape)(pstr, ue.buf, ue.len); - // fprintf(stderr, "* %s\n", encoded.buf); - fio_str_info_s decoded; - { - FIO_NAME(FIO_STR_NAME, s) tmps; - FIO_NAME(FIO_STR_NAME, init_copy2)(&tmps, pstr); - decoded = FIO_NAME(FIO_STR_NAME, - write_unescape)(pstr, - FIO_NAME(FIO_STR_NAME, ptr)(&tmps), - FIO_NAME(FIO_STR_NAME, len)(&tmps)); - FIO_NAME(FIO_STR_NAME, destroy)(&tmps); - encoded.buf = decoded.buf; - } - FIO_ASSERT(!memcmp(encoded.buf, utf8_sample, FIO_STRLEN(utf8_sample)), - "valid UTF-8 data shouldn't be escaped:\n%.*s\n%s", - (int)encoded.len, - encoded.buf, - decoded.buf); - FIO_ASSERT(encoded.len, "JSON encoding failed"); - FIO_ASSERT(decoded.len > encoded.len, - "JSON decoding failed:\n%s", - encoded.buf); - FIO_ASSERT(ue.len == decoded.len - encoded.len, - "JSON roundtrip length error, %zu != %zu (%zu - %zu):\n %s", - ue.len, - decoded.len - encoded.len, - decoded.len, - encoded.len, - decoded.buf); + const uint8_t *end = r + (len & (~(uint64_t)127ULL)); + while ((uintptr_t)r < (uintptr_t)end) { + fio___sha512_round(&h->hash, r); + r += 128; + } + fio_memcpy127x(h->cache.u64, r, len); +} - FIO_ASSERT(!memcmp(ue.buf, decoded.buf + encoded.len, ue.len), - "JSON roundtrip failed:\n %s", - decoded.buf); - FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&unescaped)); - FIO_NAME(FIO_STR_NAME, destroy)(pstr); +/** finalizes a fio_u512 with the SHA 512 hash. */ +SFUNC fio_u512 fio_sha512_finalize(fio_sha512_s *h) { + if (h->total_len == ((uint64_t)0ULL - 1ULL)) + return h->hash; + const size_t total = h->total_len; + const size_t remainder = total & 127; + h->cache.u8[remainder] = 0x80U; /* set the 1 bit at the left most position */ + if ((remainder) > 112) { /* make sure there's room to attach `total_len` */ + fio___sha512_round(&h->hash, h->cache.u8); + FIO_MEMSET(h->cache.u8, 0, 128); } + h->cache.u64[15] = fio_lton64((total << 3)); + fio___sha512_round(&h->hash, h->cache.u8); + for (size_t i = 0; i < 8; ++i) + h->hash.u64[i] = fio_ntol64(h->hash.u64[i]); /* back to/from big endien */ + h->total_len = ((uint64_t)0ULL - 1ULL); + return h->hash; } -#undef FIO__STR_SMALL_CAPA -#undef FIO_STR_WRITE_TEST_FUNC -#endif /* FIO_STR_WRITE_TEST_FUNC */ /* ***************************************************************************** -String Cleanup +Cleanup ***************************************************************************** */ #endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_SHA2 */ +#undef FIO_SHA2 +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_ED25519 /* Development inclusion - ignore line */ +#define FIO_SHA2 /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -#undef FIO_STR_SMALL -#undef FIO_STR_SMALL_CAPA -#undef FIO_STR_SMALL_DATA -#undef FIO_STR_SMALL_LEN -#undef FIO_STR_SMALL_LEN_SET -#undef FIO_STR_BIG_CAPA -#undef FIO_STR_BIG_CAPA_SET -#undef FIO_STR_BIG_DATA -#undef FIO_STR_BIG_FREE_BUF -#undef FIO_STR_BIG_IS_DYNAMIC -#undef FIO_STR_BIG_LEN -#undef FIO_STR_BIG_LEN_SET -#undef FIO_STR_BIG_SET_STATIC -#undef FIO_STR_FREEZE_ -#undef FIO_STR_IS_FROZEN -#undef FIO_STR_IS_SMALL -#undef FIO_STR_NAME + Elliptic Curve ED25519 (WIP) -#undef FIO_STR_OPTIMIZE4IMMUTABILITY -#undef FIO_STR_OPTIMIZE_EMBEDDED -#undef FIO_STR_PTR -#undef FIO_STR_THAW_ -#undef FIO_STR_RESERVE_NAME -#endif /* FIO_STR_NAME */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_ARRAY_NAME ary /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if 0 && defined(FIO_ED25519) && !defined(H___FIO_ED25519___H) +#define H___FIO_ED25519___H + /* ***************************************************************************** +TODO: ED 25519 +ED-25519 key generation, key exchange and signatures are crucial to complete the +minimal building blocks that would allow to secure inter-machine communication +in mostly secure environments. Of course the use of a tested cryptographic +library (where accessible) might be preferred, but some security is better than +none. +***************************************************************************** */ +/* ***************************************************************************** +ED25519 API +***************************************************************************** */ +/** ED25519 Key Pair */ +typedef struct { + fio_u512 private_key; /* Private key (with extra internal storage?) */ + fio_u256 public_key; /* Public key */ +} fio_ed25519_s; - Dynamic Arrays +/* Generates a random ED25519 keypair. */ +SFUNC void fio_ed25519_keypair(fio_ed25519_s *keypair); +/* Sign a message using ED25519 */ +SFUNC void fio_ed25519_sign(uint8_t *signature, + const fio_buf_info_s message, + const fio_ed25519_s *keypair); +/* Verify an ED25519 signature */ +SFUNC int fio_ed25519_verify(const uint8_t *signature, + const fio_buf_info_s message, + const fio_u256 *public_key); -Copyright and License: see header file (000 copyright.h) or top of file +/* ***************************************************************************** +Implementation - inlined static functions ***************************************************************************** */ -#ifdef FIO_ARRAY_NAME -#ifndef FIO_ARRAY_NOT_FOUND -#define FIO_ARRAY_NOT_FOUND ((uint32_t)-1) -#endif +/* ***************************************************************************** +Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -#ifdef FIO_ARRAY_TYPE_STR -#ifndef FIO_ARRAY_TYPE -#define FIO_ARRAY_TYPE fio_keystr_s -#endif -#ifndef FIO_ARRAY_TYPE_COPY -#define FIO_ARRAY_TYPE_COPY(dest, src) ((dest) = fio_keystr_init((src))) -#endif -#ifndef FIO_ARRAY_TYPE_DESTROY -#define FIO_ARRAY_TYPE_DESTROY(obj) fio_keystr_destroy(&(obj)); -#endif -#ifndef FIO_ARRAY_TYPE_CMP -#define FIO_ARRAY_TYPE_CMP(a, b) fio_keystr_is_eq((a), (b)) -#endif -#undef FIO_ARRAY_DESTROY_AFTER_COPY -#define FIO_ARRAY_DESTROY_AFTER_COPY 1 -#endif +/* prevent ED25519 keys from having a small period (cyclic value). */ +FIO_IFUNC void fio___ed25519_clamp_on_key(uint8_t *k) { + k[0] &= 0xF8U; /* zero out 3 least significant bits (emulate mul by 8) */ + k[31] &= 0x7FU; /* unset most significant bit (constant time fix) */ + k[31] |= 0x40U; /* set the 255th bit (making sure the value is big) */ +} -#ifndef FIO_ARRAY_TYPE -/** The type for array elements (an array of FIO_ARRAY_TYPE) */ -#define FIO_ARRAY_TYPE void * -/** An invalid value for that type (if any). */ -#define FIO_ARRAY_TYPE_INVALID NULL -#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1 -#else -#ifndef FIO_ARRAY_TYPE_INVALID -/** An invalid value for that type (if any). */ -#define FIO_ARRAY_TYPE_INVALID ((FIO_ARRAY_TYPE){0}) -/* internal flag - do not set */ -#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1 -#endif -#endif +static fio_u256 FIO___ED25519_PRIME = fio_u256_init64(0x7FFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFFF, + 0xFFFFFFFFFFFFFFED); +/* Obfuscate or recover ED25519 keys to prevent easy memory scraping */ +FIO_IFUNC void fio___ed25519_flip(fio_ed25519_s *k) { + /* Generate a deterministic mask */ + uint64_t msk = + k->public_key.u64[3] + (uint64_t)(uintptr_t)(void *)&fio_ed25519_keypair; + /* XOR mask the private key */ + fio_u512_cxor64(&k->private_key, &k->private_key, msk); + /* XOR mask the first 192 bits of the public key */ + k->public_key.u64[0] ^= msk; + k->public_key.u64[1] ^= msk; + k->public_key.u64[2] ^= msk; +} -#ifndef FIO_ARRAY_TYPE_INVALID_SIMPLE -/** Is the FIO_ARRAY_TYPE_INVALID object memory is all zero? (yes = 1) */ -#define FIO_ARRAY_TYPE_INVALID_SIMPLE 0 -#endif +/* Elliptic Curve Point Addition for Ed25519 */ +FIO_IFUNC void fio___ed25519_point_add(fio_u1024 *R, const fio_u1024 *P) { + /* Extract coordinates for P1 and P2 (R and P) */ + fio_u256 X1 = R->u256[0], Y1 = R->u256[1], Z1 = R->u256[2], T1 = R->u256[3]; + fio_u256 X2 = P->u256[0], Y2 = P->u256[1], Z2 = P->u256[2], T2 = P->u256[3]; -#ifndef FIO_ARRAY_TYPE_COPY -/** Handles a copy operation for an array's element. */ -#define FIO_ARRAY_TYPE_COPY(dest, src) (dest) = (src) -/* internal flag - do not set */ -#define FIO_ARRAY_TYPE_COPY_SIMPLE 1 -#endif + fio_u256 A, B, C, D, X3, Y3, Z3, T3; -#ifndef FIO_ARRAY_TYPE_DESTROY -/** Handles a destroy / free operation for an array's element. */ -#define FIO_ARRAY_TYPE_DESTROY(obj) -/* internal flag - do not set */ -#define FIO_ARRAY_TYPE_DESTROY_SIMPLE 1 -#endif + /* A = (Y1 - X1) * (Y2 - X2) */ + fio_u256 Y1_minus_X1 = fio_u256_sub(Y1, X1); + fio_u256 Y2_minus_X2 = fio_u256_sub(Y2, X2); + A = fio_u256_mul(Y1_minus_X1, Y2_minus_X2); -#ifndef FIO_ARRAY_TYPE_CMP -/** Handles a comparison operation for an array's element. */ -#define FIO_ARRAY_TYPE_CMP(a, b) (a) == (b) -/* internal flag - do not set */ -#define FIO_ARRAY_TYPE_CMP_SIMPLE 1 -#endif + /* B = (Y1 + X1) * (Y2 + X2) */ + fio_u256 Y1_plus_X1 = fio_u256_add(Y1, X1); + fio_u256 Y2_plus_X2 = fio_u256_add(Y2, X2); + B = fio_u256_mul(Y1_plus_X1, Y2_plus_X2); -#ifndef FIO_ARRAY_TYPE_CONCAT_COPY -#define FIO_ARRAY_TYPE_CONCAT_COPY FIO_ARRAY_TYPE_COPY -#define FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE FIO_ARRAY_TYPE_COPY_SIMPLE -#endif -/** - * The FIO_ARRAY_DESTROY_AFTER_COPY macro should be set if - * FIO_ARRAY_TYPE_DESTROY should be called after FIO_ARRAY_TYPE_COPY when an - * object is removed from the array after being copied to an external container - * (an `old` pointer) - */ -#ifndef FIO_ARRAY_DESTROY_AFTER_COPY -#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE && !FIO_ARRAY_TYPE_COPY_SIMPLE -#define FIO_ARRAY_DESTROY_AFTER_COPY 1 -#else -#define FIO_ARRAY_DESTROY_AFTER_COPY 0 -#endif -#endif + /* C = 2 * T1 * T2 * d */ + C = fio_u256_mul(fio_u256_mul(T1, T2), ED25519_D); -/* Extra empty slots when allocating memory. */ -#ifndef FIO_ARRAY_PADDING -#define FIO_ARRAY_PADDING 4 -#endif + /* D = 2 * Z1 * Z2 */ + D = fio_u256_mul(fio_u256_mul(Z1, Z2), fio_u256_two()); -/* - * Uses the array structure to embed object, if there's space for them. - * - * This optimizes small arrays and specifically touplets. For `void *` type - * arrays this allows for 2 objects to be embedded, resulting in faster access - * due to cache locality and reduced pointer redirection. - * - * For large arrays, it is better to disable this feature. - * - * Note: values larger than 1 add a memory allocation cost to the array - * container, adding enough room for at least `FIO_ARRAY_ENABLE_EMBEDDED - 1` - * items. - */ -#ifndef FIO_ARRAY_ENABLE_EMBEDDED -#define FIO_ARRAY_ENABLE_EMBEDDED 1 -#endif + /* X3 = (B - A) * (D - C) */ + X3 = fio_u256_mul(fio_u256_sub(B, A), fio_u256_sub(D, C)); -/* Sets memory growth to exponentially increase. Consumes more memory. */ -#ifndef FIO_ARRAY_EXPONENTIAL -#define FIO_ARRAY_EXPONENTIAL 0 -#endif + /* Y3 = (B + A) * (D + C) */ + Y3 = fio_u256_mul(fio_u256_add(B, A), fio_u256_add(D, C)); -#undef FIO_ARRAY_SIZE2WORDS -#define FIO_ARRAY_SIZE2WORDS(size) \ - ((sizeof(FIO_ARRAY_TYPE) & 1) ? (((size) & (~15)) + 16) \ - : (sizeof(FIO_ARRAY_TYPE) & 2) ? (((size) & (~7)) + 8) \ - : (sizeof(FIO_ARRAY_TYPE) & 4) ? (((size) & (~3)) + 4) \ - : (sizeof(FIO_ARRAY_TYPE) & 8) ? (((size) & (~1)) + 2) \ - : (size)) + /* Z3 = D * C */ + Z3 = fio_u256_mul(D, C); -/* ***************************************************************************** -Dynamic Arrays - type -***************************************************************************** */ + /* T3 = (B - A) * (B + A) */ + T3 = fio_u256_mul(fio_u256_sub(B, A), fio_u256_add(B, A)); -/** an Array type. */ -typedef struct FIO_NAME(FIO_ARRAY_NAME, s) { - /* start common header (with embedded array type) */ - /** the offset to the first item. */ - uint32_t start; - /** The offset to the first empty location the array. */ - uint32_t end; - /* end common header (with embedded array type) */ - /** The array's capacity - limited to 32bits, but we use the extra padding. */ - uint32_t capa; - /** a pointer to the array's memory (if not embedded) */ - FIO_ARRAY_TYPE *ary; -#if FIO_ARRAY_ENABLE_EMBEDDED > 1 - /** Do we wanted larger small-array optimizations? */ - FIO_ARRAY_TYPE - extra_memory_for_embedded_arrays[(FIO_ARRAY_ENABLE_EMBEDDED - 1)] -#endif -} FIO_NAME(FIO_ARRAY_NAME, s); + /* Update R with the result */ + R->u256[0] = X3; /* X */ + R->u256[1] = Y3; /* Y */ + R->u256[2] = Z3; /* Z */ + R->u256[3] = T3; /* T */ +} -#ifdef FIO_PTR_TAG_TYPE -#define FIO_ARRAY_PTR FIO_PTR_TAG_TYPE -#else -#define FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, s) * -#endif +/* Helper function: Scalar multiplication on the elliptic curve */ +FIO_IFUNC void fio___ed25519_mul(fio_u512 *result, + const fio_u512 *scalar, + const fio_u512 *point) { + /* Start with the point */ + fio_u512 R[2] = {{0}, point[0]}; /* Identity point */ + + /* Step 2: Perform the Montgomery ladder scalar multiplication */ + for (int i = 255; i >= 0; --i) { + uint64_t bit = (scalar->u64[i >> 6] >> (i & 63)) & 1U; + /* Elliptic curve point addition and doubling */ + fio___ed25519_point_add(R, R + 1); + fio___ed25519_point_double(R + bit); + } + + /* Step 3: The final result is stored in R0 */ + *result = R[0]; +} + +/* Helper function: Modular reduction for Ed25519 */ +FIO_IFUNC void fio___ed25519_mod_reduce(fio_u256 *s) { + /* TODO: Implement modular reduction for Ed25519 scalar */ +} + +/* ED25519 Base Point (G) */ +const fio_u512 FIO___ED25519_BASEPOINT = { + .u64 = + { + 0x216936D3CD6E53FEULL, /* x-coordinate (lower 64 bits) */ + 0xC0A4E231FDD6DC5CULL, /* x-coordinate (upper 64 bits) */ + 0x6666666666666666ULL, /* y-coordinate (lower 64 bits) */ + 0x6666666666666666ULL /* y-coordinate (upper 64 bits) */ + }, +}; + +/* Generate ED25519 keypair */ +SFUNC fio_ed25519_s fio_ed25519_keypair(void) { + fio_ed25519_s keypair; + /* Generate the 512-bit (clamped) private key */ + keypair.private_key.u64[0] = fio_rand64(); + keypair.private_key.u64[1] = fio_rand64(); + keypair.private_key.u64[2] = fio_rand64(); + keypair.private_key.u64[3] = fio_rand64(); + keypair.private_key = fio_sha512(keypair.private_key.u8, 32); + fio___ed25519_clamp_on_key(keypair.private_key.u8); + /* TODO: Derive the public key */ + fio_u256_mul(fio_u512 * result, const fio_u256 *a, const fio_u256 *b) + fio___ed25519_mul(&keypair.public_key, + &keypair.private_key, + &FIO___ED25519_BASEPOINT); + /* Maybe... */ + + /* Mask data, so it's harder to scrape in case of a memory dump. */ + fio___ed25519_flip(&keypair); + return keypair; +} + +/* Sign a message using ED25519 */ +SFUNC void fio_ed25519_sign(uint8_t *signature, + const fio_buf_info_s message, + const fio_ed25519_s *keypair) { + fio_sha512_s sha; + fio_u512 r, h; + fio_u256 R; + + /* Step 1: Hash the private key and message */ + sha = fio_sha512_init(); + fio_sha512_consume(&sha, + keypair->private_key.u8 + 32, + 32); /* Hash private key second part */ + fio_sha512_consume(&sha, message.buf, message.len); /* Hash the message */ + r = fio_sha512_finalize(&sha); /* Finalize the hash */ + + /* Step 2: Clamp and scalar multiply */ + fio___ed25519_clamp_on_key(r.u8); + fio___ed25519_mul((fio_ed25519_s *)&R, &r, &FIO___ED25519_BASEPOINT); + + /* Step 3: Compute 's' */ + sha = fio_sha512_init(); + fio_sha512_consume(&sha, R.u8, 32); /* Hash R */ + fio_sha512_consume(&sha, message.buf, message.len); /* Hash message */ + h = fio_sha512_finalize(&sha); /* Compute H(R || message) */ + fio___ed25519_mod_reduce(h.u8); /* Modular reduction of the hash */ + + /* Step 4: Create the signature */ + memcpy(signature, R.u8, 32); /* Copy R to the signature */ + memcpy(signature + 32, h.u8, 32); /* Copy the reduced hash 's' */ +} + +/* Verify an ED25519 signature */ +SFUNC int fio_ed25519_verify(const uint8_t *signature, + const fio_buf_info_s message, + const fio_u256 *public_key) { + fio_sha512_s sha; + fio_u512 r, h; + uint8_t calculated_R[32]; + + /* Step 1: Recalculate R */ + sha = fio_sha512_init(); + fio_sha512_consume(&sha, public_key->u8, 32); /* Hash the public key */ + fio_sha512_consume(&sha, message.buf, message.len); /* Hash the message */ + r = fio_sha512_finalize(&sha); /* Finalize the hash */ + + fio___ed25519_mul((fio_ed25519_s *)calculated_R, &r, &public_key->u8); + + /* Step 2: Compare calculated R with signature R using FIO_MEMCMP */ + return FIO_MEMCMP(calculated_R, signature, 32) == 0; +} /* ***************************************************************************** -Dynamic Arrays - API +Cleanup ***************************************************************************** */ -#ifndef FIO_ARRAY_INIT -/* Initialization macro. */ -#define FIO_ARRAY_INIT \ - { 0 } -#endif +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_ED25519 +#endif /* FIO_ED25519 */ +/* ************************************************************************** */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_STR_NAME fio /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -#ifndef FIO_REF_CONSTRUCTOR_ONLY -/* Allocates a new array object on the heap and initializes it's memory. */ -SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void); -/* Frees an array's internal data AND it's container! */ -SFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary); -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + Dynamic Strings (binary safe) -/* Destroys any objects stored in the array and frees the internal state. */ -SFUNC void FIO_NAME(FIO_ARRAY_NAME, destroy)(FIO_ARRAY_PTR ary); -/** Returns the number of elements in the Array. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, count)(FIO_ARRAY_PTR ary); -/** Returns the current, temporary, array capacity (it's dynamic). */ -FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, capa)(FIO_ARRAY_PTR ary); +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#ifdef FIO_STR_SMALL +#ifndef FIO_STR_NAME +#define FIO_STR_NAME FIO_STR_SMALL +#endif +#ifndef FIO_STR_OPTIMIZE4IMMUTABILITY +#define FIO_STR_OPTIMIZE4IMMUTABILITY 1 +#endif +#endif /* FIO_STR_SMALL */ -/** - * Returns 1 if the array is embedded, 0 if it has memory allocated and -1 on an - * error. - */ -FIO_IFUNC int FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(FIO_ARRAY_PTR ary); +#if defined(FIO_STR_NAME) +#ifndef FIO_STR_OPTIMIZE_EMBEDDED /** - * Returns a pointer to the C array containing the objects. + * For each unit (0 by default), adds `sizeof(char *)` bytes to the type size, + * increasing the amount of strings that could be embedded within the type + * without additional memory allocation. + * + * For example, when using a reference counter wrapper on a 64bit system, it + * would make sense to set this value to 1 - allowing the type size to fully + * utilize a 16 byte memory allocation alignment. */ -FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME2(FIO_ARRAY_NAME, ptr)(FIO_ARRAY_PTR ary); +#define FIO_STR_OPTIMIZE_EMBEDDED 0 +#endif +#ifndef FIO_STR_OPTIMIZE4IMMUTABILITY /** - * Reserves a minimal capacity for additional elements to be added to the array. - * - * If `capa` is negative, new memory will be allocated at the beginning of the - * array rather then it's end. + * Minimizes the struct size, storing only string length and pointer. * - * Returns the array's new capacity. + * By avoiding extra (mutable related) data, such as the allocated memory's + * capacity, strings require less memory. However, this does introduce a + * performance penalty when editing the string data. */ -SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, reserve)(FIO_ARRAY_PTR ary, - int64_t capa); +#define FIO_STR_OPTIMIZE4IMMUTABILITY 0 +#endif + +#if FIO_STR_OPTIMIZE4IMMUTABILITY +/* enforce limit after which FIO_STR_OPTIMIZE4IMMUTABILITY makes no sense */ +#if FIO_STR_OPTIMIZE_EMBEDDED > 1 +#undef FIO_STR_OPTIMIZE_EMBEDDED +#define FIO_STR_OPTIMIZE_EMBEDDED 1 +#endif +#else +/* enforce limit due to 6 bit embedded string length limit (assumes 64 bit) */ +#if FIO_STR_OPTIMIZE_EMBEDDED > 4 +#undef FIO_STR_OPTIMIZE_EMBEDDED +#define FIO_STR_OPTIMIZE_EMBEDDED 4 +#endif +#endif /* FIO_STR_OPTIMIZE4IMMUTABILITY*/ + +/* ***************************************************************************** +String API - Initialization and Destruction +***************************************************************************** */ /** - * Adds all the items in the `src` Array to the end of the `dest` Array. + * The `fio_str_s` type should be considered opaque. * - * The `src` Array remain untouched. + * The type's attributes should be accessed ONLY through the accessor + * functions: `fio_str2cstr`, `fio_str_len`, `fio_str2ptr`, `fio_str_capa`, + * etc'. * - * Always returns the destination array (`dest`). + * Note: when the `small` flag is present, the structure is ignored and used + * as raw memory for a small String (no additional allocation). This changes + * the String's behavior drastically and requires that the accessor functions + * be used. */ -SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, concat)(FIO_ARRAY_PTR dest, - FIO_ARRAY_PTR src); +typedef struct { + /* String flags: + * + * bit 1: small string. + * bit 2: frozen string. + * bit 3: static (non allocated) string (big strings only). + * bit 3-8: small string length (up to 64 bytes). + */ + uint8_t special; + uint8_t reserved[(sizeof(void *) * (1 + FIO_STR_OPTIMIZE_EMBEDDED)) - + (sizeof(uint8_t))]; /* padding length */ +#if !FIO_STR_OPTIMIZE4IMMUTABILITY + size_t capa; /* known capacity for longer Strings */ + size_t len; /* String length for longer Strings */ +#endif /* FIO_STR_OPTIMIZE4IMMUTABILITY */ + char *buf; /* pointer for longer Strings */ +} FIO_NAME(FIO_STR_NAME, s); + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_STR_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_STR_PTR FIO_NAME(FIO_STR_NAME, s) * +#endif +#ifndef FIO_STR_INIT /** - * Sets `index` to the value in `data`. + * This value should be used for initialization. For example: * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). + * // on the stack + * fio_str_s str = FIO_STR_INIT; * - * If `old` isn't NULL, the existing data will be copied to the location pointed - * to by `old` before the copy in the Array is destroyed. + * // or on the heap + * fio_str_s *str = malloc(sizeof(*str)); + * *str = FIO_STR_INIT; * - * Returns a pointer to the new object, or NULL on error. + * Remember to cleanup: + * + * // on the stack + * fio_str_destroy(&str); + * + * // or on the heap + * fio_str_free(str); + * free(str); */ -SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, set)(FIO_ARRAY_PTR ary, - int64_t index, - FIO_ARRAY_TYPE data, - FIO_ARRAY_TYPE *old); +#define FIO_STR_INIT \ + { .special = 0 } /** - * Returns the value located at `index` (no copying is performed). + * This macro allows the container to be initialized with existing data, as long + * as it's memory was allocated with the same allocator (`malloc` / + * `fio_malloc`). * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). - */ -FIO_IFUNC FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, get)(FIO_ARRAY_PTR ary, - int64_t index); - -/** - * Returns the index of the object or (uint32_t)-1 if the object wasn't found. + * The `capacity` value should exclude the NUL character (if exists). * - * If `start_at` is negative (i.e., -1), than seeking will be performed in - * reverse, where -1 == last index (-2 == second to last, etc'). + * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the + * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) */ -SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, find)(FIO_ARRAY_PTR ary, - FIO_ARRAY_TYPE data, - int64_t start_at); +#define FIO_STR_INIT_EXISTING(buffer, length, capacity) \ + { .capa = (capacity), .len = (length), .buf = (buffer) } /** - * Removes an object from the array, MOVING all the other objects to prevent - * "holes" in the data. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns 0 on success and -1 on error. + * This macro allows the container to be initialized with existing static data, + * that shouldn't be freed. * - * This action is O(n) where n in the length of the array. - * It could get expensive. + * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the + * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) */ -SFUNC int FIO_NAME(FIO_ARRAY_NAME, remove)(FIO_ARRAY_PTR ary, - int64_t index, - FIO_ARRAY_TYPE *old); +#define FIO_STR_INIT_STATIC(buffer) \ + { \ + .special = 4, .capa = FIO_STRLEN((buffer)), .len = FIO_STRLEN((buffer)), \ + .buf = (char *)(buffer) \ + } /** - * Removes all occurrences of an object from the array (if any), MOVING all the - * existing objects to prevent "holes" in the data. - * - * Returns the number of items removed. + * This macro allows the container to be initialized with existing static data, + * that shouldn't be freed. * - * This action is O(n) where n in the length of the array. - * It could get expensive. + * NOTE: This macro isn't valid for FIO_STR_SMALL (or strings with the + * FIO_STR_OPTIMIZE4IMMUTABILITY optimization) */ -SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, remove2)(FIO_ARRAY_PTR ary, - FIO_ARRAY_TYPE data); +#define FIO_STR_INIT_STATIC2(buffer, length) \ + { .special = 4, .capa = (length), .len = (length), .buf = (char *)(buffer) } -/** Attempts to lower the array's memory consumption. */ -SFUNC void FIO_NAME(FIO_ARRAY_NAME, compact)(FIO_ARRAY_PTR ary); +#endif /* FIO_STR_INIT */ + +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/** Allocates a new String object on the heap. */ +FIO_IFUNC FIO_STR_PTR FIO_NAME(FIO_STR_NAME, new)(void); /** - * Pushes an object to the end of the Array. Returns a pointer to the new object - * or NULL on error. + * Destroys the string and frees the container (if allocated with `new`). */ -SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, push)(FIO_ARRAY_PTR ary, - FIO_ARRAY_TYPE data); +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, free)(FIO_STR_PTR s); +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ /** - * Removes an object from the end of the Array. + * Initializes the container with the provided static / constant string. * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. + * The string will be copied to the container **only** if it will fit in the + * container itself. Otherwise, the supplied pointer will be used as is and it + * should remain valid until the string is destroyed. * - * Returns -1 on error (Array is empty) and 0 on success. + * The final string can be safely be destroyed (using the `destroy` function). */ -SFUNC int FIO_NAME(FIO_ARRAY_NAME, pop)(FIO_ARRAY_PTR ary, FIO_ARRAY_TYPE *old); +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_const)(FIO_STR_PTR s, + const char *str, + size_t len); /** - * Unshifts an object to the beginning of the Array. Returns a pointer to the - * new object or NULL on error. + * Initializes the container with a copy of the provided dynamic string. * - * This could be expensive, causing `memmove`. + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). */ -SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, unshift)(FIO_ARRAY_PTR ary, - FIO_ARRAY_TYPE data); +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy)(FIO_STR_PTR s, + const char *str, + size_t len); /** - * Removes an object from the beginning of the Array. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. + * Initializes the container with a copy of an existing String object. * - * Returns -1 on error (Array is empty) and 0 on success. + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). */ -SFUNC int FIO_NAME(FIO_ARRAY_NAME, shift)(FIO_ARRAY_PTR ary, - FIO_ARRAY_TYPE *old); +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_STR_PTR dest, + FIO_STR_PTR src); -/** Iteration information structure passed to the callback. */ -typedef struct FIO_NAME(FIO_ARRAY_NAME, each_s) { - /** The array iterated. Once set, cannot be safely changed. */ - FIO_ARRAY_PTR const parent; - /** The current object's index */ - uint64_t index; - /** The callback / task called for each index, may be updated mid-cycle. */ - int (*task)(struct FIO_NAME(FIO_ARRAY_NAME, each_s) * info); - /** Opaque user data. */ - void *udata; - /** The object / value at the current index. */ - FIO_ARRAY_TYPE value; - /* memory padding used for FIOBJ */ - uint64_t padding; -} FIO_NAME(FIO_ARRAY_NAME, each_s); +/** + * Frees the String's resources and re-initializes the container. + * + * Note: if the container isn't allocated on the stack, it should be freed + * separately using the appropriate `free` function. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, destroy)(FIO_STR_PTR s); /** - * Iteration using a callback for each entry in the array. + * Returns a C string with the existing data, re-initializing the String. * - * The callback task function must accept an each_s pointer, see above. + * Note: the String data is removed from the container, but the container + * isn't freed. * - * If the callback returns -1, the loop is broken. Any other value is ignored. + * Returns NULL if there's no String data. * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. + * NOTE: Returned string is ALWAYS dynamically allocated. Remember to free. */ -IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, - each)(FIO_ARRAY_PTR ary, - int (*task)(FIO_NAME(FIO_ARRAY_NAME, each_s) * - info), - void *udata, - int64_t start_at); +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, detach)(FIO_STR_PTR s); + +/** Frees the pointer returned by `detach`. */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, dealloc)(void *ptr); + +/* ***************************************************************************** +String API - String state (data pointers, length, capacity, etc') +***************************************************************************** */ + +/** Returns the String's complete state (capacity, length and pointer). */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, info)(const FIO_STR_PTR s); + +/** Returns the String's partial state (length and pointer). */ +FIO_IFUNC fio_buf_info_s FIO_NAME(FIO_STR_NAME, buf)(const FIO_STR_PTR s); + +/** Returns a pointer (`char *`) to the String's content. */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, ptr)(FIO_STR_PTR s); + +/** Returns the String's length in bytes. */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, len)(FIO_STR_PTR s); + +/** Returns the String's existing capacity (total used & available memory). */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, capa)(FIO_STR_PTR s); + +/** Prevents further manipulations to the String's content. */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, freeze)(FIO_STR_PTR s); + +/** Returns true if the string is frozen. */ +FIO_IFUNC uint8_t FIO_NAME_BL(FIO_STR_NAME, frozen)(FIO_STR_PTR s); + +/** Returns 1 if memory was allocated (and the String must be destroyed). */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, allocated)(const FIO_STR_PTR s); + +/** Binary comparison returns `1` if both strings are equal and `0` if not. */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, eq)(const FIO_STR_PTR str1, + const FIO_STR_PTR str2); -#ifndef FIO_ARRAY_EACH /** - * Iterates through the array using a `for` loop. - * - * Access the object with the pointer `pos`. The `pos` variable can be named - * however you please. - * - * Avoid editing the array during a FOR loop, although I hope it's possible, I - * wouldn't count on it. + * Returns the string's Risky Hash value. * - * **Note**: this variant supports automatic pointer tagging / untagging. + * Note: Hash algorithm might change without notice. */ -#define FIO_ARRAY_EACH(array_name, array, pos) \ - for (FIO_NAME(array_name, ____type_t) \ - *first___ai = NULL, \ - *pos = FIO_NAME(array_name, each_next)((array), &first___ai, NULL); \ - pos; \ - pos = FIO_NAME(array_name, each_next)((array), &first___ai, pos)) -#endif +FIO_IFUNC uint64_t FIO_NAME(FIO_STR_NAME, hash)(const FIO_STR_PTR s, + uint64_t seed); + +/* ***************************************************************************** +String API - Memory management +***************************************************************************** */ /** - * Returns a pointer to the (next) object in the array. + * Sets the new String size without reallocating any memory (limited by + * existing capacity). * - * Returns a pointer to the first object if `pos == NULL` and there are objects - * in the array. + * Returns the updated state of the String. * - * The first pointer is automatically set and it allows object insertions and - * memory effecting functions to be called from within the loop. + * Note: When shrinking, any existing data beyond the new size may be + * corrupted. + */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, resize)(FIO_STR_PTR s, + size_t size); + +/** + * Performs a best attempt at minimizing memory consumption. * - * If the object in `pos` (or an object before it) were removed, consider - * passing `pos-1` to the function, to avoid skipping any elements while - * looping. + * Actual effects depend on the underlying memory allocator and it's + * implementation. Not all allocators will free any memory. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, compact)(FIO_STR_PTR s); + +#if !FIO_STR_OPTIMIZE4IMMUTABILITY +/** + * Reserves (at least) `amount` of bytes for the string's data. * - * Returns the next object if both `first` and `pos` are valid. + * The reserved count includes used data. If `amount` is less than the current + * string length, the string will be truncated(!). * - * Returns NULL if `pos` was the last object or no object exist. + * Note: When optimized for immutability (`FIO_STR_SMALL`), this may corrupt the + * string length data. * - * Returns the first object if either `first` or `pos` are invalid. + * Make sure to call `resize` with the updated information once the editing is + * done. * + * Returns the updated state of the String. */ -FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, - each_next)(FIO_ARRAY_PTR ary, - FIO_ARRAY_TYPE **first, - FIO_ARRAY_TYPE *pos); - +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, reserve)(FIO_STR_PTR s, + size_t amount); +#define FIO_STR_RESERVE_NAME reserve +#else +/** INTERNAL - DO NOT USE! */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, __reserve)(FIO_STR_PTR s, + size_t amount); +#define FIO_STR_RESERVE_NAME __reserve +#endif /* ***************************************************************************** -Dynamic Arrays - embedded arrays +String API - UTF-8 State ***************************************************************************** */ -typedef struct { - /* start common header */ - /** the offset to the first item. */ - uint32_t start; - /** The offset to the first empty location the array. */ - uint32_t end; - /* end common header */ - FIO_ARRAY_TYPE embedded[]; -} FIO_NAME(FIO_ARRAY_NAME, ___embedded_s); -#define FIO_ARRAY2EMBEDDED(a) ((FIO_NAME(FIO_ARRAY_NAME, ___embedded_s) *)(a)) +/** Returns 1 if the String is UTF-8 valid and 0 if not. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_valid)(FIO_STR_PTR s); -#if FIO_ARRAY_ENABLE_EMBEDDED -#define FIO_ARRAY_IS_EMBEDDED(a) \ - ((sizeof(FIO_ARRAY_TYPE) + \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) <= \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) && \ - (((a)->start > (a)->end) || !(a)->ary)) -#define FIO_ARRAY_IS_EMBEDDED_PTR(ary, ptr) \ - ((sizeof(FIO_ARRAY_TYPE) + \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) <= \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) && \ - (uintptr_t)(ptr) > (uintptr_t)(ary) && \ - (uintptr_t)(ptr) < (uintptr_t)((ary) + 1)) -#define FIO_ARRAY_EMBEDDED_CAPA \ - ((sizeof(FIO_ARRAY_TYPE) + \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) > \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) \ - ? 0 \ - : ((sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) - \ - sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) / \ - sizeof(FIO_ARRAY_TYPE))) +/** Returns the String's length in UTF-8 characters. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_len)(FIO_STR_PTR s); -#else -#define FIO_ARRAY_IS_EMBEDDED(a) 0 -#define FIO_ARRAY_IS_EMBEDDED_PTR(ary, ptr) 0 -#define FIO_ARRAY_EMBEDDED_CAPA 0 +/** + * Takes a UTF-8 character selection information (UTF-8 position and length) + * and updates the same variables so they reference the raw byte slice + * information. + * + * If the String isn't UTF-8 valid up to the requested selection, than `pos` + * will be updated to `-1` otherwise values are always positive. + * + * The returned `len` value may be shorter than the original if there wasn't + * enough data left to accommodate the requested length. When a `len` value of + * `0` is returned, this means that `pos` marks the end of the String. + * + * Returns -1 on error and 0 on success. + */ +SFUNC int FIO_NAME(FIO_STR_NAME, + utf8_select)(FIO_STR_PTR s, intptr_t *pos, size_t *len); -#endif /* FIO_ARRAY_ENABLE_EMBEDDED */ /* ***************************************************************************** -Inlined functions +String API - Content Manipulation and Review ***************************************************************************** */ -/** Returns the number of elements in the Array. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, count)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: return ary->end - ary->start; - case 1: return ary->start; - } - return 0; -} -/** Returns the current, temporary, array capacity (it's dynamic). */ -FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, capa)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: return (uint32_t)ary->capa; - case 1: return FIO_ARRAY_EMBEDDED_CAPA; - } - return 0; -} +/** Writes data at the end of the String. */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write)(FIO_STR_PTR s, + const void *src, + size_t src_len); /** - * Returns a pointer to the C array containing the objects. + * Appends the `src` String to the end of the `dest` String. + * + * If `dest` is empty, the resulting Strings will be equal. */ -FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME2(FIO_ARRAY_NAME, ptr)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, NULL); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: return ary->ary + ary->start; - case 1: return FIO_ARRAY2EMBEDDED(ary)->embedded; - } - return NULL; -} +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, concat)(FIO_STR_PTR dest, + FIO_STR_PTR const src); -/** - * Returns 1 if the array is embedded, 0 if it has memory allocated and -1 on an - * error. - */ -FIO_IFUNC int FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, -1); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - return FIO_ARRAY_IS_EMBEDDED(ary); - (void)ary; /* if unused (never embedded) */ +/** Alias for fio_str_concat */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, join)(FIO_STR_PTR dest, + FIO_STR_PTR const src) { + return FIO_NAME(FIO_STR_NAME, concat)(dest, src); } /** - * Returns the value located at `index` (no copying is performed). + * Replaces the data in the String - replacing `old_len` bytes starting at + * `start_pos`, with the data at `src` (`src_len` bytes long). * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). + * Negative `start_pos` values are calculated backwards, `-1` == end of + * String. + * + * When `old_len` is zero, the function will insert the data at `start_pos`. + * + * If `src_len == 0` than `src` will be ignored and the data marked for + * replacement will be erased. */ -FIO_IFUNC FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, get)(FIO_ARRAY_PTR ary_, - int64_t index) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, FIO_ARRAY_TYPE_INVALID); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - FIO_ARRAY_TYPE *a; - size_t count; - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - a = ary->ary + ary->start; - count = ary->end - ary->start; - break; - case 1: - a = FIO_ARRAY2EMBEDDED(ary)->embedded; - count = ary->start; - break; - default: return FIO_ARRAY_TYPE_INVALID; - } - - if (index < 0) { - index += count; - if (index < 0) - return FIO_ARRAY_TYPE_INVALID; - } - if ((uint32_t)index >= count) - return FIO_ARRAY_TYPE_INVALID; - return a[index]; -} - -/* Returns a pointer to the (next) object in the array. */ -FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, - each_next)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE **first, - FIO_ARRAY_TYPE *pos) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, NULL); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - int64_t count; - FIO_ARRAY_TYPE *a; - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - count = ary->end - ary->start; - a = ary->ary + ary->start; - break; - case 1: - count = ary->start; - a = FIO_ARRAY2EMBEDDED(ary)->embedded; - break; - default: return NULL; - } - intptr_t i; - if (!count || !first) - return NULL; - if (!pos || !(*first) || (*first) > pos) { - i = -1; - } else { - i = (intptr_t)(pos - (*first)); - } - *first = a; - ++i; - if (i >= count) - return NULL; - return i + a; -} +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, replace)(FIO_STR_PTR s, + intptr_t start_pos, + size_t old_len, + const void *src, + size_t src_len); -/** Used internally for the EACH macro */ -typedef FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, ____type_t); +/** Writes data at the end of the String. See `fio_string_write2`. */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + __write2)(FIO_STR_PTR s, + const fio_string_write_s srcs[]); +#ifndef FIO_STR_WRITE2 +#define FIO_STR_WRITE2(str_name, dest, ...) \ + FIO_NAME(str_name, __write2)(dest, (fio_string_write_s[]){__VA_ARGS__, {0}}) +#endif /* ***************************************************************************** -Exported functions -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/* ***************************************************************************** -Helper macros +String API - Numerals ***************************************************************************** */ -#if FIO_ARRAY_EXPONENTIAL -#define FIO_ARRAY_ADD2CAPA(capa) (((capa) << 1) + FIO_ARRAY_PADDING) -#else -#define FIO_ARRAY_ADD2CAPA(capa) ((capa) + FIO_ARRAY_PADDING) -#endif + +/** Writes a number at the end of the String using normal base 10 notation. */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_i)(FIO_STR_PTR s, + int64_t num); + +/** Writes a number at the end of the String using Hex (base 16) notation. */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_hex)(FIO_STR_PTR s, + int64_t num); + +/* Writes a binary representation of `i` to the String */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_bin)(FIO_STR_PTR s, + int64_t num); /* ***************************************************************************** -Dynamic Arrays - internal helpers +String API - printf style support ***************************************************************************** */ -#define FIO_ARRAY_POS2ABS(ary, pos) \ - (pos >= 0 ? (ary->start + pos) : (ary->end - pos)) +/** + * Writes to the String using a vprintf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, vprintf)(FIO_STR_PTR s, + const char *format, + va_list argv); -#define FIO_ARRAY_AB_CT(cond, a, b) ((b) ^ ((0 - ((cond)&1)) & ((a) ^ (b)))) +/** + * Writes to the String using a printf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + printf)(FIO_STR_PTR s, const char *format, ...); -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_ARRAY_NAME, s)) -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_ARRAY_NAME, destroy)) /* ***************************************************************************** -Dynamic Arrays - implementation +String API - C / JSON escaping ***************************************************************************** */ -#ifndef FIO_REF_CONSTRUCTOR_ONLY -/* Allocates a new array object on the heap and initializes it's memory. */ -SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void) { - FIO_NAME(FIO_ARRAY_NAME, s) *a = - (FIO_NAME(FIO_ARRAY_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*a), 0); - if (!FIO_MEM_REALLOC_IS_SAFE_ && a) { - *a = (FIO_NAME(FIO_ARRAY_NAME, s))FIO_ARRAY_INIT; - } - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, s)); - return (FIO_ARRAY_PTR)FIO_PTR_TAG(a); -} - -/* Frees an array's internal data AND it's container! */ -SFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - FIO_NAME(FIO_ARRAY_NAME, destroy)(ary_); - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, s)); - FIO_MEM_FREE_(ary, sizeof(*ary)); -} -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ +/** + * Writes data at the end of the String, escaping the data using JSON semantics. + * + * The JSON semantic are common to many programming languages, promising a UTF-8 + * String while making it easy to read and copy the string during debugging. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_escape)(FIO_STR_PTR s, + const void *data, + size_t data_len); -/* Destroys any objects stored in the array and frees the internal state. */ -SFUNC void FIO_NAME(FIO_ARRAY_NAME, destroy)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - union { - FIO_NAME(FIO_ARRAY_NAME, s) a; - FIO_NAME(FIO_ARRAY_NAME, ___embedded_s) e; - } tmp = {.a = *ary}; - *ary = (FIO_NAME(FIO_ARRAY_NAME, s))FIO_ARRAY_INIT; +/** + * Writes an escaped data into the string after unescaping the data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_unescape)(FIO_STR_PTR s, + const void *escaped, + size_t len); - switch ( - FIO_NAME_BL(FIO_ARRAY_NAME, embedded)((FIO_ARRAY_PTR)FIO_PTR_TAG(&tmp))) { - case 0: -#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE - for (size_t i = tmp.a.start; i < tmp.a.end; ++i) { - FIO_ARRAY_TYPE_DESTROY(tmp.a.ary[i]); - } -#endif - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, destroy)); - FIO_MEM_FREE_(tmp.a.ary, tmp.a.capa * sizeof(*tmp.a.ary)); - return; - case 1: -#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE - while (tmp.e.start--) { - FIO_ARRAY_TYPE_DESTROY((tmp.e.embedded[tmp.e.start])); - } -#endif - return; - } - return; -} +/* ***************************************************************************** +String API - Base64 support +***************************************************************************** */ -/** Reserves a minimal capacity for the array. */ -SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, reserve)(FIO_ARRAY_PTR ary_, - int64_t capa_) { - FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - if (capa_ > UINT32_MAX || capa_ < ((int64_t)0LL - UINT32_MAX)) - return ary->capa; - uint32_t abs_capa = ((capa_ >= 0) ? (uint32_t)capa_ : (uint32_t)(0 - capa_)); - uint32_t capa; - FIO_ARRAY_TYPE *tmp; - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - abs_capa += ary->end - ary->start; - capa = FIO_ARRAY_SIZE2WORDS((abs_capa)); - if (abs_capa <= ary->capa) - return (uint32_t)ary->capa; - /* objects don't move, use only realloc */ - if ((capa_ >= 0) || (capa_ < 0 && ary->start > 0)) { - tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(ary->ary, - 0, - sizeof(*tmp) * capa, - sizeof(*tmp) * ary->end); - if (!tmp) - return (uint32_t)ary->capa; - if (!ary->ary) - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); - ary->capa = capa; - ary->ary = tmp; - return capa; - } else { /* moving objects, starting with a fresh piece of memory */ - tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*tmp) * capa, 0); - const uint32_t count = ary->end - ary->start; - if (!tmp) - return (uint32_t)ary->capa; - if (!ary->ary) - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); - if (capa_ >= 0) { /* copy items at beginning of memory stack */ - if (count) { - FIO_MEMCPY(tmp, ary->ary + ary->start, count * sizeof(*tmp)); - } - FIO_MEM_FREE_(ary->ary, sizeof(*ary->ary) * ary->capa); - *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ - .start = 0, - .end = count, - .capa = capa, - .ary = tmp, - }; - return capa; - } else { /* copy items at ending of memory stack */ - if (count) { - FIO_MEMCPY(tmp + (capa - count), - ary->ary + ary->start, - count * sizeof(*tmp)); - } - FIO_MEM_FREE_(ary->ary, sizeof(*ary->ary) * ary->capa); - *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ - .start = (capa - count), - .end = capa, - .capa = capa, - .ary = tmp, - }; - } - } - return capa; - case 1: - abs_capa += ary->start; - capa = FIO_ARRAY_SIZE2WORDS((abs_capa)); - if (abs_capa <= FIO_ARRAY_EMBEDDED_CAPA) - return FIO_ARRAY_EMBEDDED_CAPA; - tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*tmp) * capa, 0); - if (!tmp) - return FIO_ARRAY_EMBEDDED_CAPA; - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); - if (capa_ >= 0) { - /* copy items at beginning of memory stack */ - if (ary->start) { - FIO_MEMCPY(tmp, - FIO_ARRAY2EMBEDDED(ary)->embedded, - ary->start * sizeof(*tmp)); - } - *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ - .start = 0, - .end = ary->start, - .capa = capa, - .ary = tmp, - }; - return capa; - } - /* copy items at ending of memory stack */ - if (ary->start) { - FIO_MEMCPY(tmp + (capa - ary->start), - FIO_ARRAY2EMBEDDED(ary)->embedded, - ary->start * sizeof(*tmp)); - } - *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ - .start = (capa - ary->start), - .end = capa, - .capa = capa, - .ary = tmp, - }; - return capa; - default: return 0; - } -} +/** + * Writes data at the end of the String, encoding the data as Base64 encoded + * data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64enc)(FIO_STR_PTR s, + const void *data, + size_t data_len, + uint8_t url_encoded); /** - * Adds all the items in the `src` Array to the end of the `dest` Array. - * - * The `src` Array remain untouched. - * - * Returns `dest` on success or NULL on error (i.e., no memory). + * Writes decoded base64 data to the end of the String. */ -SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, concat)(FIO_ARRAY_PTR dest_, - FIO_ARRAY_PTR src_) { - FIO_PTR_TAG_VALID_OR_RETURN(dest_, (FIO_ARRAY_PTR)NULL); - FIO_PTR_TAG_VALID_OR_RETURN(src_, (FIO_ARRAY_PTR)NULL); - FIO_NAME(FIO_ARRAY_NAME, s) *dest = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), dest_); - FIO_NAME(FIO_ARRAY_NAME, s) *src = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), src_); - if (!dest || !src) - return dest_; - const uint32_t offset = FIO_NAME(FIO_ARRAY_NAME, count)(dest_); - const uint32_t added = FIO_NAME(FIO_ARRAY_NAME, count)(src_); - const uint32_t total = offset + added; - if (!added) - return dest_; +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64dec)(FIO_STR_PTR s, + const void *encoded, + size_t encoded_len); - if (total < offset || total + offset < total) - return NULL; /* item count overflow */ +/* ***************************************************************************** +String API - HTML escaping support +***************************************************************************** */ - const uint32_t capa = FIO_NAME(FIO_ARRAY_NAME, reserve)(dest_, added); +/** Writes HTML escaped data to a String. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_html_escape)(FIO_STR_PTR s, + const void *raw, + size_t len); - if (!FIO_ARRAY_IS_EMBEDDED(dest) && dest->start + total > capa) { - /* we need to move the existing items due to the offset */ - FIO_MEMMOVE(dest->ary, - dest->ary + dest->start, - (dest->end - dest->start) * sizeof(*dest->ary)); - dest->start = 0; - dest->end = offset; - } -#if FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE - /* copy data */ - FIO_MEMCPY(FIO_NAME2(FIO_ARRAY_NAME, ptr)(dest_) + offset, - FIO_NAME2(FIO_ARRAY_NAME, ptr)(src_), - added); -#else - { - FIO_ARRAY_TYPE *const a1 = FIO_NAME2(FIO_ARRAY_NAME, ptr)(dest_); - FIO_ARRAY_TYPE *const a2 = FIO_NAME2(FIO_ARRAY_NAME, ptr)(src_); - for (uint32_t i = 0; i < added; ++i) { - FIO_ARRAY_TYPE_CONCAT_COPY(a1[i + offset], a2[i]); - } - } -#endif /* FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE */ - /* update dest */ - if (!FIO_ARRAY_IS_EMBEDDED(dest)) { - dest->end += added; - return dest_; - } else - dest->start = total; - return dest_; -} +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_html_unescape)(FIO_STR_PTR s, + const void *escaped, + size_t len); + +/* ***************************************************************************** +String API - writing data from files to the String +***************************************************************************** */ /** - * Sets `index` to the value in `data`. + * Reads data from a file descriptor `fd` at offset `start_at` and pastes it's + * contents (or a slice of it) at the end of the String. If `limit == 0`, than + * the data will be read until EOF. * - * If `index` is negative, it will be counted from the end of the Array (-1 == - * last element). + * The file should be a regular file or the operation might fail (can't be used + * for sockets). * - * If `old` isn't NULL, the existing data will be copied to the location pointed - * to by `old` before the copy in the Array is destroyed. + * The file descriptor will remain open and should be closed manually. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfd)(FIO_STR_PTR s, + int fd, + intptr_t start_at, + intptr_t limit); +/** + * Opens the file `filename` and pastes it's contents (or a slice ot it) at + * the end of the String. If `limit == 0`, than the data will be read until + * EOF. * - * Returns a pointer to the new object, or NULL on error. + * If the file can't be located, opened or read, or if `start_at` is beyond + * the EOF position, NULL is returned in the state's `data` field. */ -SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, set)(FIO_ARRAY_PTR ary_, - int64_t index, - FIO_ARRAY_TYPE data, - FIO_ARRAY_TYPE *old) { - FIO_ARRAY_TYPE *a = NULL; - FIO_NAME(FIO_ARRAY_NAME, s) * ary; - uint32_t count; - uint8_t pre_existing = 1; +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfile)(FIO_STR_PTR s, + const char *filename, + intptr_t start_at, + intptr_t limit); +/* ***************************************************************************** +String API - Testing +***************************************************************************** */ +#ifdef FIO_STR_WRITE_TEST_FUNC +/** + * Tests the fio_str functionality. + */ +SFUNC void FIO_NAME_TEST(stl, FIO_STR_NAME)(void); +#endif +/* ***************************************************************************** - FIO_PTR_TAG_VALID_OR_GOTO(ary_, invalid); - ary = FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + String Implementation - if (index < 0) { - index += count; - if (index < 0) - goto negative_expansion; - } + IMPLEMENTATION - INLINED - if ((size_t)index > 0xFFFFFFFFULL) - goto invalid; - if ((uint32_t)index >= count) { - if ((uint32_t)index == count) - FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, FIO_ARRAY_ADD2CAPA(index)); - else - FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, (uint32_t)index + 1); - if (FIO_ARRAY_IS_EMBEDDED(ary)) - goto expand_embedded; - goto expansion; - } +***************************************************************************** */ - a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); +/* used here, but declared later (reference counter is static / global). */ -done: +SFUNC FIO_NAME(FIO_STR_NAME, s) * FIO_NAME(FIO_STR_NAME, __object_new)(void); +SFUNC void FIO_NAME(FIO_STR_NAME, __object_free)(FIO_NAME(FIO_STR_NAME, s) * s); +SFUNC int FIO_NAME(FIO_STR_NAME, __default_reallocate)(fio_str_info_s *dest, + size_t new_capa); +SFUNC int FIO_NAME(FIO_STR_NAME, + __default_copy_and_reallocate)(fio_str_info_s *dest, + size_t new_capa); +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free)(void *ptr, size_t capa); - /* copy / clear object */ - if (pre_existing) { - if (old) { - FIO_ARRAY_TYPE_COPY(old[0], a[index]); -#if FIO_ARRAY_DESTROY_AFTER_COPY - FIO_ARRAY_TYPE_DESTROY(a[index]); -#endif - } else { - FIO_ARRAY_TYPE_DESTROY(a[index]); - } - } else if (old) { - FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); - } - FIO_ARRAY_TYPE_COPY(a[index], FIO_ARRAY_TYPE_INVALID); - FIO_ARRAY_TYPE_COPY(a[index], data); - return a + index; +/* ***************************************************************************** +String Macro Helpers +***************************************************************************** */ -expansion: +#define FIO_STR_IS_SMALL(s) ((((s)->special & 1) | !(s)->buf)) +#define FIO_STR_SMALL_LEN(s) ((size_t)((s)->special >> 2)) +#define FIO_STR_SMALL_LEN_SET(s, l) \ + ((s)->special = (((s)->special & 2) | ((uint8_t)(l) << 2) | 1)) +#define FIO_STR_SMALL_CAPA(s) ((sizeof(*(s)) - 2) & 63) +#define FIO_STR_SMALL_DATA(s) ((char *)((s)->reserved)) - pre_existing = 0; - a = ary->ary; - { - uint8_t was_moved = 0; - /* test if we need to move objects to make room at the end */ - if (ary->start + (uint32_t)index >= ary->capa) { - FIO_MEMMOVE(ary->ary, ary->ary + ary->start, (count) * sizeof(*ary->ary)); - ary->start = 0; - ary->end = (uint32_t)index + 1; - was_moved = 1; - } - /* initialize memory in between objects */ - if (was_moved || !FIO_MEM_REALLOC_IS_SAFE_ || - !FIO_ARRAY_TYPE_INVALID_SIMPLE) { -#if FIO_ARRAY_TYPE_INVALID_SIMPLE - FIO_MEMSET(a + count, 0, ((uint32_t)index - count) * sizeof(*ary->ary)); -#else - for (size_t i = count; i <= (size_t)index; ++i) { - FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); - } -#endif - } - ary->end = (uint32_t)index + 1; - } - goto done; +#define FIO_STR_BIG_DATA(s) ((s)->buf) +#define FIO_STR_BIG_IS_DYNAMIC(s) (!((s)->special & 4)) +#define FIO_STR_BIG_SET_STATIC(s) ((s)->special |= 4) +#define FIO_STR_BIG_FREE_BUF(s) \ + (FIO_NAME(FIO_STR_NAME, __default_free)((s)->buf, FIO_STR_BIG_CAPA((s)))) -expand_embedded: - pre_existing = 0; - ary->start = (uint32_t)index + 1; - a = FIO_ARRAY2EMBEDDED(ary)->embedded; - goto done; +#define FIO_STR_IS_FROZEN(s) ((s)->special & 2) +#define FIO_STR_FREEZE_(s) ((s)->special |= 2) +#define FIO_STR_THAW_(s) ((s)->special ^= (uint8_t)2) -negative_expansion: - pre_existing = 0; - FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, (index - count)); - index = 0 - index; - if (index > ary->capa) - goto invalid; - if ((FIO_ARRAY_IS_EMBEDDED(ary))) - goto negative_expansion_embedded; - a = ary->ary; - if (index > (int32_t)ary->start) { - FIO_MEMMOVE(a + index, a + ary->start, count * sizeof(*a)); - ary->end = (uint32_t)index + count; - ary->start = (uint32_t)index; - } - index = ary->start - (uint32_t)index; - if ((uint32_t)(index + 1) < ary->start) { -#if FIO_ARRAY_TYPE_INVALID_SIMPLE - FIO_MEMSET(a + index, 0, (ary->start - index) * (sizeof(*a))); -#else - for (size_t i = index; i < (size_t)ary->start; ++i) { - FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); - } -#endif - } - ary->start = (uint32_t)index; - goto done; +#if FIO_STR_OPTIMIZE4IMMUTABILITY -negative_expansion_embedded: - a = FIO_ARRAY2EMBEDDED(ary)->embedded; - FIO_MEMMOVE(a + index, a, count * count * sizeof(*a)); -#if FIO_ARRAY_TYPE_INVALID_SIMPLE - FIO_MEMSET(a, 0, index * (sizeof(a))); +#define FIO_STR_BIG_LEN(s) \ + ((sizeof(void *) == 4) \ + ? (((uint32_t)(s)->reserved[0]) | ((uint32_t)(s)->reserved[1] << 8) | \ + ((uint32_t)(s)->reserved[2] << 16)) \ + : (((uint64_t)(s)->reserved[0]) | ((uint64_t)(s)->reserved[1] << 8) | \ + ((uint64_t)(s)->reserved[2] << 16) | \ + ((uint64_t)(s)->reserved[3] << 24) | \ + ((uint64_t)(s)->reserved[4] << 32) | \ + ((uint64_t)(s)->reserved[5] << 40) | \ + ((uint64_t)(s)->reserved[6] << 48))) +#define FIO_STR_BIG_LEN_SET(s, l) \ + do { \ + if (sizeof(void *) == 4) { \ + if (!((l) & ((~(uint32_t)0) << 24))) { \ + (s)->reserved[0] = (l)&0xFF; \ + (s)->reserved[1] = ((uint32_t)(l) >> 8) & 0xFF; \ + (s)->reserved[2] = ((uint32_t)(l) >> 16) & 0xFF; \ + } else { \ + FIO_LOG_ERROR("facil.io small string length error - too long"); \ + (s)->reserved[0] = 0xFF; \ + (s)->reserved[1] = 0xFF; \ + (s)->reserved[2] = 0xFF; \ + } \ + } else { \ + if (!((l) & ((~(uint64_t)0) << 56))) { \ + (s)->reserved[0] = (l)&0xFF; \ + (s)->reserved[1] = ((uint64_t)(l) >> 8) & 0xFF; \ + (s)->reserved[2] = ((uint64_t)(l) >> 16) & 0xFF; \ + (s)->reserved[3] = ((uint64_t)(l) >> 24) & 0xFF; \ + (s)->reserved[4] = ((uint64_t)(l) >> 32) & 0xFF; \ + (s)->reserved[5] = ((uint64_t)(l) >> 40) & 0xFF; \ + (s)->reserved[6] = ((uint64_t)(l) >> 48) & 0xFF; \ + } else { \ + FIO_LOG_ERROR("facil.io small string length error - too long"); \ + (s)->reserved[0] = 0xFF; \ + (s)->reserved[1] = 0xFF; \ + (s)->reserved[2] = 0xFF; \ + (s)->reserved[3] = 0xFF; \ + (s)->reserved[4] = 0xFF; \ + (s)->reserved[5] = 0xFF; \ + (s)->reserved[6] = 0xFF; \ + } \ + } \ + } while (0) +#define FIO_STR_BIG_CAPA(s) fio_string_capa4len(FIO_STR_BIG_LEN((s))) +#define FIO_STR_BIG_CAPA_SET(s, capa) #else - for (size_t i = 0; i < (size_t)index; ++i) { - FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); - } +#define FIO_STR_BIG_LEN(s) ((s)->len) +#define FIO_STR_BIG_LEN_SET(s, l) ((s)->len = (l)) +#define FIO_STR_BIG_CAPA(s) ((s)->capa) +#define FIO_STR_BIG_CAPA_SET(s, capa) (FIO_STR_BIG_CAPA(s) = (capa)) #endif - index = 0; - goto done; -invalid: - FIO_ARRAY_TYPE_DESTROY(data); - if (old) { - FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); - } +/* ***************************************************************************** +String Information Round-tripping +***************************************************************************** */ - return a; +/** Returns the String's complete state (capacity, length and pointer). */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, info)(const FIO_STR_PTR s_) { + fio_str_info_s r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, r); + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s)) + r = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), + FIO_STR_SMALL_LEN(s), + FIO_STR_SMALL_CAPA(s)); + else + r = FIO_STR_INFO3(FIO_STR_BIG_DATA(s), + FIO_STR_BIG_LEN(s), + FIO_STR_BIG_CAPA(s)); + r.capa &= ((size_t)0ULL - (!FIO_STR_IS_FROZEN(s))); + return r; } -/** - * Returns the index of the object or (uint32_t)-1 if the object wasn't found. - * - * If `start_at` is negative (i.e., -1), than seeking will be performed in - * reverse, where -1 == last index (-2 == second to last, etc'). - */ -SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, find)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE data, - int64_t start_at) { - FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); - if (!a) - return -1; - size_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); - if (start_at >= 0) { - /* seek forwards */ - if ((uint32_t)start_at >= count) - start_at = (int32_t)count; - while ((uint32_t)start_at < count) { - if (FIO_ARRAY_TYPE_CMP(a[start_at], data)) - return (uint32_t)start_at; - ++start_at; - } - } else { - /* seek backwards */ - if (start_at + (int32_t)count < 0) - return -1; - count += start_at; - count += 1; - while (count--) { - if (FIO_ARRAY_TYPE_CMP(a[count], data)) - return (uint32_t)count; - } +/** Returns the String's partial state (length and pointer). */ +FIO_IFUNC fio_buf_info_s FIO_NAME(FIO_STR_NAME, buf)(const FIO_STR_PTR s_) { + fio_buf_info_s r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, r); + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s)) + r = FIO_BUF_INFO2(FIO_STR_SMALL_DATA(s), FIO_STR_SMALL_LEN(s)); + else + r = FIO_BUF_INFO2(FIO_STR_BIG_DATA(s), FIO_STR_BIG_LEN(s)); + return r; +} + +/* Internal(!): updated String data according to `info`. */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, __info_update)(const FIO_STR_PTR s_, + fio_str_info_s info) { + /* internally used function, tagging already validated. */ + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (info.buf == FIO_STR_SMALL_DATA(s)) { + s->special |= 1; + FIO_STR_SMALL_LEN_SET(s, info.len); + return; } - return -1; + s->special = 0; + FIO_STR_BIG_LEN_SET(s, info.len); + FIO_STR_BIG_CAPA_SET(s, info.capa); + s->buf = info.buf; } -/** - * Removes an object from the array, MOVING all the other objects to prevent - * "holes" in the data. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. - * - * Returns 0 on success and -1 on error. - */ -SFUNC int FIO_NAME(FIO_ARRAY_NAME, remove)(FIO_ARRAY_PTR ary_, - int64_t index, - FIO_ARRAY_TYPE *old) { - FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - size_t count; - if (!a) - goto invalid; - count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); +/* Internal(!): updated String data according to `info`. */ +FIO_IFUNC fio_string_realloc_fn FIO_NAME(FIO_STR_NAME, + __realloc_func)(const FIO_STR_PTR s_) { + fio_string_realloc_fn options[] = { + FIO_NAME(FIO_STR_NAME, __default_reallocate), + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate), + }; + /* internally used function, tagging already validated. */ + FIO_NAME(FIO_STR_NAME, s) *s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + return options[FIO_STR_IS_SMALL(s) | !FIO_STR_BIG_IS_DYNAMIC(s)]; +} - if (index < 0) { - index += count; - if (index < 0) { - FIO_LOG_WARNING( - FIO_MACRO2STR(FIO_NAME(FIO_ARRAY_NAME, - remove)) " called with a negative index lower " - "than the element count."); - goto invalid; - } - } - if ((uint32_t)index >= count) - goto invalid; - if (!index) { - FIO_NAME(FIO_ARRAY_NAME, shift)(ary_, old); - return 0; - } - if ((uint32_t)index + 1 == count) { - FIO_NAME(FIO_ARRAY_NAME, pop)(ary_, old); - return 0; - } +/* ***************************************************************************** +String Constructors (inline) +***************************************************************************** */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY - if (old) { - FIO_ARRAY_TYPE_COPY(*old, a[index]); -#if FIO_ARRAY_DESTROY_AFTER_COPY - FIO_ARRAY_TYPE_DESTROY(a[index]); -#endif - } else { - FIO_ARRAY_TYPE_DESTROY(a[index]); +/** Allocates a new String object on the heap. */ +FIO_IFUNC FIO_STR_PTR FIO_NAME(FIO_STR_NAME, new)(void) { + FIO_NAME(FIO_STR_NAME, s) *const s = FIO_NAME(FIO_STR_NAME, __object_new)(); + if (!FIO_MEM_REALLOC_IS_SAFE_ && s) { + *s = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT; } - - if ((uint32_t)(index + 1) < count) { - FIO_MEMMOVE(a + index, a + index + 1, (count - (index + 1)) * sizeof(*a)); +#ifdef DEBUG + { + FIO_NAME(FIO_STR_NAME, s) tmp = {0}; + FIO_ASSERT(!FIO_MEMCMP(&tmp, s, sizeof(tmp)), + "new " FIO_MACRO2STR( + FIO_NAME(FIO_STR_NAME, s)) " object not initialized!"); } - FIO_ARRAY_TYPE_COPY((a + (count - 1))[0], FIO_ARRAY_TYPE_INVALID); - - if (FIO_ARRAY_IS_EMBEDDED(ary)) - goto embedded; - --ary->end; - return 0; - -embedded: - --ary->start; - return 0; +#endif + return (FIO_STR_PTR)FIO_PTR_TAG(s); +} -invalid: - if (old) { - FIO_ARRAY_TYPE_COPY(*old, FIO_ARRAY_TYPE_INVALID); +/** Destroys the string and frees the container (if allocated with `new`). */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, free)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (!FIO_STR_IS_SMALL(s) && FIO_STR_BIG_IS_DYNAMIC(s)) { + FIO_STR_BIG_FREE_BUF(s); } - return -1; + FIO_NAME(FIO_STR_NAME, __object_free)(s); } +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + /** - * Removes all occurrences of an object from the array (if any), MOVING all the - * existing objects to prevent "holes" in the data. + * Frees the String's resources and reinitializes the container. * - * Returns the number of items removed. + * Note: if the container isn't allocated on the stack, it should be freed + * separately using the appropriate `free` function. */ -SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, remove2)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE data) { - size_t c = 0; - FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - size_t count; - if (!a) - return (uint32_t)c; - count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); - - size_t i = 0; - while ((i + c) < count) { - if (!(FIO_ARRAY_TYPE_CMP(a[i + c], data))) { - a[i] = a[i + c]; - ++i; - continue; - } - FIO_ARRAY_TYPE_DESTROY(a[i + c]); - ++c; - } - if (c && FIO_MEM_REALLOC_IS_SAFE_) { - /* keep memory zeroed out */ - FIO_MEMSET(a + i, 0, sizeof(*a) * c); - } - if (!FIO_ARRAY_IS_EMBEDDED_PTR(ary, a)) { - ary->end = (uint32_t)(ary->start + i); - return (uint32_t)c; +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, destroy)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (!FIO_STR_IS_SMALL(s) && FIO_STR_BIG_IS_DYNAMIC(s)) { + FIO_STR_BIG_FREE_BUF(s); } - ary->start = (uint32_t)i; - return (uint32_t)c; + *s = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT; } -/** Attempts to lower the array's memory consumption. */ -SFUNC void FIO_NAME(FIO_ARRAY_NAME, compact)(FIO_ARRAY_PTR ary_) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - size_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); - FIO_ARRAY_TYPE *tmp = NULL; - - if (count <= FIO_ARRAY_EMBEDDED_CAPA) - goto re_embed; - - tmp = (FIO_ARRAY_TYPE *) - FIO_MEM_REALLOC_(NULL, 0, (ary->end - ary->start) * sizeof(*tmp), 0); - if (!tmp) - return; - FIO_MEMCPY(tmp, ary->ary + ary->start, count * sizeof(*ary->ary)); - FIO_MEM_FREE_(ary->ary, ary->capa * sizeof(*ary->ary)); - *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ - .start = 0, - .end = (ary->end - ary->start), - .capa = (ary->end - ary->start), - .ary = tmp, - }; - return; +/** + * Returns a C string with the existing data, re-initializing the String. + * + * Note: the String data is removed from the container, but the container + * isn't freed. + * + * Returns NULL if there's no String data. + */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, detach)(FIO_STR_PTR s_) { + char *data = NULL; + FIO_PTR_TAG_VALID_OR_RETURN(s_, data); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); -re_embed: - if (!FIO_ARRAY_IS_EMBEDDED(ary)) { - tmp = ary->ary; - uint32_t offset = ary->start; - size_t old_capa = ary->capa; - *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ - .start = (uint32_t)count, - }; - if (count) { - FIO_MEMCPY(FIO_ARRAY2EMBEDDED(ary)->embedded, - tmp + offset, - count * sizeof(*tmp)); + if (FIO_STR_IS_SMALL(s)) { + if (FIO_STR_SMALL_LEN(s)) { /* keep these ifs apart */ + fio_str_info_s cpy = + FIO_STR_INFO2(FIO_STR_SMALL_DATA(s), FIO_STR_SMALL_LEN(s)); + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&cpy, cpy.len); + data = cpy.buf; } - if (tmp) { - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, destroy)); - FIO_MEM_FREE_(tmp, sizeof(*tmp) * old_capa); - (void)old_capa; /* if unused */ + } else { + if (FIO_STR_BIG_IS_DYNAMIC(s)) { + data = FIO_STR_BIG_DATA(s); + } else if (FIO_STR_BIG_LEN(s)) { + fio_str_info_s cpy = + FIO_STR_INFO2(FIO_STR_BIG_DATA(s), FIO_STR_BIG_LEN(s)); + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&cpy, cpy.len); + data = cpy.buf; } } - return; + *s = (FIO_NAME(FIO_STR_NAME, s)){0}; + return data; } + /** - * Pushes an object to the end of the Array. Returns NULL on error. + * Performs a best attempt at minimizing memory consumption. + * + * Actual effects depend on the underlying memory allocator and it's + * implementation. Not all allocators will free any memory. */ -SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, push)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE data) { - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - if (ary->end == ary->capa) { - if (!ary->start) { - if (FIO_NAME(FIO_ARRAY_NAME, - reserve)(ary_, (uint32_t)FIO_ARRAY_ADD2CAPA(ary->capa)) == - ary->end) - goto invalid; - } else { - const uint32_t new_start = (ary->start >> 2); - const uint32_t count = ary->end - ary->start; - if (count) - FIO_MEMMOVE(ary->ary + new_start, - ary->ary + ary->start, - count * sizeof(*ary->ary)); - ary->end = count + new_start; - ary->start = new_start; - } - } - FIO_ARRAY_TYPE_COPY(ary->ary[ary->end], data); - return ary->ary + (ary->end++); - - case 1: - if (ary->start == FIO_ARRAY_EMBEDDED_CAPA) - goto needs_memory_embedded; - FIO_ARRAY_TYPE_COPY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start], data); - return FIO_ARRAY2EMBEDDED(ary)->embedded + (ary->start++); - } -invalid: - FIO_ARRAY_TYPE_DESTROY(data); - return NULL; - -needs_memory_embedded: - if (FIO_NAME(FIO_ARRAY_NAME, - reserve)(ary_, FIO_ARRAY_ADD2CAPA(FIO_ARRAY_EMBEDDED_CAPA)) == - FIO_ARRAY_EMBEDDED_CAPA) - goto invalid; - FIO_ARRAY_TYPE_COPY(ary->ary[ary->end], data); - return ary->ary + (ary->end++); +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, compact)(FIO_STR_PTR s_) { +#if FIO_STR_OPTIMIZE4IMMUTABILITY + (void)s_; +#else + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s) || !FIO_STR_BIG_IS_DYNAMIC(s) || + fio_string_capa4len(FIO_NAME(FIO_STR_NAME, len)(s_)) >= + FIO_NAME(FIO_STR_NAME, capa)(s_)) + return; + FIO_NAME(FIO_STR_NAME, s) tmp = FIO_STR_INIT; + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + FIO_NAME(FIO_STR_NAME, init_copy) + ((FIO_STR_PTR)FIO_PTR_TAG(&tmp), i.buf, i.len); + FIO_NAME(FIO_STR_NAME, destroy)(s_); + *s = tmp; +#endif } +/* ***************************************************************************** +String Initialization (inline) +***************************************************************************** */ + /** - * Removes an object from the end of the Array. + * Initializes the container with the provided static / constant string. * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. + * The string will be copied to the container **only** if it will fit in the + * container itself. Otherwise, the supplied pointer will be used as is and it + * should remain valid until the string is destroyed. * - * Returns -1 on error (Array is empty) and 0 on success. + * The final string can be safely be destroyed (using the `destroy` function). */ -SFUNC int FIO_NAME(FIO_ARRAY_NAME, pop)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE *old) { - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - if (ary->end == ary->start) - return -1; - --ary->end; - if (old) { - FIO_ARRAY_TYPE_COPY(*old, ary->ary[ary->end]); -#if FIO_ARRAY_DESTROY_AFTER_COPY - FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->end]); -#endif - } else { - FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->end]); - } - return 0; - case 1: - if (!ary->start) - return -1; - --ary->start; - if (old) { - FIO_ARRAY_TYPE_COPY(*old, FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); -#if FIO_ARRAY_DESTROY_AFTER_COPY - FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); -#endif - } else { - FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); - } - FIO_MEMSET(FIO_ARRAY2EMBEDDED(ary)->embedded + ary->start, - 0, - sizeof(*ary->ary)); - return 0; +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_const)(FIO_STR_PTR s_, + const char *str, + size_t len) { + fio_str_info_s i = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, i); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + *s = (FIO_NAME(FIO_STR_NAME, s)){0}; + if (len < FIO_STR_SMALL_CAPA(s)) { + FIO_STR_SMALL_LEN_SET(s, len); + if (len && str) + FIO_MEMCPY(FIO_STR_SMALL_DATA(s), str, len); + FIO_STR_SMALL_DATA(s)[len] = 0; + + i = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), len, FIO_STR_SMALL_CAPA(s)); + return i; } - if (old) - FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); - return -1; + FIO_STR_BIG_DATA(s) = (char *)str; + FIO_STR_BIG_LEN_SET(s, len); + FIO_STR_BIG_CAPA_SET(s, len); + FIO_STR_BIG_SET_STATIC(s); + i = FIO_STR_INFO3(FIO_STR_BIG_DATA(s), len, 0); + return i; } /** - * Unshifts an object to the beginning of the Array. Returns -1 on error. + * Initializes the container with the provided dynamic string. * - * This could be expensive, causing `memmove`. + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). */ -SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, unshift)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE data) { - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - if (!ary->start) { - if (ary->end == ary->capa) { - FIO_NAME(FIO_ARRAY_NAME, reserve) - (ary_, (-1 - (int32_t)FIO_ARRAY_ADD2CAPA(ary->capa))); - if (!ary->start) - goto invalid; - } else { - const uint32_t new_end = - (uint32_t)(ary->capa - ((ary->capa - ary->end) >> 2)); - const uint32_t count = (uint32_t)(ary->end - ary->start); - const uint32_t new_start = (uint32_t)(new_end - count); - if (count) - FIO_MEMMOVE(ary->ary + new_start, - ary->ary + ary->start, - count * sizeof(*ary->ary)); - ary->end = new_end; - ary->start = new_start; - } - } - FIO_ARRAY_TYPE_COPY(ary->ary[--ary->start], data); - return ary->ary + ary->start; +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy)(FIO_STR_PTR s_, + const char *str, + size_t len) { + fio_str_info_s i = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, i); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + *s = (FIO_NAME(FIO_STR_NAME, s)){0}; + if (len < FIO_STR_SMALL_CAPA(s)) { + FIO_STR_SMALL_LEN_SET(s, len); + if (len && str) + FIO_MEMCPY(FIO_STR_SMALL_DATA(s), str, len); + FIO_STR_SMALL_DATA(s)[len] = 0; - case 1: - if (ary->start == FIO_ARRAY_EMBEDDED_CAPA) - goto needs_memory_embed; - if (ary->start) - FIO_MEMMOVE(FIO_ARRAY2EMBEDDED(ary)->embedded + 1, - FIO_ARRAY2EMBEDDED(ary)->embedded, - sizeof(*ary->ary) * ary->start); - ++ary->start; - FIO_ARRAY_TYPE_COPY(FIO_ARRAY2EMBEDDED(ary)->embedded[0], data); - return FIO_ARRAY2EMBEDDED(ary)->embedded; + i = FIO_STR_INFO3(FIO_STR_SMALL_DATA(s), len, FIO_STR_SMALL_CAPA(s)); + return i; } -invalid: - FIO_ARRAY_TYPE_DESTROY(data); - return NULL; - -needs_memory_embed: - if (FIO_NAME(FIO_ARRAY_NAME, reserve)( - ary_, - (-1 - (int32_t)FIO_ARRAY_ADD2CAPA(FIO_ARRAY_EMBEDDED_CAPA))) == - FIO_ARRAY_EMBEDDED_CAPA) - goto invalid; - FIO_ARRAY_TYPE_COPY(ary->ary[--ary->start], data); - return ary->ary + ary->start; + i = FIO_STR_INFO2((char *)str, len); + FIO_NAME(FIO_STR_NAME, __default_copy_and_reallocate)(&i, len); + FIO_STR_BIG_CAPA_SET(s, i.capa); + FIO_STR_BIG_DATA(s) = i.buf; + FIO_STR_BIG_LEN_SET(s, len); + return i; } /** - * Removes an object from the beginning of the Array. - * - * If `old` is set, the data is copied to the location pointed to by `old` - * before the data in the array is destroyed. + * Initializes the container with a copy of an existing String object. * - * Returns -1 on error (Array is empty) and 0 on success. + * The string is always copied and the final string must be destroyed (using the + * `destroy` function). */ -SFUNC int FIO_NAME(FIO_ARRAY_NAME, shift)(FIO_ARRAY_PTR ary_, - FIO_ARRAY_TYPE *old) { +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_STR_PTR dest, + FIO_STR_PTR src) { + fio_str_info_s i; + i = FIO_NAME(FIO_STR_NAME, info)(src); + i = FIO_NAME(FIO_STR_NAME, init_copy)(dest, i.buf, i.len); + return i; +} - FIO_NAME(FIO_ARRAY_NAME, s) *ary = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); +/* ***************************************************************************** +String Information (inline) +***************************************************************************** */ - switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { - case 0: - if (ary->end == ary->start) - return -1; - if (old) { - FIO_ARRAY_TYPE_COPY(*old, ary->ary[ary->start]); -#if FIO_ARRAY_DESTROY_AFTER_COPY - FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->start]); -#endif - } else { - FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->start]); - } - ++ary->start; - return 0; - case 1: - if (!ary->start) - return -1; - if (old) { - FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY2EMBEDDED(ary)->embedded[0]); -#if FIO_ARRAY_DESTROY_AFTER_COPY - FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[0]); -#endif - } else { - FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[0]); - } - --ary->start; - if (ary->start) - FIO_MEMMOVE(FIO_ARRAY2EMBEDDED(ary)->embedded, - FIO_ARRAY2EMBEDDED(ary)->embedded + - FIO_ARRAY2EMBEDDED(ary)->start, - FIO_ARRAY2EMBEDDED(ary)->start * - sizeof(*FIO_ARRAY2EMBEDDED(ary)->embedded)); - FIO_MEMSET(FIO_ARRAY2EMBEDDED(ary)->embedded + ary->start, - 0, - sizeof(*ary->ary)); - return 0; - } - if (old) - FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); - return -1; +/** Returns a pointer (`char *`) to the String's content. */ +FIO_IFUNC char *FIO_NAME(FIO_STR_NAME, ptr)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, NULL); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + char *results[] = {(FIO_STR_BIG_DATA(s)), (FIO_STR_SMALL_DATA(s))}; + return results[FIO_STR_IS_SMALL(s)]; +} + +/** Returns the String's length in bytes. */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, len)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + size_t results[] = {(FIO_STR_BIG_LEN(s)), (FIO_STR_SMALL_LEN(s))}; + return results[FIO_STR_IS_SMALL(s)]; +} + +/** Returns the String's existing capacity (total used & available memory). */ +FIO_IFUNC size_t FIO_NAME(FIO_STR_NAME, capa)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + if (FIO_STR_IS_SMALL(s)) + return FIO_STR_SMALL_CAPA(s); + if (FIO_STR_BIG_IS_DYNAMIC(s)) + return FIO_STR_BIG_CAPA(s); + return 0; } /** - * Iteration using a callback for each entry in the array. - * - * The callback task function must accept an the entry data as well as an opaque - * user pointer. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. + * Sets the new String size without reallocating any memory (limited by + * existing capacity). */ -IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, - each)(FIO_ARRAY_PTR ary_, - int (*task)(FIO_NAME(FIO_ARRAY_NAME, each_s) * - info), - void *udata, - int64_t start_at) { - FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); - if (!a) - return (uint32_t)-1; +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, resize)(FIO_STR_PTR s_, + size_t size) { + fio_str_info_s i = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, i); + i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) { + return i; + } + /* resize may be used to reserve memory in advance while setting size */ + if (i.capa > size) { + i.len = size; + i.buf[i.len] = 0; + } else { + fio_string_write(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + NULL, + size - i.len); + } + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); - uint32_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + return i; +} - if (start_at < 0) { - start_at = count - start_at; - if (start_at < 0) - start_at = 0; - } +/** + * Prevents further manipulations to the String's content. + */ +FIO_IFUNC void FIO_NAME(FIO_STR_NAME, freeze)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(s_); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + FIO_STR_FREEZE_(s); +} - if (!a || !task) - return (uint32_t)-1; +/** + * Returns true if the string is frozen. + */ +FIO_IFUNC uint8_t FIO_NAME_BL(FIO_STR_NAME, frozen)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 1); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + return FIO_STR_IS_FROZEN(s); +} - if ((uint32_t)start_at >= count) - return (uint32_t)count; +/** Returns 1 if memory was allocated and (the String must be destroyed). */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, allocated)(const FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + return (!FIO_STR_IS_SMALL(s) & FIO_STR_BIG_IS_DYNAMIC(s)); +} - FIO_NAME(FIO_ARRAY_NAME, each_s) - e = { - .parent = ary_, - .index = (uint64_t)start_at, - .task = task, - .udata = udata, - }; +/** + * Binary comparison returns `1` if both strings are equal and `0` if not. + */ +FIO_IFUNC int FIO_NAME_BL(FIO_STR_NAME, eq)(const FIO_STR_PTR str1_, + const FIO_STR_PTR str2_) { + if (str1_ == str2_) + return 1; + FIO_PTR_TAG_VALID_OR_RETURN(str1_, 0); + FIO_PTR_TAG_VALID_OR_RETURN(str2_, 0); + fio_buf_info_s s1 = FIO_NAME(FIO_STR_NAME, buf)(str1_); + fio_buf_info_s s2 = FIO_NAME(FIO_STR_NAME, buf)(str2_); + return FIO_BUF_INFO_IS_EQ(s1, s2); +} - while ((uint32_t)e.index < FIO_NAME(FIO_ARRAY_NAME, count)(ary_)) { - a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); - e.value = a[e.index]; - int r = e.task(&e); - ++e.index; - if (r == -1) { - return (uint32_t)(e.index); - } - } - return (uint32_t)e.index; +/** + * Returns the string's Risky Hash value. + * + * Note: Hash algorithm might change without notice. + */ +FIO_IFUNC uint64_t FIO_NAME(FIO_STR_NAME, hash)(const FIO_STR_PTR s_, + uint64_t seed) { + fio_buf_info_s i = FIO_NAME(FIO_STR_NAME, buf)(s_); + return fio_risky_hash((void *)i.buf, i.len, seed); } /* ***************************************************************************** -Dynamic Arrays - test +String API - Content Manipulation and Review (inline) ***************************************************************************** */ -#ifdef FIO_TEST_ALL - -/* make suer the functions are defined for the testing */ -#ifdef FIO_REF_CONSTRUCTOR_ONLY -IFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void); -IFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary); -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ -#define FIO_ARRAY_TEST_OBJ_SET(dest, val) \ - FIO_MEMSET(&(dest), (int)(val), sizeof(FIO_ARRAY_TYPE)) -#define FIO_ARRAY_TEST_OBJ_IS(val) \ - (!FIO_MEMCMP(&o, \ - FIO_MEMSET(&v, (int)(val), sizeof(v)), \ - sizeof(FIO_ARRAY_TYPE))) +/** Writes data at the end of the String. */ +FIO_IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write)(FIO_STR_PTR s_, + const void *src, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), src, len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -FIO_SFUNC int FIO_NAME_TEST(stl, FIO_NAME(FIO_ARRAY_NAME, test_task))( - FIO_NAME(FIO_ARRAY_NAME, each_s) * i) { - struct data_s { - int i; - int va[]; - } *d = (struct data_s *)i->udata; - FIO_ARRAY_TYPE v; +/* ***************************************************************************** - FIO_ARRAY_TEST_OBJ_SET(v, d->va[d->i]); - ++d->i; - if (d->va[d->i + 1]) - return 0; - return -1; -} -FIO_SFUNC void FIO_NAME_TEST(stl, FIO_ARRAY_NAME)(void) { - FIO_ARRAY_TYPE o; - FIO_ARRAY_TYPE v; - FIO_NAME(FIO_ARRAY_NAME, s) a_on_stack = FIO_ARRAY_INIT; - FIO_ARRAY_PTR a_array[2]; - a_array[0] = (FIO_ARRAY_PTR)FIO_PTR_TAG((&a_on_stack)); - a_array[1] = FIO_NAME(FIO_ARRAY_NAME, new)(); - FIO_ASSERT_ALLOC(a_array[1]); - /* perform test twice, once for an array on the stack and once for allocate */ - for (int selector = 0; selector < 2; ++selector) { - FIO_ARRAY_PTR a = a_array[selector]; - fprintf(stderr, - "* Testing dynamic arrays on the %s (" FIO_MACRO2STR( - FIO_NAME(FIO_ARRAY_NAME, - s)) ").\n" - " This type supports %zu embedded items\n", - (selector ? "heap" : "stack"), - FIO_ARRAY_EMBEDDED_CAPA); - /* Test start here */ + String Implementation - /* test push */ - for (int i = 0; i < (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; ++i) { - FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); - o = *FIO_NAME(FIO_ARRAY_NAME, push)(a, o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "push failed (%d)", i); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "push-get cycle failed (%d)", i); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), - "get with -1 returned wrong result (%d)", - i); - } - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - FIO_ARRAY_EMBEDDED_CAPA + 3, - "push didn't update count correctly (%d != %d)", - FIO_NAME(FIO_ARRAY_NAME, count)(a), - (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3); + IMPLEMENTATION - /* test pop */ - for (int i = (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; i--;) { - FIO_NAME(FIO_ARRAY_NAME, pop)(a, &o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((i + 1)), - "pop value error failed (%d)", - i); - } - FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), - "pop didn't pop all elements?"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, pop)(a, &o), - "pop for empty array should return an error."); - /* test compact with zero elements */ - FIO_NAME(FIO_ARRAY_NAME, compact)(a); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, - "compact zero elementes didn't make array embedded?"); +***************************************************************************** */ - /* test unshift */ - for (int i = (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; i--;) { - FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); - o = *FIO_NAME(FIO_ARRAY_NAME, unshift)(a, o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "shift failed (%d)", i); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), - "unshift-get cycle failed (%d)", - i); - int64_t negative_index = 0 - (((int)(FIO_ARRAY_EMBEDDED_CAPA) + 3) - i); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, negative_index); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), - "get with %d returned wrong result.", - negative_index); - } - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - FIO_ARRAY_EMBEDDED_CAPA + 3, - "unshift didn't update count correctly (%d != %d)", - FIO_NAME(FIO_ARRAY_NAME, count)(a), - (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3); +/* ***************************************************************************** +External functions +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - /* test shift */ - for (int i = 0; i < (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; ++i) { - FIO_NAME(FIO_ARRAY_NAME, shift)(a, &o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((i + 1)), - "shift value error failed (%d)", - i); - } - FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), - "shift didn't shift all elements?"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, shift)(a, &o), - "shift for empty array should return an error."); +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_STR_NAME, s)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_STR_NAME, destroy)) - /* test set from embedded? array */ - FIO_NAME(FIO_ARRAY_NAME, compact)(a); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, - "compact zero elementes didn't make array embedded (2)?"); - FIO_ARRAY_TEST_OBJ_SET(o, 1); - FIO_NAME(FIO_ARRAY_NAME, push)(a, o); - if (FIO_ARRAY_EMBEDDED_CAPA) { - FIO_ARRAY_TEST_OBJ_SET(o, 1); - FIO_NAME(FIO_ARRAY_NAME, set)(a, FIO_ARRAY_EMBEDDED_CAPA, o, &o); - FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), - "set overflow from embedded array should reset `old`"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - FIO_ARRAY_EMBEDDED_CAPA + 1, - "set didn't update count correctly from embedded " - "array (%d != %d)", - FIO_NAME(FIO_ARRAY_NAME, count)(a), - (int)FIO_ARRAY_EMBEDDED_CAPA); - } +/* ***************************************************************************** +String Core Callbacks - Memory management +***************************************************************************** */ +SFUNC FIO_NAME(FIO_STR_NAME, s) * FIO_NAME(FIO_STR_NAME, __object_new)(void) { + FIO_NAME(FIO_STR_NAME, s) *r = + (FIO_NAME(FIO_STR_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, (sizeof(*r)), 0); + if (r) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, s)); + return r; +} +SFUNC void FIO_NAME(FIO_STR_NAME, + __object_free)(FIO_NAME(FIO_STR_NAME, s) * s) { + if (!s) + return; + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, s)); + FIO_MEM_FREE_(s, sizeof(*s)); +} - /* test set from bigger array */ - FIO_ARRAY_TEST_OBJ_SET(o, 1); - FIO_NAME(FIO_ARRAY_NAME, set) - (a, ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), o, &o); - FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), - "set overflow should reset `old`"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, - "set didn't update count correctly (%d != %d)", - FIO_NAME(FIO_ARRAY_NAME, count)(a), - (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4)); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), - "set capa should be above item count"); - if (FIO_ARRAY_EMBEDDED_CAPA) { - FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); - FIO_NAME(FIO_ARRAY_NAME, set)(a, FIO_ARRAY_EMBEDDED_CAPA, o, &o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), - "set overflow lost last item while growing."); - } - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, (FIO_ARRAY_EMBEDDED_CAPA + 1) * 2); - FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), - "set overflow should have memory in the middle set to invalid " - "objetcs."); - FIO_ARRAY_TEST_OBJ_SET(o, 2); - FIO_NAME(FIO_ARRAY_NAME, set)(a, 0, o, &o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), - "set should set `old` to previous value"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, - "set item count error"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, - "set capa should be above item count"); +SFUNC int FIO_NAME(FIO_STR_NAME, __default_reallocate)(fio_str_info_s *dest, + size_t new_capa) { + new_capa = fio_string_capa4len(new_capa); + void *tmp = FIO_MEM_REALLOC_(dest->buf, dest->capa, new_capa, dest->len); + if (!tmp) + return -1; + if (!dest->buf) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, destroy)); + dest->capa = new_capa; + dest->buf = (char *)tmp; + return 0; +} +SFUNC int FIO_NAME(FIO_STR_NAME, + __default_copy_and_reallocate)(fio_str_info_s *dest, + size_t new_capa) { + if (dest->len && new_capa < dest->len) + new_capa = dest->len; + new_capa = fio_string_capa4len(new_capa); + void *tmp = FIO_MEM_REALLOC_(NULL, 0, new_capa, 0); + if (!tmp) + return -1; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_STR_NAME, destroy)); + if (dest->len) + FIO_MEMCPY(tmp, dest->buf, dest->len); + ((char *)tmp)[dest->len] = 0; + dest->capa = new_capa; + dest->buf = (char *)tmp; + return 0; +} +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free)(void *ptr, size_t capa) { + if (!ptr) + return; + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, destroy)); + FIO_MEM_FREE_(ptr, capa); + (void)capa; /* if unused */ +} +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free_noop)(void *str) { (void)str; } +SFUNC void FIO_NAME(FIO_STR_NAME, __default_free_noop2)(fio_str_info_s str) { + (void)str; +} - /* test find TODO: test with uninitialized array */ - FIO_ARRAY_TEST_OBJ_SET(o, 99); - if (FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID)) { - FIO_ARRAY_TEST_OBJ_SET(o, 100); - } - uint32_t found = FIO_NAME(FIO_ARRAY_NAME, find)(a, o, 0); - FIO_ASSERT(found == (uint32_t)-1, - "seeking for an object that doesn't exist should fail."); - FIO_ARRAY_TEST_OBJ_SET(o, 1); - found = FIO_NAME(FIO_ARRAY_NAME, find)(a, o, 1); - FIO_ASSERT(found == ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), - "seeking for an object returned the wrong index."); - FIO_ASSERT(found == FIO_NAME(FIO_ARRAY_NAME, find)(a, o, -1), - "seeking for an object in reverse returned the wrong index."); - FIO_ARRAY_TEST_OBJ_SET(o, 2); - FIO_ASSERT( - !FIO_NAME(FIO_ARRAY_NAME, find)(a, o, -2), - "seeking for an object in reverse (2) returned the wrong index."); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, - "find should have side-effects - count error"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, - "find should have side-effects - capa error"); +/* ***************************************************************************** +String Implementation - Memory management +***************************************************************************** */ - /* test remove */ - FIO_NAME(FIO_ARRAY_NAME, remove)(a, found, &o); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), "remove didn't copy old data?"); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(2), "remove removed more?"); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), - "remove with didn't update count correctly (%d != %s)", - FIO_NAME(FIO_ARRAY_NAME, count)(a), - (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4)); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); +/** Frees the pointer returned by `detach`. */ +SFUNC void FIO_NAME(FIO_STR_NAME, dealloc)(void *ptr) { + if (!ptr) + return; + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_STR_NAME, destroy)); + FIO_MEM_FREE_(ptr, -1); +} - /* test remove2 */ - FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); - FIO_ASSERT((found = FIO_NAME(FIO_ARRAY_NAME, remove2)(a, o)) == - ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) - 1, - "remove2 result error, %d != %d items.", - found, - (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) - 1); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == 1, - "remove2 didn't update count correctly (%d != 1)", - FIO_NAME(FIO_ARRAY_NAME, count)(a)); +/** + * Reserves at least `amount` of bytes for the string's data. + * + * Returns the current state of the String. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + FIO_STR_RESERVE_NAME)(FIO_STR_PTR s_, + size_t amount) { + fio_str_info_s state = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(s_, state); + FIO_NAME(FIO_STR_NAME, s) *const s = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s_); + state = FIO_NAME(FIO_STR_NAME, info)(s_); + if (FIO_STR_IS_FROZEN(s)) + return state; + amount += state.len; + if (state.capa <= amount) { + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_)(&state, amount); + state.buf[state.len] = 0; + FIO_NAME(FIO_STR_NAME, __info_update)(s_, state); + } else if (state.capa > FIO_STR_SMALL_CAPA(s) && + amount <= FIO_STR_SMALL_CAPA(s) && + state.len <= FIO_STR_SMALL_CAPA(s)) { + FIO_NAME(FIO_STR_NAME, s) tmp; + state = FIO_NAME(FIO_STR_NAME, init_copy)((FIO_STR_PTR)FIO_PTR_TAG(&tmp), + state.buf, + state.len); + FIO_NAME(FIO_STR_NAME, destroy)(s_); + *s = tmp; + } + return state; +} - /* hopefuly these will end... or crash on error. */ - while (!FIO_NAME(FIO_ARRAY_NAME, pop)(a, NULL)) { - ; - } - while (!FIO_NAME(FIO_ARRAY_NAME, shift)(a, NULL)) { - ; - } +/* ***************************************************************************** +String Implementation - UTF-8 State +***************************************************************************** */ - /* test push / unshift alternate */ - FIO_NAME(FIO_ARRAY_NAME, destroy)(a); - for (int i = 0; i < 4096; ++i) { - FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); - FIO_NAME(FIO_ARRAY_NAME, push)(a, o); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) + 1 == - ((uint32_t)(i + 1) << 1), - "push-unshift[%d.5] cycle count arror (%d != %d)", - i, - FIO_NAME(FIO_ARRAY_NAME, count)(a), - (((uint32_t)(i + 1) << 1)) - 1); - FIO_ARRAY_TEST_OBJ_SET(o, (i + 4097)); - FIO_NAME(FIO_ARRAY_NAME, unshift)(a, o); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == ((uint32_t)(i + 1) << 1), - "push-unshift[%d] cycle count arror (%d != %d)", - i, - FIO_NAME(FIO_ARRAY_NAME, count)(a), - ((uint32_t)(i + 1) << 1)); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 4097), - "unshift-push cycle failed (%d)", - i); - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), - "push-shift cycle failed (%d)", - i); - } - for (int i = 0; i < 4096; ++i) { - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((4096 * 2) - i), - "item value error at index %d", - i); - } - for (int i = 0; i < 4096; ++i) { - o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i + 4096); - FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((1 + i)), - "item value error at index %d", - i + 4096); - } -#if DEBUG - for (int i = 0; i < 2; ++i) { - FIO_LOG_DEBUG2( - "\t- " FIO_MACRO2STR( - FIO_NAME(FIO_ARRAY_NAME, s)) " after push/unshit cycle%s:\n" - "\t\t- item count: %d items\n" - "\t\t- capacity: %d items\n" - "\t\t- memory: %d bytes\n", - (i ? " after compact" : ""), - FIO_NAME(FIO_ARRAY_NAME, count)(a), - FIO_NAME(FIO_ARRAY_NAME, capa)(a), - FIO_NAME(FIO_ARRAY_NAME, capa)(a) * sizeof(FIO_ARRAY_TYPE)); - FIO_NAME(FIO_ARRAY_NAME, compact)(a); - } -#endif /* DEBUG */ - - FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); -/* test set with NULL, hopefully a bug will cause a crash */ -#if FIO_ARRAY_TYPE_DESTROY_SIMPLE - for (int i = 0; i < 4096; ++i) { - FIO_NAME(FIO_ARRAY_NAME, set)(a, i, o, NULL); - } -#else - /* - * we need to clear the memory to make sure a cleanup actions don't get - * unexpected values. - */ - for (int i = 0; i < (4096 * 2); ++i) { - FIO_ARRAY_TYPE_COPY((FIO_NAME2(FIO_ARRAY_NAME, ptr)(a)[i]), - FIO_ARRAY_TYPE_INVALID); - } - -#endif - - /* TODO: test concat */ +/** Returns 1 if the String is UTF-8 valid and 0 if not. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_valid)(FIO_STR_PTR s_) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, 0); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); + return fio_string_utf8_len(state); +} - /* test each */ - { - struct data_s { - int i; - int va[10]; - } d = {1, {1, 8, 2, 7, 3, 6, 4, 5}}; - FIO_NAME(FIO_ARRAY_NAME, destroy)(a); - for (int i = 0; d.va[i]; ++i) { - FIO_ARRAY_TEST_OBJ_SET(o, d.va[i]); - FIO_NAME(FIO_ARRAY_NAME, push)(a, o); - } +/** Returns the String's length in UTF-8 characters. */ +SFUNC size_t FIO_NAME(FIO_STR_NAME, utf8_len)(FIO_STR_PTR s_) { + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); + return fio_string_utf8_len(state); +} - int index = FIO_NAME(FIO_ARRAY_NAME, each)( - a, - FIO_NAME_TEST(stl, FIO_NAME(FIO_ARRAY_NAME, test_task)), - (void *)&d, - d.i); - FIO_ASSERT(index == d.i, - "index rerturned from each should match next object"); - FIO_ASSERT(*(char *)&d.va[d.i], - "array each error (didn't stop in time?)."); - FIO_ASSERT(!(*(char *)&d.va[d.i + 1]), - "array each error (didn't stop in time?)."); - } -#if FIO_ARRAY_TYPE_DESTROY_SIMPLE - { - FIO_NAME(FIO_ARRAY_NAME, destroy)(a); - size_t max_items = 63; - FIO_ARRAY_TYPE tmp[64]; - for (size_t i = 0; i < max_items; ++i) { - FIO_MEMSET(tmp + i, i + 1, sizeof(*tmp)); - } - for (size_t items = 0; items <= max_items; items = ((items << 1) | 1)) { - FIO_LOG_DEBUG2("* testing the FIO_ARRAY_EACH macro with %zu items.", - items); - size_t i = 0; - for (i = 0; i < items; ++i) - FIO_NAME(FIO_ARRAY_NAME, push)(a, tmp[i]); - i = 0; - FIO_ARRAY_EACH(FIO_ARRAY_NAME, a, pos) { - FIO_ASSERT(!memcmp(tmp + i, pos, sizeof(*pos)), - "FIO_ARRAY_EACH pos is at wrong index %zu != %zu", - (size_t)(pos - FIO_NAME2(FIO_ARRAY_NAME, ptr)(a)), - i); - ++i; - } - FIO_ASSERT(i == items, - "FIO_ARRAY_EACH macro count error - didn't review all " - "items? %zu != %zu ", - i, - items); - FIO_NAME(FIO_ARRAY_NAME, destroy)(a); - } - } -#endif - /* test destroy */ - FIO_NAME(FIO_ARRAY_NAME, destroy)(a); - FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), - "destroy didn't clear count."); - FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, - "destroy capa error."); - /* Test end here */ - } - FIO_NAME(FIO_ARRAY_NAME, free)(a_array[1]); +/** + * Takes a UTF-8 character selection information (UTF-8 position and length) + * and updates the same variables so they reference the raw byte slice + * information. + * + * If the String isn't UTF-8 valid up to the requested selection, than `pos` + * will be updated to `-1` otherwise values are always positive. + * + * The returned `len` value may be shorter than the original if there wasn't + * enough data left to accommodate the requested length. When a `len` value of + * `0` is returned, this means that `pos` marks the end of the String. + * + * Returns -1 on error and 0 on success. + */ +SFUNC int FIO_NAME(FIO_STR_NAME, + utf8_select)(FIO_STR_PTR s_, intptr_t *pos, size_t *len) { + FIO_PTR_TAG_VALID_OR_RETURN(s_, -1); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, info)(s_); + return fio_string_utf8_select(state, pos, len); } -#undef FIO_ARRAY_TEST_OBJ_SET -#undef FIO_ARRAY_TEST_OBJ_IS -#endif /* FIO_TEST_ALL */ /* ***************************************************************************** -Dynamic Arrays - cleanup +String Implementation - Content Manipulation and Review ***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#endif /* FIO_ARRAY_NAME */ -#undef FIO_ARRAY_NAME -#undef FIO_ARRAY_TYPE -#undef FIO_ARRAY_ENABLE_EMBEDDED -#undef FIO_ARRAY_TYPE_INVALID -#undef FIO_ARRAY_TYPE_INVALID_SIMPLE -#undef FIO_ARRAY_TYPE_COPY -#undef FIO_ARRAY_TYPE_COPY_SIMPLE -#undef FIO_ARRAY_TYPE_CONCAT_COPY -#undef FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE -#undef FIO_ARRAY_TYPE_DESTROY -#undef FIO_ARRAY_TYPE_DESTROY_SIMPLE -#undef FIO_ARRAY_DESTROY_AFTER_COPY -#undef FIO_ARRAY_TYPE_CMP -#undef FIO_ARRAY_TYPE_CMP_SIMPLE -#undef FIO_ARRAY_PADDING -#undef FIO_ARRAY_SIZE2WORDS -#undef FIO_ARRAY_POS2ABS -#undef FIO_ARRAY_AB_CT -#undef FIO_ARRAY_PTR -#undef FIO_ARRAY_EXPONENTIAL -#undef FIO_ARRAY_ADD2CAPA -#undef FIO_ARRAY_IS_EMBEDDED -#undef FIO_ARRAY_IS_EMBEDDED_PTR -#undef FIO_ARRAY_EMBEDDED_CAPA -#undef FIO_ARRAY2EMBEDDED -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_MAP_NAME map /* Development inclusion - ignore line */ -#define FIO_MAP_KEY size_t /* Development inclusion - ignore line */ -// #define FIO_MAP_VALUE_BSTR /* Development inclusion - ignore line */ -// #define FIO_MAP_ORDERED /* Development inclusion - ignore line */ -#define FIO_MAP_TEST /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** +/** + * Writes a number at the end of the String using normal base 10 notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_i)(FIO_STR_PTR s_, + int64_t num) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_i(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} + +/** + * Writes a number at the end of the String using Hex (base 16) notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_hex)(FIO_STR_PTR s_, + int64_t num) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_hex(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} +/** + * Writes a number at the end of the String using binary notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_bin)(FIO_STR_PTR s_, + int64_t num) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_bin(&i, FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), num); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} +/** + * Appends the `src` String to the end of the `dest` String. + * + * If `dest` is empty, the resulting Strings will be equal. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, concat)(FIO_STR_PTR dest_, + FIO_STR_PTR const src_) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(dest_); + if (!i.capa) + return i; + FIO_PTR_TAG_VALID_OR_RETURN(src_, i); + fio_str_info_s src = FIO_NAME(FIO_STR_NAME, info)(src_); + if (!src.len) + return i; + fio_string_write(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(dest_), + src.buf, + src.len); + FIO_NAME(FIO_STR_NAME, __info_update)(dest_, i); + return i; +} +/** + * Replaces the data in the String - replacing `old_len` bytes starting at + * `start_pos`, with the data at `src` (`src_len` bytes long). + * + * Negative `start_pos` values are calculated backwards, `-1` == end of + * String. + * + * When `old_len` is zero, the function will insert the data at `start_pos`. + * + * If `src_len == 0` than `src` will be ignored and the data marked for + * replacement will be erased. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, replace)(FIO_STR_PTR s_, + intptr_t start_pos, + size_t old_len, + const void *src, + size_t src_len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_replace(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + start_pos, + old_len, + src, + src_len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} - Unordered/Ordered Map Implementation +/** + * Writes a number at the end of the String using binary notation. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + __write2)(FIO_STR_PTR s_, + const fio_string_write_s srcs[]) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write2 FIO_NOOP(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + srcs); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} +/** + * Writes to the String using a vprintf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO___PRINTF_STYLE(2, 0) + FIO_NAME(FIO_STR_NAME, + vprintf)(FIO_STR_PTR s_, const char *format, va_list argv) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_vprintf(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + format, + argv); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} +/** + * Writes to the String using a printf like interface. + * + * Data is written to the end of the String. + */ +SFUNC fio_str_info_s FIO___PRINTF_STYLE(2, 3) + FIO_NAME(FIO_STR_NAME, printf)(FIO_STR_PTR s_, const char *format, ...) { + va_list argv; + va_start(argv, format); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, vprintf)(s_, format, argv); + va_end(argv); + return state; +} -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_MAP_NAME) /* ***************************************************************************** -Map Settings - Sets have only keys (value == key) - Hash Maps have values +String API - C / JSON escaping ***************************************************************************** */ -/* if FIO_MAP_KEY_KSTR is defined, use fio_keystr_s keys */ -#ifdef FIO_MAP_KEY_KSTR -#define FIO_MAP_KEY fio_str_info_s -#define FIO_MAP_KEY_INTERNAL fio_keystr_s -#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_keystr_info(&(k)) -#define FIO_MAP_KEY_COPY(dest, src) \ - (dest) = fio_keystr_init((src), FIO_NAME(FIO_MAP_NAME, __key_alloc)) -#define FIO_MAP_KEY_CMP(a, b) fio_keystr_is_eq2((a), (b)) -#define FIO_MAP_KEY_DESTROY(key) \ - fio_keystr_destroy(&(key), FIO_NAME(FIO_MAP_NAME, __key_free)) -#define FIO_MAP_KEY_DISCARD(key) -FIO_SFUNC void *FIO_NAME(FIO_MAP_NAME, __key_alloc)(size_t len) { - return FIO_MEM_REALLOC_(NULL, 0, len, 0); -} -FIO_SFUNC void FIO_NAME(FIO_MAP_NAME, __key_free)(void *ptr, size_t len) { - FIO_MEM_FREE_(ptr, len); - (void)len; /* if unused */ +/** + * Writes data at the end of the String, escaping the data using JSON semantics. + * + * The JSON semantic are common to many programming languages, promising a UTF-8 + * String while making it easy to read and copy the string during debugging. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_escape)(FIO_STR_PTR s_, + const void *src, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_escape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + src, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; } -#undef FIO_MAP_KEY_KSTR -/* if FIO_MAP_KEY is undefined, assume String keys (using `fio_bstr`). */ -#elif !defined(FIO_MAP_KEY) || defined(FIO_MAP_KEY_BSTR) -#define FIO_MAP_KEY fio_str_info_s -#define FIO_MAP_KEY_INTERNAL char * -#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) -#define FIO_MAP_KEY_COPY(dest, src) \ - (dest) = fio_bstr_write(NULL, (src).buf, (src).len) -#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) -#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) -#define FIO_MAP_KEY_DISCARD(key) -#endif -#undef FIO_MAP_KEY_BSTR +/** + * Writes an escaped data into the string after unescaping the data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_unescape)(FIO_STR_PTR s_, + const void *src, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_unescape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + src, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -#ifndef FIO_MAP_KEY_INTERNAL -#define FIO_MAP_KEY_INTERNAL FIO_MAP_KEY -#endif +/* ***************************************************************************** +String - Base64 support +***************************************************************************** */ -#ifndef FIO_MAP_KEY_FROM_INTERNAL -#define FIO_MAP_KEY_FROM_INTERNAL(o) o -#endif +/** + * Writes data at the end of the String, encoding the data as Base64 encoded + * data. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64enc)(FIO_STR_PTR s_, + const void *data, + size_t len, + uint8_t url_encoded) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_base64enc(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + data, + len, + url_encoded); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -#ifndef FIO_MAP_KEY_COPY -#define FIO_MAP_KEY_COPY(dest, src) ((dest) = (src)) -#endif +/** + * Writes decoded base64 data to the end of the String. + */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_base64dec)(FIO_STR_PTR s_, + const void *encoded_, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_base64dec(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + encoded_, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -#ifndef FIO_MAP_KEY_CMP -#define FIO_MAP_KEY_CMP(a, b) ((a) == (b)) -#endif +/* ***************************************************************************** +String API - HTML escaping support +***************************************************************************** */ -#ifndef FIO_MAP_KEY_DESTROY -#define FIO_MAP_KEY_DESTROY(o) -#define FIO_MAP_KEY_DESTROY_SIMPLE 1 -#endif +/** Writes HTML escaped data to a String. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, write_html_escape)(FIO_STR_PTR s_, + const void *data, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_html_escape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + data, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -#ifndef FIO_MAP_KEY_DISCARD -#define FIO_MAP_KEY_DISCARD(o) -#endif +/** Writes HTML un-escaped data to a String - incomplete and minimal. */ +IFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, + write_html_unescape)(FIO_STR_PTR s_, + const void *data, + size_t len) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_write_html_unescape(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + data, + len); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -/* FIO_MAP_HASH_FN(key) - used instead of providing a hash value. */ -#ifndef FIO_MAP_HASH_FN -#undef FIO_MAP_RECALC_HASH -#endif +/* ***************************************************************************** +String - read file +***************************************************************************** */ -/* FIO_MAP_RECALC_HASH - if true, hash values won't be cached. */ -#ifndef FIO_MAP_RECALC_HASH -#define FIO_MAP_RECALC_HASH 0 -#endif - -#ifdef FIO_MAP_VALUE_BSTR -#define FIO_MAP_VALUE fio_str_info_s -#define FIO_MAP_VALUE_INTERNAL char * -#define FIO_MAP_VALUE_FROM_INTERNAL(v) fio_bstr_info((v)) -#define FIO_MAP_VALUE_COPY(dest, src) \ - (dest) = fio_bstr_write(NULL, (src).buf, (src).len) -#define FIO_MAP_VALUE_DESTROY(v) fio_bstr_free((v)) -#define FIO_MAP_VALUE_DISCARD(v) -#endif - -#ifdef FIO_MAP_VALUE -#define FIO_MAP_GET_T FIO_MAP_VALUE -#else -#define FIO_MAP_GET_T FIO_MAP_KEY -#endif - -#ifndef FIO_MAP_VALUE_INTERNAL -#define FIO_MAP_VALUE_INTERNAL FIO_MAP_VALUE -#endif - -#ifndef FIO_MAP_VALUE_FROM_INTERNAL -#ifdef FIO_MAP_VALUE -#define FIO_MAP_VALUE_FROM_INTERNAL(o) o -#else -#define FIO_MAP_VALUE_FROM_INTERNAL(o) -#endif -#endif - -#ifndef FIO_MAP_VALUE_COPY -#ifdef FIO_MAP_VALUE -#define FIO_MAP_VALUE_COPY(dest, src) (dest) = (src) -#else -#define FIO_MAP_VALUE_COPY(dest, src) -#endif -#endif - -#ifndef FIO_MAP_VALUE_DESTROY -#define FIO_MAP_VALUE_DESTROY(o) -#define FIO_MAP_VALUE_DESTROY_SIMPLE 1 -#endif - -#ifndef FIO_MAP_VALUE_DISCARD -#define FIO_MAP_VALUE_DISCARD(o) -#endif +/** + * Reads data from a file descriptor `fd` at offset `start_at` and pastes it's + * contents (or a slice of it) at the end of the String. If `limit == 0`, than + * the data will be read until EOF. + * + * The file should be a regular file or the operation might fail (can't be used + * for sockets). + * + * The file descriptor will remain open and should be closed manually. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfd)(FIO_STR_PTR s_, + int fd, + intptr_t start_at, + intptr_t limit) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_readfd(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + fd, + start_at, + limit); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -#ifdef FIO_MAP_LRU -#undef FIO_MAP_ORDERED -#define FIO_MAP_ORDERED 1 /* required for least recently used order */ -#endif +/** + * Opens the file `filename` and pastes it's contents (or a slice ot it) at + * the end of the String. If `limit == 0`, than the data will be read until + * EOF. + * + * If the file can't be located, opened or read, or if `start_at` is beyond + * the EOF position, NULL is returned in the state's `data` field. + */ +SFUNC fio_str_info_s FIO_NAME(FIO_STR_NAME, readfile)(FIO_STR_PTR s_, + const char *filename, + intptr_t start_at, + intptr_t limit) { + fio_str_info_s i = FIO_NAME(FIO_STR_NAME, info)(s_); + if (!i.capa) + return i; + fio_string_readfile(&i, + FIO_NAME(FIO_STR_NAME, __realloc_func)(s_), + filename, + start_at, + limit); + FIO_NAME(FIO_STR_NAME, __info_update)(s_, i); + return i; +} -/* test if FIO_MAP_ORDERED was defined as an empty macro */ -#if defined(FIO_MAP_ORDERED) && ((0 - FIO_MAP_ORDERED - 1) == 1) -#undef FIO_MAP_ORDERED -#define FIO_MAP_ORDERED 1 /* assume developer's intention */ -#endif +/* ***************************************************************************** -#ifndef FIO_MAP_ORDERED -#define FIO_MAP_ORDERED 0 -#endif -/* ***************************************************************************** -Pointer Tagging Support -***************************************************************************** */ + String Test -#ifdef FIO_PTR_TAG_TYPE -#define FIO_MAP_PTR FIO_PTR_TAG_TYPE -#else -#define FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, s) * -#endif -#define FIO_MAP_T FIO_NAME(FIO_MAP_NAME, s) -/* ***************************************************************************** -Map Types ***************************************************************************** */ +#ifdef FIO_STR_WRITE_TEST_FUNC -#ifndef FIO_MAP_INIT -/* Initialization macro. */ -#define FIO_MAP_INIT \ - { 0 } -#endif +/** + * Tests the fio_str functionality. + */ +SFUNC void FIO_NAME_TEST(stl, FIO_STR_NAME)(void) { + FIO_NAME(FIO_STR_NAME, s) str = {0}; /* test zeroed out memory */ +#define FIO__STR_SMALL_CAPA FIO_STR_SMALL_CAPA(&str) + FIO_STR_PTR pstr = FIO_PTR_TAG((&str)); + fprintf( + stderr, + "* Testing core string features for " FIO_MACRO2STR(FIO_STR_NAME) ".\n"); + fprintf(stderr, + "* String container size (without wrapper): %zu\n", + sizeof(FIO_NAME(FIO_STR_NAME, s))); + fprintf(stderr, + "* Self-contained capacity (FIO_STR_SMALL_CAPA): %zu\n", + FIO__STR_SMALL_CAPA); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, frozen)(pstr), "new string is frozen"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == FIO__STR_SMALL_CAPA, + "small string capacity returned %zu", + FIO_NAME(FIO_STR_NAME, capa)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 0, + "small string length reporting error!"); + FIO_ASSERT( + FIO_NAME(FIO_STR_NAME, ptr)(pstr) == ((char *)(&str) + 1), + "small string pointer reporting error (%zd offset)!", + (ssize_t)(((char *)(&str) + 1) - FIO_NAME(FIO_STR_NAME, ptr)(pstr))); + FIO_NAME(FIO_STR_NAME, write)(pstr, "World", 4); + FIO_ASSERT(FIO_STR_IS_SMALL(&str), + "small string writing error - not small on small write!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == FIO__STR_SMALL_CAPA, + "Small string capacity reporting error after write!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 4, + "small string length reporting error after write!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == (char *)&str + 1, + "small string pointer reporting error after write!"); + FIO_ASSERT(!FIO_NAME(FIO_STR_NAME, ptr)(pstr)[4] && + FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr)) == 4, + "small string NUL missing after write (%zu)!", + FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr))); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Worl"), + "small string write error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == + FIO_NAME(FIO_STR_NAME, info)(pstr).buf, + "small string `data` != `info.buf` (%p != %p)", + (void *)FIO_NAME(FIO_STR_NAME, ptr)(pstr), + (void *)FIO_NAME(FIO_STR_NAME, info)(pstr).buf); -/** internal object data representation */ -typedef struct FIO_NAME(FIO_MAP_NAME, node_s) FIO_NAME(FIO_MAP_NAME, node_s); + FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME) + (pstr, sizeof(FIO_NAME(FIO_STR_NAME, s))); + FIO_ASSERT(!FIO_STR_IS_SMALL(&str), + "Long String reporting as small after capacity update!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) >= + sizeof(FIO_NAME(FIO_STR_NAME, s)) - 1, + "Long String capacity update error (%zu != %zu)!", + FIO_NAME(FIO_STR_NAME, capa)(pstr), + FIO_STR_SMALL_CAPA(&str)); -/** A Hash Map / Set type */ -typedef struct FIO_NAME(FIO_MAP_NAME, s) { - uint32_t bits; - uint32_t count; - FIO_NAME(FIO_MAP_NAME, node_s) * map; -#if FIO_MAP_ORDERED - FIO_INDEXED_LIST32_HEAD head; -#endif -} FIO_NAME(FIO_MAP_NAME, s); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == + FIO_NAME(FIO_STR_NAME, info)(pstr).buf, + "Long String `ptr` !>= " + "`cstr(s).buf` (%p != %p)", + (void *)FIO_NAME(FIO_STR_NAME, ptr)(pstr), + (void *)FIO_NAME(FIO_STR_NAME, info)(pstr).buf); -/** internal object data representation */ -struct FIO_NAME(FIO_MAP_NAME, node_s) { -#if !FIO_MAP_RECALC_HASH - uint64_t hash; -#endif - FIO_MAP_KEY_INTERNAL key; -#ifdef FIO_MAP_VALUE - FIO_MAP_VALUE_INTERNAL value; -#endif -#if FIO_MAP_ORDERED - FIO_INDEXED_LIST32_NODE node; +#if FIO_STR_OPTIMIZE4IMMUTABILITY + /* immutable string length is updated after `reserve` to reflect new capa */ + FIO_NAME(FIO_STR_NAME, resize)(pstr, 4); #endif -}; + FIO_ASSERT( + FIO_NAME(FIO_STR_NAME, len)(pstr) == 4, + "Long String length changed during conversion from small string (%zu)!", + FIO_NAME(FIO_STR_NAME, len)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, ptr)(pstr) == str.buf, + "Long String pointer reporting error after capacity update!"); + FIO_ASSERT(FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr)) == 4, + "Long String NUL missing after capacity update (%zu)!", + FIO_STRLEN(FIO_NAME(FIO_STR_NAME, ptr)(pstr))); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Worl"), + "Long String value changed after capacity update (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/** Map iterator type */ -typedef struct { - /** the node in the internal map */ - FIO_NAME(FIO_MAP_NAME, node_s) * node; - /** the key in the current position */ - FIO_MAP_KEY key; -#ifdef FIO_MAP_VALUE - /** the value in the current position */ - FIO_MAP_VALUE value; -#endif -#if !FIO_MAP_RECALC_HASH - /** the hash for the current position */ - uint64_t hash; -#endif - struct { /* internal usage, do not access */ - uint32_t index; /* the index in the internal map */ - uint32_t pos; /* the position in the ordering scheme */ - uintptr_t map_validator; /* map mutation guard */ - } private_; -} FIO_NAME(FIO_MAP_NAME, iterator_s); + FIO_NAME(FIO_STR_NAME, write)(pstr, "d!", 2); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "World!"), + "Long String `write` error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/* ***************************************************************************** -Construction / Deconstruction -***************************************************************************** */ + FIO_NAME(FIO_STR_NAME, replace)(pstr, 0, 0, "Hello ", 6); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello World!"), + "Long String `insert` error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/* do we have a constructor? */ -#ifndef FIO_REF_CONSTRUCTOR_ONLY + FIO_NAME(FIO_STR_NAME, resize)(pstr, 6); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello "), + "Long String `resize` clipping error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/* Allocates a new object on the heap and initializes it's memory. */ -SFUNC FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, new)(void); + FIO_NAME(FIO_STR_NAME, replace)(pstr, 6, 0, "My World!", 9); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello My World!"), + "Long String `replace` error when testing overflow (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/* Frees any internal data AND the object's container! */ -SFUNC void FIO_NAME(FIO_MAP_NAME, free)(FIO_MAP_PTR map); + FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME) + (pstr, FIO_NAME(FIO_STR_NAME, len)(pstr)); /* may truncate */ -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ + FIO_NAME(FIO_STR_NAME, replace)(pstr, -10, 2, "Big", 3); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World!"), + "Long String `replace` error when testing splicing (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/** Destroys the object, re-initializing its container. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, destroy)(FIO_MAP_PTR map); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == + fio_string_capa4len(FIO_STRLEN("Hello Big World!")) || + !FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), + "Long String `replace` capacity update error " + "(%zu >=? %zu)!", + FIO_NAME(FIO_STR_NAME, capa)(pstr), + fio_string_capa4len(FIO_STRLEN("Hello Big World!"))); -/* ***************************************************************************** -Map State -***************************************************************************** */ + if (FIO_NAME(FIO_STR_NAME, len)(pstr) < (sizeof(str) - 2)) { + FIO_NAME(FIO_STR_NAME, compact)(pstr); + FIO_ASSERT(FIO_STR_IS_SMALL(&str), + "Compacting didn't change String to small!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == + FIO_STRLEN("Hello Big World!"), + "Compacting altered String length! (%zu != %zu)!", + FIO_NAME(FIO_STR_NAME, len)(pstr), + FIO_STRLEN("Hello Big World!")); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World!"), + "Compact data error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == sizeof(str) - 2, + "Compacted String capacity reporting error!"); + } else { + FIO_LOG_DEBUG2("* Skipped `compact` test (irrelevant for type)."); + } -/** Theoretical map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, capa)(FIO_MAP_PTR map); + { + FIO_NAME(FIO_STR_NAME, freeze)(pstr); + FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, frozen)(pstr), + "Frozen String not flagged as frozen."); + fio_str_info_s old_state = FIO_NAME(FIO_STR_NAME, info)(pstr); + FIO_NAME(FIO_STR_NAME, write)(pstr, "more data to be written here", 28); + FIO_NAME(FIO_STR_NAME, replace) + (pstr, 2, 1, "more data to be written here", 28); + fio_str_info_s new_state = FIO_NAME(FIO_STR_NAME, info)(pstr); + FIO_ASSERT(old_state.len == new_state.len, "Frozen String length changed!"); + FIO_ASSERT(old_state.buf == new_state.buf, + "Frozen String pointer changed!"); + FIO_ASSERT( + old_state.capa == new_state.capa, + "Frozen String capacity changed (allowed, but shouldn't happen)!"); + FIO_STR_THAW_(&str); + } + FIO_NAME(FIO_STR_NAME, printf)(pstr, " %u", 42); + FIO_ASSERT(!strcmp(FIO_NAME(FIO_STR_NAME, ptr)(pstr), "Hello Big World! 42"), + "`printf` data error (%s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); -/** The number of objects in the map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, count)(FIO_MAP_PTR map); + { + FIO_NAME(FIO_STR_NAME, s) str2 = FIO_STR_INIT; + FIO_STR_PTR pstr2 = FIO_PTR_TAG(&str2); + FIO_NAME(FIO_STR_NAME, concat)(pstr2, pstr); + FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, eq)(pstr, pstr2), + "`concat` error, strings not equal (%s != %s)!", + FIO_NAME(FIO_STR_NAME, ptr)(pstr), + FIO_NAME(FIO_STR_NAME, ptr)(pstr2)); + FIO_NAME(FIO_STR_NAME, write)(pstr2, ":extra data", 11); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, eq)(pstr, pstr2), + "`write` error after copy, strings equal " + "((%zu)%s == (%zu)%s)!", + FIO_NAME(FIO_STR_NAME, len)(pstr), + FIO_NAME(FIO_STR_NAME, ptr)(pstr), + FIO_NAME(FIO_STR_NAME, len)(pstr2), + FIO_NAME(FIO_STR_NAME, ptr)(pstr2)); -/** Reserves at minimum the capacity requested. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, reserve)(FIO_MAP_PTR map, size_t capa); + FIO_NAME(FIO_STR_NAME, destroy)(pstr2); + } -/** Returns the key value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, - node2key)(FIO_NAME(FIO_MAP_NAME, node_s) * node); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); -/** Returns the hash value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, - node2hash)(FIO_NAME(FIO_MAP_NAME, node_s) * node); + FIO_NAME(FIO_STR_NAME, write_i)(pstr, -42); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 3 && + !memcmp("-42", FIO_NAME(FIO_STR_NAME, ptr)(pstr), 3), + "write_i output error ((%zu) %s != -42)", + FIO_NAME(FIO_STR_NAME, len)(pstr), + FIO_NAME(FIO_STR_NAME, ptr)(pstr)); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + { + fprintf(stderr, "* Testing string `readfile`.\n"); + FIO_NAME(FIO_STR_NAME, s) *s = FIO_NAME(FIO_STR_NAME, new)(); + FIO_ASSERT(FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_STR_NAME, s), s), + "error, string not allocated (%p)!", + (void *)s); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, readfile)(s, __FILE__, 0, 0); -#ifdef FIO_MAP_VALUE -/** Returns the value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP_VALUE FIO_NAME(FIO_MAP_NAME, - node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * - node); -#endif + FIO_ASSERT(state.len && state.buf, + "error, no data was read for file %s!", + __FILE__); +#if defined(H___FIO_CSTL_COMBINED___H) + FIO_ASSERT(!memcmp(state.buf, + "/* " + "******************************************************" + "***********************", + 80), + "content error, header mismatch!\n %s", + state.buf); +#endif /* H___FIO_CSTL_COMBINED___H */ + fprintf(stderr, "* Testing UTF-8 validation and length.\n"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_valid)(s), + "`utf8_valid` error, code in this file " + "should be valid!"); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_len)(s) && + (FIO_NAME(FIO_STR_NAME, utf8_len)(s) <= + FIO_NAME(FIO_STR_NAME, len)(s)) && + (FIO_NAME(FIO_STR_NAME, utf8_len)(s) >= + (FIO_NAME(FIO_STR_NAME, len)(s)) >> 1), + "`utf8_len` error, invalid value (%zu / %zu!", + FIO_NAME(FIO_STR_NAME, utf8_len)(s), + FIO_NAME(FIO_STR_NAME, len)(s)); -/** Returns the key value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2key_ptr)( - FIO_NAME(FIO_MAP_NAME, node_s) * node); + if (1) { + /* String content == whole file (this file) */ + intptr_t pos = -10; + size_t len = 20; + fprintf(stderr, "* Testing UTF-8 positioning.\n"); -#ifdef FIO_MAP_VALUE -/** Returns the value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP_VALUE_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( - FIO_NAME(FIO_MAP_NAME, node_s) * node); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_select)(s, &pos, &len) == 0, + "`select` returned error for negative " + "pos! (%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(pos == + (intptr_t)state.len - 10, /* no UTF-8 bytes in this file */ + "`utf8_select` error, negative position " + "invalid! (%zd)", + (ssize_t)pos); + FIO_ASSERT(len == 10, + "`utf8_select` error, trancated length " + "invalid! (%zd)", + (ssize_t)len); + pos = 10; + len = 20; + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, utf8_select)(s, &pos, &len) == 0, + "`utf8_select` returned error! (%zd, %zu)", + (ssize_t)pos, + len); + FIO_ASSERT(pos == 10, + "`utf8_select` error, position invalid! (%zd)", + (ssize_t)pos); + FIO_ASSERT(len == 20, + "`utf8_select` error, length invalid! (%zd)", + (ssize_t)len); + } + FIO_NAME(FIO_STR_NAME, free)(s); + } + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + if (1) { + /* Testing Static initialization and writing */ +#if FIO_STR_OPTIMIZE4IMMUTABILITY + FIO_NAME(FIO_STR_NAME, init_const)(pstr, "Welcome", 7); +#else + str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC("Welcome"); #endif + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == 0 || + FIO_STR_IS_SMALL(&str), + "Static string capacity non-zero."); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) > 0, + "Static string length should be automatically calculated."); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), + "Static strings shouldn't be dynamic."); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); -/* ***************************************************************************** -Adding / Removing Elements from the Map -***************************************************************************** */ - -/** Removes an object in the map, returning a pointer to the map data. */ -SFUNC int FIO_NAME(FIO_MAP_NAME, remove)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif - FIO_MAP_KEY key, -#if defined(FIO_MAP_VALUE) - FIO_MAP_VALUE_INTERNAL *old +#if FIO_STR_OPTIMIZE4IMMUTABILITY + FIO_NAME(FIO_STR_NAME, init_const) + (pstr, + "Welcome to a very long static string that should not fit within a " + "containing struct... hopefuly", + 95); #else - FIO_MAP_KEY_INTERNAL *old + str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC( + "Welcome to a very long static string that should not fit within a " + "containing struct... hopefuly"); #endif -); - -/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, evict)(FIO_MAP_PTR map, - size_t number_of_elements); - -/** Removes all objects from the map, without releasing the map's resources. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, clear)(FIO_MAP_PTR map); - -/** Attempts to minimize memory use. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, compact)(FIO_MAP_PTR map); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, capa)(pstr) == 0 || + FIO_STR_IS_SMALL(&str), + "Static string capacity non-zero."); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) > 0, + "Static string length should be automatically calculated."); + FIO_ASSERT(!FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr), + "Static strings shouldn't be dynamic."); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); -/** Gets a value from the map, if exists. */ -FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, get)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, +#if FIO_STR_OPTIMIZE4IMMUTABILITY + FIO_NAME(FIO_STR_NAME, init_const)(pstr, "Welcome", 7); +#else + str = (FIO_NAME(FIO_STR_NAME, s))FIO_STR_INIT_STATIC("Welcome"); #endif - FIO_MAP_KEY key); - -/** Sets a value in the map, hash maps will overwrite existing data if any. */ -FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif -#ifdef FIO_MAP_VALUE - FIO_MAP_KEY key, - FIO_MAP_VALUE obj, - FIO_MAP_VALUE_INTERNAL *old -#else - FIO_MAP_KEY key -#endif -); + fio_str_info_s state = FIO_NAME(FIO_STR_NAME, write)(pstr, " Home", 5); + FIO_ASSERT(state.capa > 0, "Static string not converted to non-static."); + FIO_ASSERT(FIO_NAME_BL(FIO_STR_NAME, allocated)(pstr) || + FIO_STR_IS_SMALL(&str), + "String should be dynamic after `write`."); -/** Sets a value in the map if not set previously. */ -FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set_if_missing)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif - FIO_MAP_KEY key -#ifdef FIO_MAP_VALUE - , - FIO_MAP_VALUE obj -#endif -); + char *cstr = FIO_NAME(FIO_STR_NAME, detach)(pstr); + FIO_ASSERT(cstr, "`detach` returned NULL"); + FIO_ASSERT(!memcmp(cstr, "Welcome Home\0", 13), + "`detach` string error: %s", + cstr); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(pstr) == 0, + "`detach` data wasn't cleared."); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); /*not really needed... detached... */ + FIO_NAME(FIO_STR_NAME, dealloc)(cstr); + } + { + fprintf(stderr, "* Testing Base64 encoding / decoding.\n"); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); /* does nothing, but why not... */ -/** - * The core set function. - * - * This function returns `NULL` on error (errors are logged). - * - * If the map is a hash map, overwriting the value (while keeping the key) is - * possible. In this case the `old` pointer is optional, and if set than the old - * data will be copied to over during an overwrite. - * - * NOTE: the function returns a pointer to the map's internal storage. - */ -SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * - FIO_NAME(FIO_MAP_NAME, set_ptr)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif -#ifdef FIO_MAP_VALUE - FIO_MAP_KEY key, - FIO_MAP_VALUE val, - FIO_MAP_VALUE_INTERNAL *old, - int overwrite -#else - FIO_MAP_KEY key -#endif - ); + FIO_NAME(FIO_STR_NAME, s) b64message = FIO_STR_INIT; + fio_str_info_s b64i = FIO_NAME(FIO_STR_NAME, write)( + FIO_PTR_TAG(&b64message), + "Hello World, this is the voice of peace:)", + 41); + for (int i = 0; i < 256; ++i) { + uint8_t c = i; + b64i = FIO_NAME(FIO_STR_NAME, write)(FIO_PTR_TAG(&b64message), &c, 1); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&b64message)) == + (size_t)(42 + i), + "Base64 message length error (%zu != %zu)", + FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&b64message)), + (size_t)(42 + i)); + FIO_ASSERT(FIO_NAME(FIO_STR_NAME, + ptr)(FIO_PTR_TAG(&b64message))[41 + i] == (char)c, + "Base64 message data error"); + } + fio_str_info_s encoded = + FIO_NAME(FIO_STR_NAME, write_base64enc)(pstr, b64i.buf, b64i.len, 1); + /* prevent encoded data from being deallocated during unencoding */ + encoded = FIO_NAME(FIO_STR_NAME, FIO_STR_RESERVE_NAME)( + pstr, + encoded.len + ((encoded.len >> 2) * 3) + 8); + fio_str_info_s decoded; + { + FIO_NAME(FIO_STR_NAME, s) tmps; + FIO_NAME(FIO_STR_NAME, init_copy2)(FIO_PTR_TAG(&tmps), pstr); + decoded = FIO_NAME(FIO_STR_NAME, write_base64dec)( + pstr, + FIO_NAME(FIO_STR_NAME, ptr)(FIO_PTR_TAG(&tmps)), + FIO_NAME(FIO_STR_NAME, len)(FIO_PTR_TAG(&tmps))); + FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&tmps)); + encoded.buf = decoded.buf; + } + FIO_ASSERT(encoded.len, "Base64 encoding failed"); + FIO_ASSERT(decoded.len > encoded.len, + "Base64 decoding failed:\n%s", + encoded.buf); + FIO_ASSERT(b64i.len == decoded.len - encoded.len, + "Base 64 roundtrip length error, %zu != %zu (%zu - %zu):\n %s", + b64i.len, + decoded.len - encoded.len, + decoded.len, + encoded.len, + decoded.buf); + + FIO_ASSERT(!memcmp(b64i.buf, decoded.buf + encoded.len, b64i.len), + "Base 64 roundtrip failed:\n %s", + decoded.buf); + FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&b64message)); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + } + { + fprintf(stderr, "* Testing JSON style character escaping / unescaping.\n"); + FIO_NAME(FIO_STR_NAME, s) unescaped = FIO_STR_INIT; + fio_str_info_s ue; + const char *utf8_sample = /* three hearts, small-big-small*/ + "\xf0\x9f\x92\x95\xe2\x9d\xa4\xef\xb8\x8f\xf0\x9f\x92\x95"; + FIO_NAME(FIO_STR_NAME, write) + (FIO_PTR_TAG(&unescaped), utf8_sample, FIO_STRLEN(utf8_sample)); + for (int i = 0; i < 256; ++i) { + uint8_t c = i; + ue = FIO_NAME(FIO_STR_NAME, write)(FIO_PTR_TAG(&unescaped), &c, 1); + } + fio_str_info_s encoded = + FIO_NAME(FIO_STR_NAME, write_escape)(pstr, ue.buf, ue.len); + // fprintf(stderr, "* %s\n", encoded.buf); + fio_str_info_s decoded; + { + FIO_NAME(FIO_STR_NAME, s) tmps; + FIO_NAME(FIO_STR_NAME, init_copy2)(&tmps, pstr); + decoded = FIO_NAME(FIO_STR_NAME, + write_unescape)(pstr, + FIO_NAME(FIO_STR_NAME, ptr)(&tmps), + FIO_NAME(FIO_STR_NAME, len)(&tmps)); + FIO_NAME(FIO_STR_NAME, destroy)(&tmps); + encoded.buf = decoded.buf; + } + FIO_ASSERT(!memcmp(encoded.buf, utf8_sample, FIO_STRLEN(utf8_sample)), + "valid UTF-8 data shouldn't be escaped:\n%.*s\n%s", + (int)encoded.len, + encoded.buf, + decoded.buf); + FIO_ASSERT(encoded.len, "JSON encoding failed"); + FIO_ASSERT(decoded.len > encoded.len, + "JSON decoding failed:\n%s", + encoded.buf); + FIO_ASSERT(ue.len == decoded.len - encoded.len, + "JSON roundtrip length error, %zu != %zu (%zu - %zu):\n %s", + ue.len, + decoded.len - encoded.len, + decoded.len, + encoded.len, + decoded.buf); + + FIO_ASSERT(!memcmp(ue.buf, decoded.buf + encoded.len, ue.len), + "JSON roundtrip failed:\n %s", + decoded.buf); + FIO_NAME(FIO_STR_NAME, destroy)(FIO_PTR_TAG(&unescaped)); + FIO_NAME(FIO_STR_NAME, destroy)(pstr); + } +} +#undef FIO__STR_SMALL_CAPA +#undef FIO_STR_WRITE_TEST_FUNC +#endif /* FIO_STR_WRITE_TEST_FUNC */ -/** - * The core get function. This function returns NULL if item is missing. - * - * NOTE: the function returns a pointer to the map's internal storage. - */ -SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * - FIO_NAME(FIO_MAP_NAME, get_ptr)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif - FIO_MAP_KEY key); /* ***************************************************************************** -Map Iteration and Traversal +String Cleanup ***************************************************************************** */ +#endif /* FIO_EXTERN_COMPLETE */ -/** Iteration information structure passed to the callback. */ -typedef struct FIO_NAME(FIO_MAP_NAME, each_s) { - /** The being iterated. Once set, cannot be safely changed. */ - FIO_MAP_PTR const parent; - /** The current object's index */ - uint64_t index; - /** The callback / task called for each index, may be updated mid-cycle. */ - int (*task)(struct FIO_NAME(FIO_MAP_NAME, each_s) * info); - /** Opaque user data. */ - void *udata; -#ifdef FIO_MAP_VALUE - /** The object's value at the current index. */ - FIO_MAP_VALUE value; -#endif - /** The object's key the current index. */ - FIO_MAP_KEY key; -} FIO_NAME(FIO_MAP_NAME, each_s); +#undef FIO_STR_SMALL +#undef FIO_STR_SMALL_CAPA +#undef FIO_STR_SMALL_DATA +#undef FIO_STR_SMALL_LEN +#undef FIO_STR_SMALL_LEN_SET -/** - * Iteration using a callback for each element in the map. - * - * The callback task function must accept an each_s pointer, see above. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. - */ -SFUNC uint32_t FIO_NAME(FIO_MAP_NAME, - each)(FIO_MAP_PTR map, - int (*task)(FIO_NAME(FIO_MAP_NAME, each_s) *), - void *udata, - ssize_t start_at); +#undef FIO_STR_BIG_CAPA +#undef FIO_STR_BIG_CAPA_SET +#undef FIO_STR_BIG_DATA +#undef FIO_STR_BIG_FREE_BUF +#undef FIO_STR_BIG_IS_DYNAMIC +#undef FIO_STR_BIG_LEN +#undef FIO_STR_BIG_LEN_SET +#undef FIO_STR_BIG_SET_STATIC -/** - * Returns the next iterator object after `current_pos` or the first if `NULL`. - * - * Note that adding objects to the map or rehashing between iterations could - * incur performance penalties when re-setting and re-seeking the previous - * iterator position. - * - * Adding objects to, or rehashing, an unordered maps could invalidate the - * iterator object completely as the ordering may have changed and so the "next" - * object might be any object in the map. - */ -SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) - FIO_NAME(FIO_MAP_NAME, - get_next)(FIO_MAP_PTR map, - FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos); +#undef FIO_STR_FREEZE_ -/** - * Returns the next iterator object after `current_pos` or the last if `NULL`. - * - * See notes in `get_next`. - */ -SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) - FIO_NAME(FIO_MAP_NAME, - get_prev)(FIO_MAP_PTR map, - FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos); +#undef FIO_STR_IS_FROZEN +#undef FIO_STR_IS_SMALL +#undef FIO_STR_NAME -/** Returns 1 if the iterator is out of bounds, otherwise returns 0. */ -FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, - iterator_is_valid)(FIO_NAME(FIO_MAP_NAME, iterator_s) * - iterator); +#undef FIO_STR_OPTIMIZE4IMMUTABILITY +#undef FIO_STR_OPTIMIZE_EMBEDDED +#undef FIO_STR_PTR +#undef FIO_STR_THAW_ +#undef FIO_STR_RESERVE_NAME -/** Returns a pointer to the node object in the internal map. */ -FIO_IFUNC FIO_NAME(FIO_MAP_NAME, node_s) * - FIO_NAME(FIO_MAP_NAME, - iterator2node)(FIO_MAP_PTR map, - FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator); +#endif /* FIO_STR_NAME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_ARRAY_NAME ary /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -#ifndef FIO_MAP_EACH -/** Iterates through the map using an iterator object. */ -#define FIO_MAP_EACH(map_name, map_ptr, i) \ - for (FIO_NAME(map_name, iterator_s) \ - i = FIO_NAME(map_name, get_next)(map_ptr, NULL); \ - FIO_NAME(map_name, iterator_is_valid)(&i); \ - i = FIO_NAME(map_name, get_next)(map_ptr, &i)) -/** Iterates through the map using an iterator object. */ -#define FIO_MAP_EACH_REVERSED(map_name, map_ptr, i) \ - for (FIO_NAME(map_name, iterator_s) \ - i = FIO_NAME(map_name, get_prev)(map_ptr, NULL); \ - FIO_NAME(map_name, iterator_is_valid)(&i); \ - i = FIO_NAME(map_name, get_prev)(map_ptr, &i)) -#endif /* FIO_MAP_EACH */ + Dynamic Arrays -/* ***************************************************************************** -Optional Sorting Support - TODO? (convert to array, sort, rehash) -***************************************************************************** */ -#if defined(FIO_MAP_KEY_IS_GREATER_THAN) && !defined(FIO_SORT_TYPE) && \ - FIO_MAP_ORDERED -#undef FIO_SORT_NAME -#endif -/* ***************************************************************************** -Map Implementation - inlined static functions +Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ +#ifdef FIO_ARRAY_NAME -#ifndef FIO_MAP_CAPA_BITS_LIMIT -/* Note: cannot be more than 31 bits unless some of the code is rewritten. */ -#define FIO_MAP_CAPA_BITS_LIMIT 31 +#ifndef FIO_ARRAY_NOT_FOUND +#define FIO_ARRAY_NOT_FOUND ((uint32_t)-1) #endif -/* Theoretical map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, capa)(FIO_MAP_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN(map, 0); - FIO_MAP_T *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (o->map) - return (uint32_t)((size_t)1ULL << o->bits); - return 0; -} - -/* The number of objects in the map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, count)(FIO_MAP_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN(map, 0); - return FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map)->count; -} - -/** Returns 1 if the iterator points to a valid object, otherwise returns 0. */ -FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, - iterator_is_valid)(FIO_NAME(FIO_MAP_NAME, iterator_s) * - iterator) { - return (iterator && iterator->private_.map_validator); -} - -/** Returns the key value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, - node2key)(FIO_NAME(FIO_MAP_NAME, node_s) * - node) { - FIO_MAP_KEY r = (FIO_MAP_KEY){0}; - if (!node) - return r; - return FIO_MAP_KEY_FROM_INTERNAL(node->key); -} - -/** Returns the hash value associated with the node's pointer. */ -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, - node2hash)(FIO_NAME(FIO_MAP_NAME, node_s) * node) { - uint32_t r = (uint32_t){0}; - if (!node) - return r; -#if FIO_MAP_RECALC_HASH - FIO_MAP_KEY k = FIO_MAP_KEY_FROM_INTERNAL(node->key); - uint64_t hash = FIO_MAP_HASH_FN(k); - hash += !hash; - return hash; -#else - return node->hash; +#ifdef FIO_ARRAY_TYPE_STR +#ifndef FIO_ARRAY_TYPE +#define FIO_ARRAY_TYPE fio_keystr_s +#endif +#ifndef FIO_ARRAY_TYPE_COPY +#define FIO_ARRAY_TYPE_COPY(dest, src) ((dest) = fio_keystr_init((src))) +#endif +#ifndef FIO_ARRAY_TYPE_DESTROY +#define FIO_ARRAY_TYPE_DESTROY(obj) fio_keystr_destroy(&(obj)); +#endif +#ifndef FIO_ARRAY_TYPE_CMP +#define FIO_ARRAY_TYPE_CMP(a, b) fio_keystr_is_eq((a), (b)) +#endif +#undef FIO_ARRAY_DESTROY_AFTER_COPY +#define FIO_ARRAY_DESTROY_AFTER_COPY 1 #endif -} -#ifdef FIO_MAP_VALUE -/** Returns the value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP_VALUE FIO_NAME(FIO_MAP_NAME, - node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * - node) { - FIO_MAP_VALUE r = (FIO_MAP_VALUE){0}; - if (!node) - return r; - return FIO_MAP_VALUE_FROM_INTERNAL(node->value); -} +#ifndef FIO_ARRAY_TYPE +/** The type for array elements (an array of FIO_ARRAY_TYPE) */ +#define FIO_ARRAY_TYPE void * +/** An invalid value for that type (if any). */ +#define FIO_ARRAY_TYPE_INVALID NULL +#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1 #else -/* If called for a node without a - * value, returns the key (simplifies - * stuff). */ -FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, - node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * - node) { - return FIO_NAME(FIO_MAP_NAME, node2key)(node); -} +#ifndef FIO_ARRAY_TYPE_INVALID +/** An invalid value for that type (if any). */ +#define FIO_ARRAY_TYPE_INVALID ((FIO_ARRAY_TYPE){0}) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_INVALID_SIMPLE 1 +#endif #endif -/** Returns the key value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2key_ptr)( - FIO_NAME(FIO_MAP_NAME, node_s) * node) { - if (!node) - return NULL; - return &(node->key); -} +#ifndef FIO_ARRAY_TYPE_INVALID_SIMPLE +/** Is the FIO_ARRAY_TYPE_INVALID object memory is all zero? (yes = 1) */ +#define FIO_ARRAY_TYPE_INVALID_SIMPLE 0 +#endif -#ifdef FIO_MAP_VALUE -/** Returns the value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP_VALUE_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( - FIO_NAME(FIO_MAP_NAME, node_s) * node) { - if (!node) - return NULL; - return &(node->value); -} -#else -/* If called for a node without a - * value, returns the key (simplifies - * stuff). */ -FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( - FIO_NAME(FIO_MAP_NAME, node_s) * node) { - return FIO_NAME(FIO_MAP_NAME, node2key_ptr)(node); -} +#ifndef FIO_ARRAY_TYPE_COPY +/** Handles a copy operation for an array's element. */ +#define FIO_ARRAY_TYPE_COPY(dest, src) (dest) = (src) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_COPY_SIMPLE 1 #endif -/** Gets a value from the map, if exists. */ -FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, get)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, +#ifndef FIO_ARRAY_TYPE_DESTROY +/** Handles a destroy / free operation for an array's element. */ +#define FIO_ARRAY_TYPE_DESTROY(obj) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_DESTROY_SIMPLE 1 #endif - FIO_MAP_KEY key) { - return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, get_ptr)(map, -#if !defined(FIO_MAP_HASH_FN) - hash, + +#ifndef FIO_ARRAY_TYPE_CMP +/** Handles a comparison operation for an array's element. */ +#define FIO_ARRAY_TYPE_CMP(a, b) (a) == (b) +/* internal flag - do not set */ +#define FIO_ARRAY_TYPE_CMP_SIMPLE 1 #endif - key)); -} -/** Sets a value in the map, hash maps will overwrite existing data if any. */ -FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, +#ifndef FIO_ARRAY_TYPE_CONCAT_COPY +#define FIO_ARRAY_TYPE_CONCAT_COPY FIO_ARRAY_TYPE_COPY +#define FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE FIO_ARRAY_TYPE_COPY_SIMPLE #endif -#ifdef FIO_MAP_VALUE - FIO_MAP_KEY key, - FIO_MAP_VALUE obj, - FIO_MAP_VALUE_INTERNAL *old +/** + * The FIO_ARRAY_DESTROY_AFTER_COPY macro should be set if + * FIO_ARRAY_TYPE_DESTROY should be called after FIO_ARRAY_TYPE_COPY when an + * object is removed from the array after being copied to an external container + * (an `old` pointer) + */ +#ifndef FIO_ARRAY_DESTROY_AFTER_COPY +#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE && !FIO_ARRAY_TYPE_COPY_SIMPLE +#define FIO_ARRAY_DESTROY_AFTER_COPY 1 #else - FIO_MAP_KEY key +#define FIO_ARRAY_DESTROY_AFTER_COPY 0 #endif -) { - return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, set_ptr)(map, -#if !defined(FIO_MAP_HASH_FN) - hash, #endif - key -#ifdef FIO_MAP_VALUE - , - obj, - old, - 1 + +/* Extra empty slots when allocating memory. */ +#ifndef FIO_ARRAY_PADDING +#define FIO_ARRAY_PADDING 4 #endif - )); -} -/** Sets a value in the map if not set previously. */ -FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set_if_missing)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, +/* + * Uses the array structure to embed object, if there's space for them. + * + * This optimizes small arrays and specifically touplets. For `void *` type + * arrays this allows for 2 objects to be embedded, resulting in faster access + * due to cache locality and reduced pointer redirection. + * + * For large arrays, it is better to disable this feature. + * + * Note: values larger than 1 add a memory allocation cost to the array + * container, adding enough room for at least `FIO_ARRAY_ENABLE_EMBEDDED - 1` + * items. + */ +#ifndef FIO_ARRAY_ENABLE_EMBEDDED +#define FIO_ARRAY_ENABLE_EMBEDDED 1 #endif - FIO_MAP_KEY key -#ifdef FIO_MAP_VALUE - , - FIO_MAP_VALUE obj -#endif -) { - return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, set_ptr)(map, -#if !defined(FIO_MAP_HASH_FN) - hash, -#endif - key -#ifdef FIO_MAP_VALUE - , - obj, - NULL, - 0 + +/* Sets memory growth to exponentially increase. Consumes more memory. */ +#ifndef FIO_ARRAY_EXPONENTIAL +#define FIO_ARRAY_EXPONENTIAL 0 #endif - )); -} -/** Returns a pointer to the node object in the internal map. */ -FIO_IFUNC FIO_NAME(FIO_MAP_NAME, node_s) * - FIO_NAME(FIO_MAP_NAME, - iterator2node)(FIO_MAP_PTR map, - FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator) { - FIO_NAME(FIO_MAP_NAME, node_s) *node = NULL; - if (!iterator || !iterator->private_.map_validator) - return node; - FIO_PTR_TAG_VALID_OR_RETURN(map, node); - FIO_NAME(FIO_MAP_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - node = o->map + iterator->private_.index; - return node; -} +#undef FIO_ARRAY_SIZE2WORDS +#define FIO_ARRAY_SIZE2WORDS(size) \ + ((sizeof(FIO_ARRAY_TYPE) & 1) ? (((size) & (~15)) + 16) \ + : (sizeof(FIO_ARRAY_TYPE) & 2) ? (((size) & (~7)) + 8) \ + : (sizeof(FIO_ARRAY_TYPE) & 4) ? (((size) & (~3)) + 4) \ + : (sizeof(FIO_ARRAY_TYPE) & 8) ? (((size) & (~1)) + 2) \ + : (size)) /* ***************************************************************************** -Map Implementation - possibly externed functions. +Dynamic Arrays - type ***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP_NAME, destroy)) +/** an Array type. */ +typedef struct FIO_NAME(FIO_ARRAY_NAME, s) { + /* start common header (with embedded array type) */ + /** the offset to the first item. */ + uint32_t start; + /** The offset to the first empty location the array. */ + uint32_t end; + /* end common header (with embedded array type) */ + /** The array's capacity - limited to 32bits, but we use the extra padding. */ + uint32_t capa; + /** a pointer to the array's memory (if not embedded) */ + FIO_ARRAY_TYPE *ary; +#if FIO_ARRAY_ENABLE_EMBEDDED > 1 + /** Do we wanted larger small-array optimizations? */ + FIO_ARRAY_TYPE + extra_memory_for_embedded_arrays[(FIO_ARRAY_ENABLE_EMBEDDED - 1)] +#endif +} FIO_NAME(FIO_ARRAY_NAME, s); + +#ifdef FIO_PTR_TAG_TYPE +#define FIO_ARRAY_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, s) * +#endif + /* ***************************************************************************** -Constructors +Dynamic Arrays - API ***************************************************************************** */ -/* do we have a constructor? */ +#ifndef FIO_ARRAY_INIT +/* Initialization macro. */ +#define FIO_ARRAY_INIT \ + { 0 } +#endif + #ifndef FIO_REF_CONSTRUCTOR_ONLY -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP_NAME, s)) -/* Allocates a new object on the heap and initializes it's memory. */ -FIO_IFUNC FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, new)(void) { - FIO_NAME(FIO_MAP_NAME, s) *o = - (FIO_NAME(FIO_MAP_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); - if (!o) - return (FIO_MAP_PTR)NULL; - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP_NAME, s)); - *o = (FIO_NAME(FIO_MAP_NAME, s))FIO_MAP_INIT; - return (FIO_MAP_PTR)FIO_PTR_TAG(o); -} -/* Frees any internal data AND the object's container! */ -FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, free)(FIO_MAP_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP_NAME, destroy)(map); - FIO_NAME(FIO_MAP_NAME, s) *o = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP_NAME, s), map); - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP_NAME, s)); - FIO_MEM_FREE_(o, sizeof(*o)); -} -#endif /* FIO_REF_CONSTRUCTOR_ONLY */ -/* ***************************************************************************** +/* Allocates a new array object on the heap and initializes it's memory. */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void); +/* Frees an array's internal data AND it's container! */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary); +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ +/* Destroys any objects stored in the array and frees the internal state. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, destroy)(FIO_ARRAY_PTR ary); -Internal Helpers (Core) +/** Returns the number of elements in the Array. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, count)(FIO_ARRAY_PTR ary); +/** Returns the current, temporary, array capacity (it's dynamic). */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, capa)(FIO_ARRAY_PTR ary); +/** + * Returns 1 if the array is embedded, 0 if it has memory allocated and -1 on an + * error. + */ +FIO_IFUNC int FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(FIO_ARRAY_PTR ary); +/** + * Returns a pointer to the C array containing the objects. + */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME2(FIO_ARRAY_NAME, ptr)(FIO_ARRAY_PTR ary); -***************************************************************************** */ +/** + * Reserves a minimal capacity for additional elements to be added to the array. + * + * If `capa` is negative, new memory will be allocated at the beginning of the + * array rather then it's end. + * + * Returns the array's new capacity. + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, reserve)(FIO_ARRAY_PTR ary, + int64_t capa); -/** internal object data representation */ -struct FIO_NAME(FIO_MAP_NAME, __imap_s) { - uint8_t h[64]; -}; +/** + * Adds all the items in the `src` Array to the end of the `dest` Array. + * + * The `src` Array remain untouched. + * + * Always returns the destination array (`dest`). + */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, concat)(FIO_ARRAY_PTR dest, + FIO_ARRAY_PTR src); -#ifndef FIO_MAP_ATTACK_LIMIT -#define FIO_MAP_ATTACK_LIMIT 16 -#endif -#ifndef FIO_MAP_MINIMAL_BITS -#define FIO_MAP_MINIMAL_BITS 1 -#endif -#ifndef FIO_MAP_CUCKOO_STEPS -/* Prime numbers are better */ -#define FIO_MAP_CUCKOO_STEPS (0x43F82D0BUL) /* a big high prime */ -#endif -#ifndef FIO_MAP_SEEK_LIMIT -#define FIO_MAP_SEEK_LIMIT 13U -#endif -#ifndef FIO_MAP_ARRAY_LOG_LIMIT -#define FIO_MAP_ARRAY_LOG_LIMIT 3 -#endif -#ifndef FIO_MAP_CAPA -#define FIO_MAP_CAPA(bits) ((size_t)1ULL << (bits)) -#endif +/** + * Sets `index` to the value in `data`. + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + * + * If `old` isn't NULL, the existing data will be copied to the location pointed + * to by `old` before the copy in the Array is destroyed. + * + * Returns a pointer to the new object, or NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, set)(FIO_ARRAY_PTR ary, + int64_t index, + FIO_ARRAY_TYPE data, + FIO_ARRAY_TYPE *old); -#ifndef FIO_MAP_IS_SPARSE -#define FIO_MAP_IS_SPARSE(map) \ - (o->bits > FIO_MAP_ARRAY_LOG_LIMIT && ((capa >> 2) > o->count)) -#endif +/** + * Returns the value located at `index` (no copying is performed). + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + */ +FIO_IFUNC FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, get)(FIO_ARRAY_PTR ary, + int64_t index); -/* Allocates resources for a new (clean) map. */ -FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, - __allocate_map)(FIO_NAME(FIO_MAP_NAME, s) * o, - uint32_t bits) { - if (bits < FIO_MAP_MINIMAL_BITS) - bits = FIO_MAP_MINIMAL_BITS; - if (bits > FIO_MAP_CAPA_BITS_LIMIT) - return -1; - size_t s = (sizeof(o->map[0]) + 1) << bits; - FIO_NAME(FIO_MAP_NAME, node_s) *n = - (FIO_NAME(FIO_MAP_NAME, node_s) *)FIO_MEM_REALLOC_(NULL, 0, s, 0); - if (!n) - return -1; - if (!FIO_MEM_REALLOC_IS_SAFE_) /* set only imap to zero */ - FIO_MEMSET((n + (1ULL << bits)), 0, (1ULL << bits)); - *o = (FIO_NAME(FIO_MAP_NAME, s)){.map = n, .bits = bits}; - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP_NAME, destroy)); - return 0; -} +/** + * Returns the index of the object or (uint32_t)-1 if the object wasn't found. + * + * If `start_at` is negative (i.e., -1), than seeking will be performed in + * reverse, where -1 == last index (-2 == second to last, etc'). + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, find)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data, + int64_t start_at); -/* The number of objects in the map capacity. */ -FIO_IFUNC uint8_t *FIO_NAME(FIO_MAP_NAME, - __imap)(FIO_NAME(FIO_MAP_NAME, s) const *o) { - return (uint8_t *)(o->map + FIO_MAP_CAPA(o->bits)); -} +/** + * Removes an object from the array, MOVING all the other objects to prevent + * "holes" in the data. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns 0 on success and -1 on error. + * + * This action is O(n) where n in the length of the array. + * It could get expensive. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, remove)(FIO_ARRAY_PTR ary, + int64_t index, + FIO_ARRAY_TYPE *old); -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, __byte_hash)(uint64_t hash) { - hash = (hash >> 48) ^ (hash >> 56); - hash &= 0xFF; - hash += !(hash); - hash -= (hash == 255); - return hash; -} +/** + * Removes all occurrences of an object from the array (if any), MOVING all the + * existing objects to prevent "holes" in the data. + * + * Returns the number of items removed. + * + * This action is O(n) where n in the length of the array. + * It could get expensive. + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, remove2)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data); -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, - __is_eq_hash)(FIO_NAME(FIO_MAP_NAME, node_s) * o, - uint64_t hash) { -#if FIO_MAP_RECALC_HASH && defined(FIO_MAP_HASH_FN) - uint64_t khash = FIO_MAP_HASH_FN(FIO_MAP_KEY_FROM_INTERNAL(o->key)); - khash += !khash; -#else - const uint64_t khash = o->hash; -#endif - return (khash == hash); -} +/** Attempts to lower the array's memory consumption. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, compact)(FIO_ARRAY_PTR ary); -typedef struct FIO_NAME(FIO_MAP_NAME, __each_node_s) { - FIO_NAME(FIO_MAP_NAME, s) * map; - FIO_NAME(FIO_MAP_NAME, node_s) * node; - int (*fn)(struct FIO_NAME(FIO_MAP_NAME, __each_node_s) *); - void *udata; -} FIO_NAME(FIO_MAP_NAME, __each_node_s); +/** + * Pushes an object to the end of the Array. Returns a pointer to the new object + * or NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, push)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data); -/* perform task for each node. */ -FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, - __each_node)(FIO_NAME(FIO_MAP_NAME, s) * o, - int (*fn)(FIO_NAME(FIO_MAP_NAME, - __each_node_s) *), - void *udata) { - FIO_NAME(FIO_MAP_NAME, __each_node_s) - each = {.map = o, .fn = fn, .udata = udata}; - uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); - size_t counter = o->count; - if (!counter) - return 0; -#if FIO_MAP_ORDERED - FIO_INDEXED_LIST_EACH(o->map, node, o->head, pos) { - each.node = o->map + pos; - if (each.fn(&each)) - return -1; - --counter; - if (FIO_UNLIKELY(imap != FIO_NAME(FIO_MAP_NAME, __imap)(o))) - return -1; - } -#else - const size_t len = FIO_MAP_CAPA(o->bits); - if (FIO_UNLIKELY(o->bits > 5 && (FIO_MAP_CAPA(o->bits) >> 2) > o->count)) - goto sparse_map; - for (size_t i = 0; counter; ++i) { - if (!imap[i] || imap[i] == 255) - continue; - each.node = o->map + i; - if (FIO_UNLIKELY(each.fn(&each))) - return -1; - --counter; - if (FIO_UNLIKELY(imap != FIO_NAME(FIO_MAP_NAME, __imap)(o))) - return -1; - } - FIO_ASSERT_DEBUG( - !counter, - "detected error while looping over all elements in map (%zu/%zu)", - counter, - (size_t)FIO_MAP_CAPA(o->bits)); - return 0; +/** + * Removes an object from the end of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, pop)(FIO_ARRAY_PTR ary, FIO_ARRAY_TYPE *old); -sparse_map: - for (size_t i = 0; counter && i < len; i += 64) { - uint64_t bitmap = 0; - for (size_t j = 0; j < 64; j += 8) { - uint64_t tmp = *((uint64_t *)(imap + i + j)); - uint64_t inv = ~tmp; - tmp = FIO_HAS_FULL_BYTE64(tmp); - inv = FIO_HAS_FULL_BYTE64(inv); - tmp |= inv; - FIO_HAS_BYTE2BITMAP(tmp, 7); - bitmap |= (tmp << j); - } - bitmap = ~bitmap; /* where 1 was a free slot, now it's an occupied one */ - for (size_t j = 0; bitmap; ++j) { - if ((bitmap & 1)) { - each.node = o->map + i + j; - if (each.fn(&each)) - return -1; - --counter; - if (imap != FIO_NAME(FIO_MAP_NAME, __imap)(o)) - return -1; - } - bitmap >>= 1; - } - } -#endif - FIO_ASSERT_DEBUG( - !counter, - "detected error while looping over all elements in map (%zu/%zu)", - counter, - (size_t)FIO_MAP_CAPA(o->bits)); - return 0; -} +/** + * Unshifts an object to the beginning of the Array. Returns a pointer to the + * new object or NULL on error. + * + * This could be expensive, causing `memmove`. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, unshift)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE data); -#if !FIO_MAP_KEY_DESTROY_SIMPLE || !FIO_MAP_VALUE_DESTROY_SIMPLE -static int FIO_NAME(FIO_MAP_NAME, - __destroy_map_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * - e) { - FIO_MAP_KEY_DESTROY(e->node->key); - FIO_MAP_VALUE_DESTROY(e->node->value); - return 0; -} -#endif +/** + * Removes an object from the beginning of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, shift)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE *old); -/* Destroys and exsiting map. */ -FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, - __destroy_map)(FIO_NAME(FIO_MAP_NAME, s) * o, - _Bool should_zero) { -#if !FIO_MAP_KEY_DESTROY_SIMPLE || !FIO_MAP_VALUE_DESTROY_SIMPLE - FIO_NAME(FIO_MAP_NAME, __each_node) - (o, FIO_NAME(FIO_MAP_NAME, __destroy_map_task), NULL); -#endif - if (should_zero) /* set only imap to zero */ - FIO_MEMSET((o->map + (1ULL << o->bits)), 0, (1ULL << o->bits)); - o->count = 0; -} +/** Iteration information structure passed to the callback. */ +typedef struct FIO_NAME(FIO_ARRAY_NAME, each_s) { + /** The array iterated. Once set, cannot be safely changed. */ + FIO_ARRAY_PTR const parent; + /** The current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct FIO_NAME(FIO_ARRAY_NAME, each_s) * info); + /** Opaque user data. */ + void *udata; + /** The object / value at the current index. */ + FIO_ARRAY_TYPE value; + /* memory padding used for FIOBJ */ + uint64_t padding; +} FIO_NAME(FIO_ARRAY_NAME, each_s); -/* Destroys and exsiting map. */ -FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, __free_map)(FIO_NAME(FIO_MAP_NAME, s) * o, - _Bool should_destroy) { - if (!o->map) - return; - if (should_destroy) - FIO_NAME(FIO_MAP_NAME, __destroy_map)(o, 0); - FIO_MEM_FREE_(o->map, ((sizeof(o->map[0]) + 1) << o->bits)); - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP_NAME, destroy)); - *o = (FIO_NAME(FIO_MAP_NAME, s)){0}; -} +/** + * Iteration using a callback for each entry in the array. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, + each)(FIO_ARRAY_PTR ary, + int (*task)(FIO_NAME(FIO_ARRAY_NAME, each_s) * + info), + void *udata, + int64_t start_at); -#ifndef H___FIO_MAP_INDEX_TYPE___H -#define H___FIO_MAP_INDEX_TYPE___H -typedef struct { - uint32_t home; - uint32_t act; - uint32_t alt; - uint32_t bhash; -} fio___map_node_info_s; +#ifndef FIO_ARRAY_EACH +/** + * Iterates through the array using a `for` loop. + * + * Access the object with the pointer `pos`. The `pos` variable can be named + * however you please. + * + * Avoid editing the array during a FOR loop, although I hope it's possible, I + * wouldn't count on it. + * + * **Note**: this variant supports automatic pointer tagging / untagging. + */ +#define FIO_ARRAY_EACH(array_name, array, pos) \ + for (FIO_NAME(array_name, ____type_t) \ + *first___ai = NULL, \ + *pos = FIO_NAME(array_name, each_next)((array), &first___ai, NULL); \ + pos; \ + pos = FIO_NAME(array_name, each_next)((array), &first___ai, pos)) #endif -/** internal object data representation */ -typedef struct FIO_NAME(FIO_MAP_NAME, __o_node_s) { - uint64_t hash; - FIO_MAP_KEY key; -#ifdef FIO_MAP_VALUE - FIO_MAP_VALUE value; -#endif -} FIO_NAME(FIO_MAP_NAME, __o_node_s); +/** + * Returns a pointer to the (next) object in the array. + * + * Returns a pointer to the first object if `pos == NULL` and there are objects + * in the array. + * + * The first pointer is automatically set and it allows object insertions and + * memory effecting functions to be called from within the loop. + * + * If the object in `pos` (or an object before it) were removed, consider + * passing `pos-1` to the function, to avoid skipping any elements while + * looping. + * + * Returns the next object if both `first` and `pos` are valid. + * + * Returns NULL if `pos` was the last object or no object exist. + * + * Returns the first object if either `first` or `pos` are invalid. + * + */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, + each_next)(FIO_ARRAY_PTR ary, + FIO_ARRAY_TYPE **first, + FIO_ARRAY_TYPE *pos); -/* seek a node for very small collections 8 item capacity at most */ -FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_mini)( - FIO_NAME(FIO_MAP_NAME, s) * o, - FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { - // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); - fio___map_node_info_s r = { - (uint32_t)-1, - (uint32_t)-1, - (uint32_t)-1, - (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; - if (!o->bits) - return r; - const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); - const uint32_t capa = (uint32_t)FIO_MAP_CAPA(o->bits); - const uint32_t mask = capa - 1; - size_t pos = node->hash & 0xFF; - for (uint32_t i = 0; i < capa; ++i) { - pos = (pos + i) & mask; - if (imap[pos] == r.bhash && - FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash) && - FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { - r.act = (uint32_t)pos; - return r; - } else if (!imap[pos]) { - r.alt = r.home = (uint32_t)pos; - return r; - } else if (imap[pos] == 255U) { /* "home" has been occupied before */ - r.alt = r.home = (uint32_t)pos; - } +/* ***************************************************************************** +Dynamic Arrays - embedded arrays +***************************************************************************** */ +typedef struct { + /* start common header */ + /** the offset to the first item. */ + uint32_t start; + /** The offset to the first empty location the array. */ + uint32_t end; + /* end common header */ + FIO_ARRAY_TYPE embedded[]; +} FIO_NAME(FIO_ARRAY_NAME, ___embedded_s); + +#define FIO_ARRAY2EMBEDDED(a) ((FIO_NAME(FIO_ARRAY_NAME, ___embedded_s) *)(a)) + +#if FIO_ARRAY_ENABLE_EMBEDDED +#define FIO_ARRAY_IS_EMBEDDED(a) \ + ((sizeof(FIO_ARRAY_TYPE) + \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) <= \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) && \ + (((a)->start > (a)->end) || !(a)->ary)) +#define FIO_ARRAY_IS_EMBEDDED_PTR(ary, ptr) \ + ((sizeof(FIO_ARRAY_TYPE) + \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) <= \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) && \ + (uintptr_t)(ptr) > (uintptr_t)(ary) && \ + (uintptr_t)(ptr) < (uintptr_t)((ary) + 1)) +#define FIO_ARRAY_EMBEDDED_CAPA \ + ((sizeof(FIO_ARRAY_TYPE) + \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) > \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) \ + ? 0 \ + : ((sizeof(FIO_NAME(FIO_ARRAY_NAME, s)) - \ + sizeof(FIO_NAME(FIO_ARRAY_NAME, ___embedded_s))) / \ + sizeof(FIO_ARRAY_TYPE))) + +#else +#define FIO_ARRAY_IS_EMBEDDED(a) 0 +#define FIO_ARRAY_IS_EMBEDDED_PTR(ary, ptr) 0 +#define FIO_ARRAY_EMBEDDED_CAPA 0 + +#endif /* FIO_ARRAY_ENABLE_EMBEDDED */ +/* ***************************************************************************** +Inlined functions +***************************************************************************** */ +/** Returns the number of elements in the Array. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, count)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: return ary->end - ary->start; + case 1: return ary->start; } - return r; + return 0; } -/* seek a node for medium sized collections, 16-512 item capacity. */ -FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_med)( - FIO_NAME(FIO_MAP_NAME, s) * o, - FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { - // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); - static int guard_print = 0; - fio___map_node_info_s r = { - (uint32_t)-1, - (uint32_t)-1, - (uint32_t)-1, - (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; - const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); - const uint32_t mask = (uint32_t)(FIO_MAP_CAPA(o->bits) - 1); - uint32_t guard = FIO_MAP_ATTACK_LIMIT + 1; - uint32_t pos = r.home = (node->hash & mask); - uint32_t step = 2; - uint32_t attempts = (mask < 511) ? ((mask >> 2) | 8) : 127; - for (; r.alt == (uint32_t)-1 && attempts; --attempts) { - if (!imap[pos]) { - r.alt = pos; - return r; - } else if (imap[pos] == 255U) { /* "home" has been occupied before */ - r.alt = pos; - } else if (imap[pos] == r.bhash && - FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash)) { - if (FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { - r.act = pos; - return r; - } - if (!--guard) { - r.act = pos; - goto possible_attack; - } - } - pos = ((pos + (step++)) & mask); - } - for (; attempts; --attempts) { - if (imap[pos] == r.bhash && - FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash)) { - if (FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { - r.act = pos; - return r; - } - if (!--guard) { - r.act = pos; - goto possible_attack; - } - } - if (!imap[pos]) - return r; - pos = ((pos + (step++)) & mask); +/** Returns the current, temporary, array capacity (it's dynamic). */ +FIO_IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, capa)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: return (uint32_t)ary->capa; + case 1: return FIO_ARRAY_EMBEDDED_CAPA; } - if (r.alt == (uint32_t)-1) - r.home = r.alt; - return r; + return 0; +} -possible_attack: - if (!guard_print) { - guard_print = 1; - FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( - FIO_NAME(FIO_MAP_NAME, s)) " under attack? (full collision guard)"); +/** + * Returns a pointer to the C array containing the objects. + */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME2(FIO_ARRAY_NAME, ptr)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, NULL); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: return ary->ary + ary->start; + case 1: return FIO_ARRAY2EMBEDDED(ary)->embedded; } - return r; + return NULL; } -/* seek a node for larger collections, where 8 byte grouping is meaningless */ -FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_full)( - FIO_NAME(FIO_MAP_NAME, s) * o, - FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { - // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); - static int guard_print = 0; - fio___map_node_info_s r = { - (uint32_t)-1, - (uint32_t)-1, - (uint32_t)-1, - (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; - const uint32_t mask = (FIO_MAP_CAPA(o->bits) - 1) & (~(uint32_t)7ULL); - const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); - const size_t attempt_limit = o->bits + 7; - const uint64_t mbyte64 = ~(UINT64_C(0x0101010101010101) * (uint64_t)r.bhash); - uint32_t guard = FIO_MAP_ATTACK_LIMIT + 1; - uint32_t pos = r.home = (node->hash & mask); - size_t attempt = 0; - for (; r.alt == (uint32_t)-1 && attempt < attempt_limit; ++attempt) { - uint64_t group = fio_buf2u64_le(imap + pos); - group ^= mbyte64; - group &= UINT64_C(0x7F7F7F7F7F7F7F7F); - group += UINT64_C(0x0101010101010101); - group &= UINT64_C(0x8080808080808080); - while (group) { - uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); - group ^= (uint64_t)1ULL << offset; - offset >>= 3; - offset += pos; - if (imap[offset] == r.bhash && - FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + offset, node->hash)) { - if (FIO_MAP_KEY_CMP(o->map[offset].key, node->key)) { - r.act = offset; - return r; - } - if (!--guard) { - r.act = offset; - goto possible_attack; - } - } - } - group = fio_buf2u64_le(imap + pos); - group &= UINT64_C(0x7F7F7F7F7F7F7F7F); - group += UINT64_C(0x0101010101010101); - group &= UINT64_C(0x8080808080808080); - while (group) { - uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); - group ^= (uint64_t)1ULL << offset; - offset >>= 3; - offset += pos; - if (imap[offset] == 255U) { - r.alt = offset; - break; - } - } - group = ~fio_buf2u64_le(imap + pos); - group &= UINT64_C(0x7F7F7F7F7F7F7F7F); - group += UINT64_C(0x0101010101010101); - group &= UINT64_C(0x8080808080808080); - while (group) { - uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); - group ^= (uint64_t)1ULL << offset; - offset >>= 3; - offset += pos; - if (!imap[offset]) { - r.alt = offset; - return r; - } - } - pos += (attempt + 2) << 3; - pos &= mask; - } - for (; attempt < attempt_limit; ++attempt) { - uint64_t group = fio_buf2u64_le(imap + pos); - group ^= mbyte64; - group &= UINT64_C(0x7F7F7F7F7F7F7F7F); - group += UINT64_C(0x0101010101010101); - group &= UINT64_C(0x8080808080808080); - while (group) { - uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); - group ^= (uint64_t)1ULL << offset; - offset >>= 3; - offset += pos; - if (imap[offset] == r.bhash && - FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + offset, node->hash)) { - if (FIO_MAP_KEY_CMP(o->map[offset].key, node->key)) { - r.act = offset; - return r; - } - if (!--guard) { - r.act = offset; - goto possible_attack; - } - } - } - group = ~fio_buf2u64_le(imap + pos); - group &= UINT64_C(0x7F7F7F7F7F7F7F7F); - group += UINT64_C(0x0101010101010101); - group &= UINT64_C(0x8080808080808080); - while (group) { - uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); - group ^= (uint64_t)1ULL << offset; - offset >>= 3; - offset += pos; - if (!imap[offset]) { - return r; - } - } - pos += (attempt + 2) << 3; - pos &= mask; - } - if (r.alt == (uint32_t)-1) - r.home = r.alt; - return r; -possible_attack: - if (!guard_print) { - guard_print = 1; - FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( - FIO_NAME(FIO_MAP_NAME, s)) " under attack? (full collision guard)"); - } - return r; +/** + * Returns 1 if the array is embedded, 0 if it has memory allocated and -1 on an + * error. + */ +FIO_IFUNC int FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, -1); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + return FIO_ARRAY_IS_EMBEDDED(ary); + (void)ary; /* if unused (never embedded) */ } -FIO_IFUNC fio___map_node_info_s -FIO_NAME(FIO_MAP_NAME, __node_info)(FIO_NAME(FIO_MAP_NAME, s) * o, - FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { -#if defined(FIO_MAP_HASH_FN) - if (!node->hash) - node->hash = FIO_MAP_HASH_FN(node->key); -#endif - node->hash += !node->hash; - if (o->bits < 4) - return FIO_NAME(FIO_MAP_NAME, __node_info_mini)(o, node); - else if (o->bits < 9) - return FIO_NAME(FIO_MAP_NAME, __node_info_med)(o, node); - else - return FIO_NAME(FIO_MAP_NAME, __node_info_full)(o, node); +/** + * Returns the value located at `index` (no copying is performed). + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + */ +FIO_IFUNC FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, get)(FIO_ARRAY_PTR ary_, + int64_t index) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, FIO_ARRAY_TYPE_INVALID); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + FIO_ARRAY_TYPE *a; + size_t count; + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + a = ary->ary + ary->start; + count = ary->end - ary->start; + break; + case 1: + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + count = ary->start; + break; + default: return FIO_ARRAY_TYPE_INVALID; + } + + if (index < 0) { + index += count; + if (index < 0) + return FIO_ARRAY_TYPE_INVALID; + } + if ((uint32_t)index >= count) + return FIO_ARRAY_TYPE_INVALID; + return a[index]; } -#if FIO_MAP_ORDERED -FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, - __update_order)(FIO_NAME(FIO_MAP_NAME, s) * o, - uint32_t i) { - if (o->count == 1) { - o->head = i; - o->map[i].node.next = o->map[i].node.prev = i; +/* Returns a pointer to the (next) object in the array. */ +FIO_IFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, + each_next)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE **first, + FIO_ARRAY_TYPE *pos) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, NULL); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + int64_t count; + FIO_ARRAY_TYPE *a; + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + count = ary->end - ary->start; + a = ary->ary + ary->start; + break; + case 1: + count = ary->start; + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + break; + default: return NULL; + } + intptr_t i; + if (!count || !first) + return NULL; + if (!pos || !(*first) || (*first) > pos) { + i = -1; } else { - FIO_INDEXED_LIST_PUSH(o->map, node, o->head, i); + i = (intptr_t)(pos - (*first)); } + *first = a; + ++i; + if (i >= count) + return NULL; + return i + a; } -#define FIO___MAP_UPDATE_ORDER(map, at) \ - FIO_NAME(FIO_MAP_NAME, __update_order)(map, at) + +/** Used internally for the EACH macro */ +typedef FIO_ARRAY_TYPE FIO_NAME(FIO_ARRAY_NAME, ____type_t); + +/* ***************************************************************************** +Exported functions +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/* ***************************************************************************** +Helper macros +***************************************************************************** */ +#if FIO_ARRAY_EXPONENTIAL +#define FIO_ARRAY_ADD2CAPA(capa) (((capa) << 1) + FIO_ARRAY_PADDING) #else -#define FIO___MAP_UPDATE_ORDER(map, at) +#define FIO_ARRAY_ADD2CAPA(capa) ((capa) + FIO_ARRAY_PADDING) #endif -static int FIO_NAME(FIO_MAP_NAME, - __move2map_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * - e) { - FIO_NAME(FIO_MAP_NAME, s) *dest = (FIO_NAME(FIO_MAP_NAME, s) *)e->udata; - FIO_NAME(FIO_MAP_NAME, __o_node_s) - n = { - .key = FIO_MAP_KEY_FROM_INTERNAL(e->node->key), -#if !FIO_MAP_RECALC_HASH - .hash = e->node->hash, -#endif - }; - fio___map_node_info_s i = FIO_NAME(FIO_MAP_NAME, __node_info)(dest, &n); - if (i.home == (uint32_t)-1) { - FIO_LOG_ERROR("move2map FAILED (%zu/%zu)!", - e->node - e->map->map, - FIO_MAP_CAPA(dest->bits)); - return -1; +/* ***************************************************************************** +Dynamic Arrays - internal helpers +***************************************************************************** */ + +#define FIO_ARRAY_POS2ABS(ary, pos) \ + (pos >= 0 ? (ary->start + pos) : (ary->end - pos)) + +#define FIO_ARRAY_AB_CT(cond, a, b) ((b) ^ ((0 - ((cond)&1)) & ((a) ^ (b)))) + +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_ARRAY_NAME, s)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_ARRAY_NAME, destroy)) +/* ***************************************************************************** +Dynamic Arrays - implementation +***************************************************************************** */ + +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/* Allocates a new array object on the heap and initializes it's memory. */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void) { + FIO_NAME(FIO_ARRAY_NAME, s) *a = + (FIO_NAME(FIO_ARRAY_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*a), 0); + if (!FIO_MEM_REALLOC_IS_SAFE_ && a) { + *a = (FIO_NAME(FIO_ARRAY_NAME, s))FIO_ARRAY_INIT; } - uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(dest); - ++dest->count; - /* insert at best position */ - imap[i.alt] = i.bhash; - dest->map[i.alt] = e->node[0]; - FIO___MAP_UPDATE_ORDER(dest, i.alt); - return 0; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, s)); + return (FIO_ARRAY_PTR)FIO_PTR_TAG(a); } -FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, - __move2map)(FIO_NAME(FIO_MAP_NAME, s) * dest, - FIO_NAME(FIO_MAP_NAME, s) * src) { - return FIO_NAME( - FIO_MAP_NAME, - __each_node)(src, FIO_NAME(FIO_MAP_NAME, __move2map_task), dest); +/* Frees an array's internal data AND it's container! */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + FIO_NAME(FIO_ARRAY_NAME, destroy)(ary_); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, s)); + FIO_MEM_FREE_(ary, sizeof(*ary)); } +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ -/* Inserts a node to the map. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, - __node_insert)(FIO_NAME(FIO_MAP_NAME, s) * o, -#ifndef FIO_MAP_HASH_FN - uint64_t hash, -#endif - FIO_MAP_KEY key, -#ifdef FIO_MAP_VALUE - FIO_MAP_VALUE value, -#endif -#ifdef FIO_MAP_VALUE - FIO_MAP_VALUE_INTERNAL *old, -#else - FIO_MAP_KEY_INTERNAL *old, -#endif - _Bool overwrite) { - static FIO_NAME(FIO_MAP_NAME, s) *last_collision = NULL; - uint32_t r = -1; - FIO_NAME(FIO_MAP_NAME, s) tmp; - FIO_NAME(FIO_MAP_NAME, __o_node_s) - node = { - .key = key, -#ifdef FIO_MAP_VALUE - .value = value, +/* Destroys any objects stored in the array and frees the internal state. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, destroy)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + union { + FIO_NAME(FIO_ARRAY_NAME, s) a; + FIO_NAME(FIO_ARRAY_NAME, ___embedded_s) e; + } tmp = {.a = *ary}; + *ary = (FIO_NAME(FIO_ARRAY_NAME, s))FIO_ARRAY_INIT; + + switch ( + FIO_NAME_BL(FIO_ARRAY_NAME, embedded)((FIO_ARRAY_PTR)FIO_PTR_TAG(&tmp))) { + case 0: +#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE + for (size_t i = tmp.a.start; i < tmp.a.end; ++i) { + FIO_ARRAY_TYPE_DESTROY(tmp.a.ary[i]); + } #endif -#ifndef FIO_MAP_HASH_FN - .hash = hash, + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, destroy)); + FIO_MEM_FREE_(tmp.a.ary, tmp.a.capa * sizeof(*tmp.a.ary)); + return; + case 1: +#if !FIO_ARRAY_TYPE_DESTROY_SIMPLE + while (tmp.e.start--) { + FIO_ARRAY_TYPE_DESTROY((tmp.e.embedded[tmp.e.start])); + } #endif - }; - fio___map_node_info_s info; - info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); - if (info.act != r) - goto perform_overwrite; - if (info.home == r) - goto reallocate_map; - -insert: - ++o->count; - r = info.alt; - FIO_NAME(FIO_MAP_NAME, __imap)(o)[r] = info.bhash; -#if !FIO_MAP_RECALC_HASH - o->map[r].hash = node.hash, -#endif - FIO_MAP_KEY_COPY(o->map[r].key, node.key); - FIO_MAP_VALUE_COPY(o->map[r].value, node.value); - FIO___MAP_UPDATE_ORDER(o, r); - return r; - -perform_overwrite: - r = info.act; - FIO_MAP_KEY_DISCARD(node.key); - if (!overwrite) { - FIO_MAP_VALUE_DISCARD(value); - return r; - } -#ifdef FIO_MAP_VALUE - if (old) - *old = o->map[r].value; - else { - FIO_MAP_VALUE_DESTROY(o->map[r].value); + return; } - FIO_MAP_VALUE_COPY(o->map[r].value, node.value); -#else - (void)old; -#endif -#if FIO_MAP_ORDERED - if (o->head == r) - o->head = o->map[o->head].node.prev; - FIO_INDEXED_LIST_REMOVE(o->map, node, r); - FIO___MAP_UPDATE_ORDER(o, r); -#endif - return r; + return; +} -reallocate_map: - /* reallocate map */ - if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&tmp, o->bits + 1)) - goto no_memory; - if (FIO_NAME(FIO_MAP_NAME, __move2map)(&tmp, o)) { - FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); - goto security_partial; - } - info = FIO_NAME(FIO_MAP_NAME, __node_info)(&tmp, &node); - if (info.home != r) { - FIO_NAME(FIO_MAP_NAME, __free_map)(o, 0); - *o = tmp; - goto insert; +/** Reserves a minimal capacity for the array. */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, reserve)(FIO_ARRAY_PTR ary_, + int64_t capa_) { + FIO_PTR_TAG_VALID_OR_RETURN(ary_, 0); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + if (capa_ > UINT32_MAX || capa_ < ((int64_t)0LL - UINT32_MAX)) + return ary->capa; + uint32_t abs_capa = ((capa_ >= 0) ? (uint32_t)capa_ : (uint32_t)(0 - capa_)); + uint32_t capa; + FIO_ARRAY_TYPE *tmp; + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + abs_capa += ary->end - ary->start; + capa = FIO_ARRAY_SIZE2WORDS((abs_capa)); + if (abs_capa <= ary->capa) + return (uint32_t)ary->capa; + /* objects don't move, use only realloc */ + if ((capa_ >= 0) || (capa_ < 0 && ary->start > 0)) { + tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(ary->ary, + 0, + sizeof(*tmp) * capa, + sizeof(*tmp) * ary->end); + if (!tmp) + return (uint32_t)ary->capa; + if (!ary->ary) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); + ary->capa = capa; + ary->ary = tmp; + return capa; + } else { /* moving objects, starting with a fresh piece of memory */ + tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*tmp) * capa, 0); + const uint32_t count = ary->end - ary->start; + if (!tmp) + return (uint32_t)ary->capa; + if (!ary->ary) + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); + if (capa_ >= 0) { /* copy items at beginning of memory stack */ + if (count) { + FIO_MEMCPY(tmp, ary->ary + ary->start, count * sizeof(*tmp)); + } + FIO_MEM_FREE_(ary->ary, sizeof(*ary->ary) * ary->capa); + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = 0, + .end = count, + .capa = capa, + .ary = tmp, + }; + return capa; + } else { /* copy items at ending of memory stack */ + if (count) { + FIO_MEMCPY(tmp + (capa - count), + ary->ary + ary->start, + count * sizeof(*tmp)); + } + FIO_MEM_FREE_(ary->ary, sizeof(*ary->ary) * ary->capa); + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = (capa - count), + .end = capa, + .capa = capa, + .ary = tmp, + }; + } + } + return capa; + case 1: + abs_capa += ary->start; + capa = FIO_ARRAY_SIZE2WORDS((abs_capa)); + if (abs_capa <= FIO_ARRAY_EMBEDDED_CAPA) + return FIO_ARRAY_EMBEDDED_CAPA; + tmp = (FIO_ARRAY_TYPE *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*tmp) * capa, 0); + if (!tmp) + return FIO_ARRAY_EMBEDDED_CAPA; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_ARRAY_NAME, destroy)); + if (capa_ >= 0) { + /* copy items at beginning of memory stack */ + if (ary->start) { + FIO_MEMCPY(tmp, + FIO_ARRAY2EMBEDDED(ary)->embedded, + ary->start * sizeof(*tmp)); + } + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = 0, + .end = ary->start, + .capa = capa, + .ary = tmp, + }; + return capa; + } + /* copy items at ending of memory stack */ + if (ary->start) { + FIO_MEMCPY(tmp + (capa - ary->start), + FIO_ARRAY2EMBEDDED(ary)->embedded, + ary->start * sizeof(*tmp)); + } + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = (capa - ary->start), + .end = capa, + .capa = capa, + .ary = tmp, + }; + return capa; + default: return 0; } - FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); - goto security_partial; +} -no_memory: - FIO_MAP_KEY_DISCARD(key); - FIO_MAP_VALUE_DISCARD(value); - FIO_LOG_ERROR( - "unknown error occurred trying to add an entry to the map (capa: %zu)", - (size_t)FIO_MAP_CAPA(o->bits)); - FIO_ASSERT_DEBUG(0, "these errors shouldn't happen - no memory?"); - return r; +/** + * Adds all the items in the `src` Array to the end of the `dest` Array. + * + * The `src` Array remain untouched. + * + * Returns `dest` on success or NULL on error (i.e., no memory). + */ +SFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, concat)(FIO_ARRAY_PTR dest_, + FIO_ARRAY_PTR src_) { + FIO_PTR_TAG_VALID_OR_RETURN(dest_, (FIO_ARRAY_PTR)NULL); + FIO_PTR_TAG_VALID_OR_RETURN(src_, (FIO_ARRAY_PTR)NULL); + FIO_NAME(FIO_ARRAY_NAME, s) *dest = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), dest_); + FIO_NAME(FIO_ARRAY_NAME, s) *src = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), src_); + if (!dest || !src) + return dest_; + const uint32_t offset = FIO_NAME(FIO_ARRAY_NAME, count)(dest_); + const uint32_t added = FIO_NAME(FIO_ARRAY_NAME, count)(src_); + const uint32_t total = offset + added; + if (!added) + return dest_; -security_partial: - if (last_collision != o) { - FIO_LOG_SECURITY( - "hash map " FIO_MACRO2STR(FIO_NAME( - FIO_MAP_NAME, - s)) " under attack? (partial/full collision guard) - capa: %zu.", - (size_t)FIO_MAP_CAPA(o->bits)); - last_collision = o; - } - return r; -} + if (total < offset || total + offset < total) + return NULL; /* item count overflow */ -/* Inserts a node to the map. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, - __node_find)(FIO_NAME(FIO_MAP_NAME, s) * o, -#ifndef FIO_MAP_HASH_FN - uint64_t hash, -#endif - FIO_MAP_KEY key) { - uint32_t r = -1; - fio___map_node_info_s info; - FIO_NAME(FIO_MAP_NAME, __o_node_s) - node = { - .key = key, -#ifndef FIO_MAP_HASH_FN - .hash = hash, -#endif - }; - if (!o->map) - return r; - info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); - return (r = info.act); -} + const uint32_t capa = FIO_NAME(FIO_ARRAY_NAME, reserve)(dest_, added); -/* Deletes a known node from the map. */ -FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, - __node_delete_at)(FIO_NAME(FIO_MAP_NAME, s) * o, - uint32_t at, -#ifdef FIO_MAP_VALUE - FIO_MAP_VALUE_INTERNAL *old -#else - FIO_MAP_KEY_INTERNAL *old -#endif -) { - --o->count; - uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); - imap[at] = 255U; /* mark as deleted */ - if (old) { -#ifdef FIO_MAP_VALUE - FIO_MAP_KEY_DESTROY(o->map[at].key); - *old = o->map[at].value; -#else - *old = o->map[at].key; -#endif - } else { - FIO_MAP_KEY_DESTROY(o->map[at].key); - FIO_MAP_VALUE_DESTROY(o->map[at].value); + if (!FIO_ARRAY_IS_EMBEDDED(dest) && dest->start + total > capa) { + /* we need to move the existing items due to the offset */ + FIO_MEMMOVE(dest->ary, + dest->ary + dest->start, + (dest->end - dest->start) * sizeof(*dest->ary)); + dest->start = 0; + dest->end = offset; } - -#if FIO_MAP_ORDERED - if (o->head == at) { - o->head = (o->count ? o->map[o->head].node.next : 0); +#if FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE + /* copy data */ + FIO_MEMCPY(FIO_NAME2(FIO_ARRAY_NAME, ptr)(dest_) + offset, + FIO_NAME2(FIO_ARRAY_NAME, ptr)(src_), + added); +#else + { + FIO_ARRAY_TYPE *const a1 = FIO_NAME2(FIO_ARRAY_NAME, ptr)(dest_); + FIO_ARRAY_TYPE *const a2 = FIO_NAME2(FIO_ARRAY_NAME, ptr)(src_); + for (uint32_t i = 0; i < added; ++i) { + FIO_ARRAY_TYPE_CONCAT_COPY(a1[i + offset], a2[i]); + } } - FIO_INDEXED_LIST_REMOVE(o->map, node, at); -#endif +#endif /* FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE */ + /* update dest */ + if (!FIO_ARRAY_IS_EMBEDDED(dest)) { + dest->end += added; + return dest_; + } else + dest->start = total; + return dest_; } -/* Deletes a node from the map. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, - __node_delete)(FIO_NAME(FIO_MAP_NAME, s) * o, - FIO_MAP_KEY key, -#ifndef FIO_MAP_HASH_FN - uint64_t hash, -#endif -#ifdef FIO_MAP_VALUE - FIO_MAP_VALUE_INTERNAL *old -#else - FIO_MAP_KEY_INTERNAL *old -#endif -) { - uint32_t r = (uint32_t)-1; - if (!o->map) - return r; - fio___map_node_info_s info; - FIO_NAME(FIO_MAP_NAME, __o_node_s) - node = { - .key = key, -#ifndef FIO_MAP_HASH_FN - .hash = hash, -#endif - }; - info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); - if (info.act == r) - return r; - r = 0; - FIO_NAME(FIO_MAP_NAME, __node_delete_at)(o, info.act, old); - return r; -} +/** + * Sets `index` to the value in `data`. + * + * If `index` is negative, it will be counted from the end of the Array (-1 == + * last element). + * + * If `old` isn't NULL, the existing data will be copied to the location pointed + * to by `old` before the copy in the Array is destroyed. + * + * Returns a pointer to the new object, or NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, set)(FIO_ARRAY_PTR ary_, + int64_t index, + FIO_ARRAY_TYPE data, + FIO_ARRAY_TYPE *old) { + FIO_ARRAY_TYPE *a = NULL; + FIO_NAME(FIO_ARRAY_NAME, s) * ary; + uint32_t count; + uint8_t pre_existing = 1; -/* ***************************************************************************** + FIO_PTR_TAG_VALID_OR_GOTO(ary_, invalid); + + ary = FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + if (index < 0) { + index += count; + if (index < 0) + goto negative_expansion; + } + if ((size_t)index > 0xFFFFFFFFULL) + goto invalid; + if ((uint32_t)index >= count) { + if ((uint32_t)index == count) + FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, FIO_ARRAY_ADD2CAPA(index)); + else + FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, (uint32_t)index + 1); + if (FIO_ARRAY_IS_EMBEDDED(ary)) + goto expand_embedded; + goto expansion; + } + a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); +done: -Map API + /* copy / clear object */ + if (pre_existing) { + if (old) { + FIO_ARRAY_TYPE_COPY(old[0], a[index]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(a[index]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(a[index]); + } + } else if (old) { + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + } + FIO_ARRAY_TYPE_COPY(a[index], FIO_ARRAY_TYPE_INVALID); + FIO_ARRAY_TYPE_COPY(a[index], data); + return a + index; +expansion: + pre_existing = 0; + a = ary->ary; + { + uint8_t was_moved = 0; + /* test if we need to move objects to make room at the end */ + if (ary->start + (uint32_t)index >= ary->capa) { + FIO_MEMMOVE(ary->ary, ary->ary + ary->start, (count) * sizeof(*ary->ary)); + ary->start = 0; + ary->end = (uint32_t)index + 1; + was_moved = 1; + } + /* initialize memory in between objects */ + if (was_moved || !FIO_MEM_REALLOC_IS_SAFE_ || + !FIO_ARRAY_TYPE_INVALID_SIMPLE) { +#if FIO_ARRAY_TYPE_INVALID_SIMPLE + FIO_MEMSET(a + count, 0, ((uint32_t)index - count) * sizeof(*ary->ary)); +#else + for (size_t i = count; i <= (size_t)index; ++i) { + FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); + } +#endif + } + ary->end = (uint32_t)index + 1; + } + goto done; +expand_embedded: + pre_existing = 0; + ary->start = (uint32_t)index + 1; + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + goto done; +negative_expansion: + pre_existing = 0; + FIO_NAME(FIO_ARRAY_NAME, reserve)(ary_, (index - count)); + index = 0 - index; + if (index > ary->capa) + goto invalid; + if ((FIO_ARRAY_IS_EMBEDDED(ary))) + goto negative_expansion_embedded; + a = ary->ary; + if (index > (int32_t)ary->start) { + FIO_MEMMOVE(a + index, a + ary->start, count * sizeof(*a)); + ary->end = (uint32_t)index + count; + ary->start = (uint32_t)index; + } + index = ary->start - (uint32_t)index; + if ((uint32_t)(index + 1) < ary->start) { +#if FIO_ARRAY_TYPE_INVALID_SIMPLE + FIO_MEMSET(a + index, 0, (ary->start - index) * (sizeof(*a))); +#else + for (size_t i = index; i < (size_t)ary->start; ++i) { + FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); + } +#endif + } + ary->start = (uint32_t)index; + goto done; +negative_expansion_embedded: + a = FIO_ARRAY2EMBEDDED(ary)->embedded; + FIO_MEMMOVE(a + index, a, count * count * sizeof(*a)); +#if FIO_ARRAY_TYPE_INVALID_SIMPLE + FIO_MEMSET(a, 0, index * (sizeof(a))); +#else + for (size_t i = 0; i < (size_t)index; ++i) { + FIO_ARRAY_TYPE_COPY(a[i], FIO_ARRAY_TYPE_INVALID); + } +#endif + index = 0; + goto done; -***************************************************************************** */ +invalid: + FIO_ARRAY_TYPE_DESTROY(data); + if (old) { + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + } -/** Destroys the object, re-initializing its container. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, destroy)(FIO_MAP_PTR map) { - // FIO_PTR_TAG_VALID_OR_RETURN(map, 0); - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - FIO_NAME(FIO_MAP_NAME, __free_map)(m, 1); - *m = (FIO_NAME(FIO_MAP_NAME, s)){0}; + return a; } -/** Reserves at minimum the capacity requested. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, reserve)(FIO_MAP_PTR map, size_t capa) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (capa <= FIO_MAP_CAPA(m->bits)) - return; - uint32_t bits = m->bits; - for (; capa > FIO_MAP_CAPA(bits); ++bits) - ; - FIO_NAME(FIO_MAP_NAME, s) tmp; - if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&tmp, bits)) - goto no_memory; - if (m->count && FIO_NAME(FIO_MAP_NAME, __move2map)(&tmp, m)) { - FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); - goto no_memory; +/** + * Returns the index of the object or (uint32_t)-1 if the object wasn't found. + * + * If `start_at` is negative (i.e., -1), than seeking will be performed in + * reverse, where -1 == last index (-2 == second to last, etc'). + */ +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, find)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data, + int64_t start_at) { + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + if (!a) + return -1; + size_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + if (start_at >= 0) { + /* seek forwards */ + if ((uint32_t)start_at >= count) + start_at = (int32_t)count; + while ((uint32_t)start_at < count) { + if (FIO_ARRAY_TYPE_CMP(a[start_at], data)) + return (uint32_t)start_at; + ++start_at; + } + } else { + /* seek backwards */ + if (start_at + (int32_t)count < 0) + return -1; + count += start_at; + count += 1; + while (count--) { + if (FIO_ARRAY_TYPE_CMP(a[count], data)) + return (uint32_t)count; + } } - FIO_NAME(FIO_MAP_NAME, __free_map)(m, 0); - *m = tmp; - return; -no_memory: - FIO_LOG_ERROR("unknown error occurred trying to rehash the map"); - FIO_ASSERT_DEBUG(0, "these errors shouldn't happen - no memory?"); - return; + return -1; } -/** Removes an object in the map, returning a pointer to the map data. */ -SFUNC int FIO_NAME(FIO_MAP_NAME, remove)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif - FIO_MAP_KEY key, -#if defined(FIO_MAP_VALUE) - FIO_MAP_VALUE_INTERNAL *old -#else - FIO_MAP_KEY_INTERNAL *old -#endif -) { - FIO_PTR_TAG_VALID_OR_RETURN(map, -1); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (!m->count) - return -1; - return FIO_NAME(FIO_MAP_NAME, __node_delete)(m, - key, -#if !defined(FIO_MAP_HASH_FN) - hash, -#endif - old); -} +/** + * Removes an object from the array, MOVING all the other objects to prevent + * "holes" in the data. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns 0 on success and -1 on error. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, remove)(FIO_ARRAY_PTR ary_, + int64_t index, + FIO_ARRAY_TYPE *old) { + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + size_t count; + if (!a) + goto invalid; + count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); -FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, - __evict_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * - e) { - size_t *counter = (size_t *)e->udata; - FIO_NAME(FIO_MAP_NAME, __node_delete_at) - (e->map, (uint32_t)(e->node - e->map->map), NULL); - if ((counter[0] -= 1)) + if (index < 0) { + index += count; + if (index < 0) { + FIO_LOG_WARNING( + FIO_MACRO2STR(FIO_NAME(FIO_ARRAY_NAME, + remove)) " called with a negative index lower " + "than the element count."); + goto invalid; + } + } + if ((uint32_t)index >= count) + goto invalid; + if (!index) { + FIO_NAME(FIO_ARRAY_NAME, shift)(ary_, old); return 0; - return -1; -} -/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, evict)(FIO_MAP_PTR map, - size_t number_of_elements) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - if (!number_of_elements) - return; - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (m->count <= number_of_elements) { - FIO_NAME(FIO_MAP_NAME, __destroy_map)(m, 1); - return; } - FIO_NAME(FIO_MAP_NAME, __each_node) - (m, FIO_NAME(FIO_MAP_NAME, __evict_task), &number_of_elements); + if ((uint32_t)index + 1 == count) { + FIO_NAME(FIO_ARRAY_NAME, pop)(ary_, old); + return 0; + } + + if (old) { + FIO_ARRAY_TYPE_COPY(*old, a[index]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(a[index]); +#endif + } else { + FIO_ARRAY_TYPE_DESTROY(a[index]); + } + + if ((uint32_t)(index + 1) < count) { + FIO_MEMMOVE(a + index, a + index + 1, (count - (index + 1)) * sizeof(*a)); + } + FIO_ARRAY_TYPE_COPY((a + (count - 1))[0], FIO_ARRAY_TYPE_INVALID); + + if (FIO_ARRAY_IS_EMBEDDED(ary)) + goto embedded; + --ary->end; + return 0; + +embedded: + --ary->start; + return 0; + +invalid: + if (old) { + FIO_ARRAY_TYPE_COPY(*old, FIO_ARRAY_TYPE_INVALID); + } + return -1; } -/** Removes all objects from the map, without releasing the map's resources. +/** + * Removes all occurrences of an object from the array (if any), MOVING all the + * existing objects to prevent "holes" in the data. + * + * Returns the number of items removed. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, clear)(FIO_MAP_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (m->map) - FIO_NAME(FIO_MAP_NAME, __destroy_map)(m, 1); +SFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, remove2)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data) { + size_t c = 0; + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + size_t count; + if (!a) + return (uint32_t)c; + count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + + size_t i = 0; + while ((i + c) < count) { + if (!(FIO_ARRAY_TYPE_CMP(a[i + c], data))) { + a[i] = a[i + c]; + ++i; + continue; + } + FIO_ARRAY_TYPE_DESTROY(a[i + c]); + ++c; + } + if (c && FIO_MEM_REALLOC_IS_SAFE_) { + /* keep memory zeroed out */ + FIO_MEMSET(a + i, 0, sizeof(*a) * c); + } + if (!FIO_ARRAY_IS_EMBEDDED_PTR(ary, a)) { + ary->end = (uint32_t)(ary->start + i); + return (uint32_t)c; + } + ary->start = (uint32_t)i; + return (uint32_t)c; } -/** Attempts to minimize memory use. */ -SFUNC void FIO_NAME(FIO_MAP_NAME, compact)(FIO_MAP_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (!o->map || !o->count) - return; - FIO_NAME(FIO_MAP_NAME, s) cpy = {0}; - uint32_t bits = o->bits; - while (FIO_MAP_CAPA(bits >> 1) > o->count) - bits >>= 1; - ++bits; - if (bits >= o->bits) +/** Attempts to lower the array's memory consumption. */ +SFUNC void FIO_NAME(FIO_ARRAY_NAME, compact)(FIO_ARRAY_PTR ary_) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(ary_); + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + size_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + FIO_ARRAY_TYPE *tmp = NULL; + + if (count <= FIO_ARRAY_EMBEDDED_CAPA) + goto re_embed; + + tmp = (FIO_ARRAY_TYPE *) + FIO_MEM_REALLOC_(NULL, 0, (ary->end - ary->start) * sizeof(*tmp), 0); + if (!tmp) return; - for (size_t i = 0; i < 2; ++i) { - if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&cpy, bits)) - return; - if (!FIO_NAME(FIO_MAP_NAME, __move2map)(&cpy, o)) - goto finish; - FIO_NAME(FIO_MAP_NAME, __free_map)(&cpy, 0); - ++bits; - } + FIO_MEMCPY(tmp, ary->ary + ary->start, count * sizeof(*ary->ary)); + FIO_MEM_FREE_(ary->ary, ary->capa * sizeof(*ary->ary)); + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = 0, + .end = (ary->end - ary->start), + .capa = (ary->end - ary->start), + .ary = tmp, + }; return; -finish: - FIO_NAME(FIO_MAP_NAME, __free_map)(o, 0); - o[0] = cpy; +re_embed: + if (!FIO_ARRAY_IS_EMBEDDED(ary)) { + tmp = ary->ary; + uint32_t offset = ary->start; + size_t old_capa = ary->capa; + *ary = (FIO_NAME(FIO_ARRAY_NAME, s)){ + .start = (uint32_t)count, + }; + if (count) { + FIO_MEMCPY(FIO_ARRAY2EMBEDDED(ary)->embedded, + tmp + offset, + count * sizeof(*tmp)); + } + if (tmp) { + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_ARRAY_NAME, destroy)); + FIO_MEM_FREE_(tmp, sizeof(*tmp) * old_capa); + (void)old_capa; /* if unused */ + } + } return; } +/** + * Pushes an object to the end of the Array. Returns NULL on error. + */ +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, push)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data) { + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (ary->end == ary->capa) { + if (!ary->start) { + if (FIO_NAME(FIO_ARRAY_NAME, + reserve)(ary_, (uint32_t)FIO_ARRAY_ADD2CAPA(ary->capa)) == + ary->end) + goto invalid; + } else { + const uint32_t new_start = (ary->start >> 2); + const uint32_t count = ary->end - ary->start; + if (count) + FIO_MEMMOVE(ary->ary + new_start, + ary->ary + ary->start, + count * sizeof(*ary->ary)); + ary->end = count + new_start; + ary->start = new_start; + } + } + FIO_ARRAY_TYPE_COPY(ary->ary[ary->end], data); + return ary->ary + (ary->end++); -SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * - FIO_NAME(FIO_MAP_NAME, set_ptr)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, -#endif -#ifdef FIO_MAP_VALUE - FIO_MAP_KEY key, - FIO_MAP_VALUE val, - FIO_MAP_VALUE_INTERNAL *old, - int overwrite -#else - FIO_MAP_KEY key -#endif - ) { - FIO_PTR_TAG_VALID_OR_RETURN(map, NULL); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - uint32_t i = FIO_NAME(FIO_MAP_NAME, __node_insert)(m, -#ifndef FIO_MAP_HASH_FN - hash, + case 1: + if (ary->start == FIO_ARRAY_EMBEDDED_CAPA) + goto needs_memory_embedded; + FIO_ARRAY_TYPE_COPY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start], data); + return FIO_ARRAY2EMBEDDED(ary)->embedded + (ary->start++); + } +invalid: + FIO_ARRAY_TYPE_DESTROY(data); + return NULL; + +needs_memory_embedded: + if (FIO_NAME(FIO_ARRAY_NAME, + reserve)(ary_, FIO_ARRAY_ADD2CAPA(FIO_ARRAY_EMBEDDED_CAPA)) == + FIO_ARRAY_EMBEDDED_CAPA) + goto invalid; + FIO_ARRAY_TYPE_COPY(ary->ary[ary->end], data); + return ary->ary + (ary->end++); +} + +/** + * Removes an object from the end of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, pop)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE *old) { + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (ary->end == ary->start) + return -1; + --ary->end; + if (old) { + FIO_ARRAY_TYPE_COPY(*old, ary->ary[ary->end]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->end]); #endif - key, -#ifdef FIO_MAP_VALUE - val, - old, - overwrite -#else - NULL, - 1 + } else { + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->end]); + } + return 0; + case 1: + if (!ary->start) + return -1; + --ary->start; + if (old) { + FIO_ARRAY_TYPE_COPY(*old, FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); #endif - ); - if (i == (uint32_t)-1) - return NULL; - return m->map + i; + } else { + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[ary->start]); + } + FIO_MEMSET(FIO_ARRAY2EMBEDDED(ary)->embedded + ary->start, + 0, + sizeof(*ary->ary)); + return 0; + } + if (old) + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + return -1; } /** - * The core get function. This function returns NULL if item is missing. + * Unshifts an object to the beginning of the Array. Returns -1 on error. * - * NOTE: the function returns a pointer to the map's internal storage. + * This could be expensive, causing `memmove`. */ -SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * - FIO_NAME(FIO_MAP_NAME, get_ptr)(FIO_MAP_PTR map, -#if !defined(FIO_MAP_HASH_FN) - uint64_t hash, +SFUNC FIO_ARRAY_TYPE *FIO_NAME(FIO_ARRAY_NAME, unshift)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE data) { + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (!ary->start) { + if (ary->end == ary->capa) { + FIO_NAME(FIO_ARRAY_NAME, reserve) + (ary_, (-1 - (int32_t)FIO_ARRAY_ADD2CAPA(ary->capa))); + if (!ary->start) + goto invalid; + } else { + const uint32_t new_end = + (uint32_t)(ary->capa - ((ary->capa - ary->end) >> 2)); + const uint32_t count = (uint32_t)(ary->end - ary->start); + const uint32_t new_start = (uint32_t)(new_end - count); + if (count) + FIO_MEMMOVE(ary->ary + new_start, + ary->ary + ary->start, + count * sizeof(*ary->ary)); + ary->end = new_end; + ary->start = new_start; + } + } + FIO_ARRAY_TYPE_COPY(ary->ary[--ary->start], data); + return ary->ary + ary->start; + + case 1: + if (ary->start == FIO_ARRAY_EMBEDDED_CAPA) + goto needs_memory_embed; + if (ary->start) + FIO_MEMMOVE(FIO_ARRAY2EMBEDDED(ary)->embedded + 1, + FIO_ARRAY2EMBEDDED(ary)->embedded, + sizeof(*ary->ary) * ary->start); + ++ary->start; + FIO_ARRAY_TYPE_COPY(FIO_ARRAY2EMBEDDED(ary)->embedded[0], data); + return FIO_ARRAY2EMBEDDED(ary)->embedded; + } +invalid: + FIO_ARRAY_TYPE_DESTROY(data); + return NULL; + +needs_memory_embed: + if (FIO_NAME(FIO_ARRAY_NAME, reserve)( + ary_, + (-1 - (int32_t)FIO_ARRAY_ADD2CAPA(FIO_ARRAY_EMBEDDED_CAPA))) == + FIO_ARRAY_EMBEDDED_CAPA) + goto invalid; + FIO_ARRAY_TYPE_COPY(ary->ary[--ary->start], data); + return ary->ary + ary->start; +} + +/** + * Removes an object from the beginning of the Array. + * + * If `old` is set, the data is copied to the location pointed to by `old` + * before the data in the array is destroyed. + * + * Returns -1 on error (Array is empty) and 0 on success. + */ +SFUNC int FIO_NAME(FIO_ARRAY_NAME, shift)(FIO_ARRAY_PTR ary_, + FIO_ARRAY_TYPE *old) { + + FIO_NAME(FIO_ARRAY_NAME, s) *ary = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_ARRAY_NAME, s), ary_); + + switch (FIO_NAME_BL(FIO_ARRAY_NAME, embedded)(ary_)) { + case 0: + if (ary->end == ary->start) + return -1; + if (old) { + FIO_ARRAY_TYPE_COPY(*old, ary->ary[ary->start]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->start]); #endif - FIO_MAP_KEY key) { - FIO_PTR_TAG_VALID_OR_RETURN(map, NULL); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - uint32_t i = FIO_NAME(FIO_MAP_NAME, __node_find)(m, -#ifndef FIO_MAP_HASH_FN - hash, + } else { + FIO_ARRAY_TYPE_DESTROY(ary->ary[ary->start]); + } + ++ary->start; + return 0; + case 1: + if (!ary->start) + return -1; + if (old) { + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY2EMBEDDED(ary)->embedded[0]); +#if FIO_ARRAY_DESTROY_AFTER_COPY + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[0]); #endif - key); - if (i == (uint32_t)-1) - return NULL; - return m->map + i; + } else { + FIO_ARRAY_TYPE_DESTROY(FIO_ARRAY2EMBEDDED(ary)->embedded[0]); + } + --ary->start; + if (ary->start) + FIO_MEMMOVE(FIO_ARRAY2EMBEDDED(ary)->embedded, + FIO_ARRAY2EMBEDDED(ary)->embedded + + FIO_ARRAY2EMBEDDED(ary)->start, + FIO_ARRAY2EMBEDDED(ary)->start * + sizeof(*FIO_ARRAY2EMBEDDED(ary)->embedded)); + FIO_MEMSET(FIO_ARRAY2EMBEDDED(ary)->embedded + ary->start, + 0, + sizeof(*ary->ary)); + return 0; + } + if (old) + FIO_ARRAY_TYPE_COPY(old[0], FIO_ARRAY_TYPE_INVALID); + return -1; } -/* ***************************************************************************** +/** + * Iteration using a callback for each entry in the array. + * + * The callback task function must accept an the entry data as well as an opaque + * user pointer. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +IFUNC uint32_t FIO_NAME(FIO_ARRAY_NAME, + each)(FIO_ARRAY_PTR ary_, + int (*task)(FIO_NAME(FIO_ARRAY_NAME, each_s) * + info), + void *udata, + int64_t start_at) { + FIO_ARRAY_TYPE *a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + if (!a) + return (uint32_t)-1; + uint32_t count = FIO_NAME(FIO_ARRAY_NAME, count)(ary_); + if (start_at < 0) { + start_at = count - start_at; + if (start_at < 0) + start_at = 0; + } -Map Iterators + if (!a || !task) + return (uint32_t)-1; + if ((uint32_t)start_at >= count) + return (uint32_t)count; + FIO_NAME(FIO_ARRAY_NAME, each_s) + e = { + .parent = ary_, + .index = (uint64_t)start_at, + .task = task, + .udata = udata, + }; -***************************************************************************** */ + while ((uint32_t)e.index < FIO_NAME(FIO_ARRAY_NAME, count)(ary_)) { + a = FIO_NAME2(FIO_ARRAY_NAME, ptr)(ary_); + e.value = a[e.index]; + int r = e.task(&e); + ++e.index; + if (r == -1) { + return (uint32_t)(e.index); + } + } + return (uint32_t)e.index; +} -SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) - FIO_NAME(FIO_MAP_NAME, - get_next)(FIO_MAP_PTR map, - FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos) { - FIO_NAME(FIO_MAP_NAME, iterator_s) r = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(map, r); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(m); - if (!m->count) - return r; - if (!current_pos) - goto empty; - if (current_pos->private_.pos + 1 == m->count) - return r; - if (current_pos->private_.map_validator != (uintptr_t)(m->map)) - return r; /* mutation stops iteration */ -#if FIO_MAP_ORDERED - if (current_pos->node->node.next == m->head) - return r; - r.node = m->map + current_pos->node->node.next; -#else - for (size_t i = current_pos->node - m->map + 1; i < FIO_MAP_CAPA(m->bits); - ++i) { - if (!imap[i] || imap[i] == 0xFFU) - continue; - r.node = m->map + i; - break; - } - if (!r.node) - return r; -#endif +/* ***************************************************************************** +Dynamic Arrays - test +***************************************************************************** */ +#ifdef FIO_TEST_ALL - r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { - .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), -#ifdef FIO_MAP_VALUE - .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), -#endif -#if !FIO_MAP_RECALC_HASH - .hash = r.node->hash, -#endif - .private_ = {.index = (uint32_t)(r.node - m->map), - .pos = current_pos->private_.pos + 1, - .map_validator = (uintptr_t)m->map}, - }; - return r; -empty: +/* make suer the functions are defined for the testing */ +#ifdef FIO_REF_CONSTRUCTOR_ONLY +IFUNC FIO_ARRAY_PTR FIO_NAME(FIO_ARRAY_NAME, new)(void); +IFUNC void FIO_NAME(FIO_ARRAY_NAME, free)(FIO_ARRAY_PTR ary); +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ -#if FIO_MAP_ORDERED - r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { - .node = m->map + m->head, - .key = FIO_MAP_KEY_FROM_INTERNAL(m->map[m->head].key), -#ifdef FIO_MAP_VALUE - .value = FIO_MAP_VALUE_FROM_INTERNAL(m->map[m->head].value), -#endif -#if !FIO_MAP_RECALC_HASH - .hash = m->map[m->head].hash, -#endif - .private_ = {.index = m->head, - .pos = 0, - .map_validator = (uintptr_t)m->map}, - }; -#else - for (size_t i = 0; i < FIO_MAP_CAPA(m->bits); ++i) { - if (!imap[i] || imap[i] == 0xFFU) - continue; - r.node = m->map + i; - break; - } +#define FIO_ARRAY_TEST_OBJ_SET(dest, val) \ + FIO_MEMSET(&(dest), (int)(val), sizeof(FIO_ARRAY_TYPE)) +#define FIO_ARRAY_TEST_OBJ_IS(val) \ + (!FIO_MEMCMP(&o, \ + FIO_MEMSET(&v, (int)(val), sizeof(v)), \ + sizeof(FIO_ARRAY_TYPE))) - r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { - .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), -#ifdef FIO_MAP_VALUE - .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), -#endif -#if !FIO_MAP_RECALC_HASH - .hash = r.node->hash, -#endif - .private_ = {.index = (uint32_t)(r.node - m->map), - .pos = 0, - .map_validator = (uintptr_t)m->map}, - }; +FIO_SFUNC int FIO_NAME_TEST(stl, FIO_NAME(FIO_ARRAY_NAME, test_task))( + FIO_NAME(FIO_ARRAY_NAME, each_s) * i) { + struct data_s { + int i; + int va[]; + } *d = (struct data_s *)i->udata; + FIO_ARRAY_TYPE v; -#endif - return r; - (void)imap; /* if unused */ + FIO_ARRAY_TEST_OBJ_SET(v, d->va[d->i]); + ++d->i; + if (d->va[d->i + 1]) + return 0; + return -1; } -SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) - FIO_NAME(FIO_MAP_NAME, get_prev)(FIO_MAP_PTR map, - FIO_NAME(FIO_MAP_NAME, iterator_s) * - current_pos) { // TODO! - FIO_NAME(FIO_MAP_NAME, iterator_s) r = {0}; - FIO_PTR_TAG_VALID_OR_RETURN(map, r); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); -#if FIO_MAP_ORDERED - uint32_t ipos; -#else - uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(m); -#endif - if (!m->count) - return r; - if (!current_pos) - goto empty; - if (!current_pos->private_.pos) - return r; - if (current_pos->private_.map_validator != (uintptr_t)(m->map)) - return r; /* mutation stops iteration */ -#if FIO_MAP_ORDERED - if (current_pos->private_.index == m->head) - return r; - r.node = m->map + current_pos->node->node.prev; -#else - for (size_t i = current_pos->node - m->map; i;) { - --i; - if (!imap[i] || imap[i] == 0xFFU) - continue; - r.node = m->map + i; - break; - } -#endif - - r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { - .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), -#ifdef FIO_MAP_VALUE - .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), -#endif -#if !FIO_MAP_RECALC_HASH - .hash = r.node->hash, -#endif - .private_ = {.index = (uint32_t)(r.node - m->map), - .pos = current_pos->private_.pos - 1, - .map_validator = (uintptr_t)m->map}, - }; - return r; -empty: - -#if FIO_MAP_ORDERED +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_ARRAY_NAME)(void) { + FIO_ARRAY_TYPE o; + FIO_ARRAY_TYPE v; + FIO_NAME(FIO_ARRAY_NAME, s) a_on_stack = FIO_ARRAY_INIT; + FIO_ARRAY_PTR a_array[2]; + a_array[0] = (FIO_ARRAY_PTR)FIO_PTR_TAG((&a_on_stack)); + a_array[1] = FIO_NAME(FIO_ARRAY_NAME, new)(); + FIO_ASSERT_ALLOC(a_array[1]); + /* perform test twice, once for an array on the stack and once for allocate */ + for (int selector = 0; selector < 2; ++selector) { + FIO_ARRAY_PTR a = a_array[selector]; + fprintf(stderr, + "* Testing dynamic arrays on the %s (" FIO_MACRO2STR( + FIO_NAME(FIO_ARRAY_NAME, + s)) ").\n" + " This type supports %zu embedded items\n", + (selector ? "heap" : "stack"), + FIO_ARRAY_EMBEDDED_CAPA); + /* Test start here */ - ipos = m->map[m->head].node.prev; - r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { - .node = m->map + ipos, .key = FIO_MAP_KEY_FROM_INTERNAL(m->map[ipos].key), -#ifdef FIO_MAP_VALUE - .value = FIO_MAP_VALUE_FROM_INTERNAL(m->map[ipos].value), -#endif -#if !FIO_MAP_RECALC_HASH - .hash = m->map[ipos].hash, -#endif - .private_ = {.index = ipos, - .pos = m->count - 1, - .map_validator = (uintptr_t)m->map}, - }; + /* test push */ + for (int i = 0; i < (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; ++i) { + FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); + o = *FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "push failed (%d)", i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "push-get cycle failed (%d)", i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "get with -1 returned wrong result (%d)", + i); + } + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + FIO_ARRAY_EMBEDDED_CAPA + 3, + "push didn't update count correctly (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3); -#else - for (size_t i = FIO_MAP_CAPA(m->bits); i;) { - --i; - if (!imap[i] || imap[i] == 0xFFU) - continue; - r.node = m->map + i; - break; - } + /* test pop */ + for (int i = (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; i--;) { + FIO_NAME(FIO_ARRAY_NAME, pop)(a, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((i + 1)), + "pop value error failed (%d)", + i); + } + FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), + "pop didn't pop all elements?"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, pop)(a, &o), + "pop for empty array should return an error."); - r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { - .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), -#ifdef FIO_MAP_VALUE - .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), -#endif -#if !FIO_MAP_RECALC_HASH - .hash = r.node->hash, -#endif - .private_ = {.index = (uint32_t)(r.node - m->map), - .pos = m->count - 1, - .map_validator = (uintptr_t)m->map}, - }; + /* test compact with zero elements */ + FIO_NAME(FIO_ARRAY_NAME, compact)(a); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, + "compact zero elementes didn't make array embedded?"); -#endif + /* test unshift */ + for (int i = (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; i--;) { + FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); + o = *FIO_NAME(FIO_ARRAY_NAME, unshift)(a, o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), "shift failed (%d)", i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "unshift-get cycle failed (%d)", + i); + int64_t negative_index = 0 - (((int)(FIO_ARRAY_EMBEDDED_CAPA) + 3) - i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, negative_index); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "get with %d returned wrong result.", + negative_index); + } + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + FIO_ARRAY_EMBEDDED_CAPA + 3, + "unshift didn't update count correctly (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3); - return r; -} + /* test shift */ + for (int i = 0; i < (int)(FIO_ARRAY_EMBEDDED_CAPA) + 3; ++i) { + FIO_NAME(FIO_ARRAY_NAME, shift)(a, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((i + 1)), + "shift value error failed (%d)", + i); + } + FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), + "shift didn't shift all elements?"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, shift)(a, &o), + "shift for empty array should return an error."); -/* ***************************************************************************** + /* test set from embedded? array */ + FIO_NAME(FIO_ARRAY_NAME, compact)(a); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, + "compact zero elementes didn't make array embedded (2)?"); + FIO_ARRAY_TEST_OBJ_SET(o, 1); + FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + if (FIO_ARRAY_EMBEDDED_CAPA) { + FIO_ARRAY_TEST_OBJ_SET(o, 1); + FIO_NAME(FIO_ARRAY_NAME, set)(a, FIO_ARRAY_EMBEDDED_CAPA, o, &o); + FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), + "set overflow from embedded array should reset `old`"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + FIO_ARRAY_EMBEDDED_CAPA + 1, + "set didn't update count correctly from embedded " + "array (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)FIO_ARRAY_EMBEDDED_CAPA); + } + /* test set from bigger array */ + FIO_ARRAY_TEST_OBJ_SET(o, 1); + FIO_NAME(FIO_ARRAY_NAME, set) + (a, ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), o, &o); + FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), + "set overflow should reset `old`"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "set didn't update count correctly (%d != %d)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4)); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), + "set capa should be above item count"); + if (FIO_ARRAY_EMBEDDED_CAPA) { + FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); + FIO_NAME(FIO_ARRAY_NAME, set)(a, FIO_ARRAY_EMBEDDED_CAPA, o, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), + "set overflow lost last item while growing."); + } + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, (FIO_ARRAY_EMBEDDED_CAPA + 1) * 2); + FIO_ASSERT(FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID), + "set overflow should have memory in the middle set to invalid " + "objetcs."); + FIO_ARRAY_TEST_OBJ_SET(o, 2); + FIO_NAME(FIO_ARRAY_NAME, set)(a, 0, o, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), + "set should set `old` to previous value"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "set item count error"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "set capa should be above item count"); + /* test find TODO: test with uninitialized array */ + FIO_ARRAY_TEST_OBJ_SET(o, 99); + if (FIO_ARRAY_TYPE_CMP(o, FIO_ARRAY_TYPE_INVALID)) { + FIO_ARRAY_TEST_OBJ_SET(o, 100); + } + uint32_t found = FIO_NAME(FIO_ARRAY_NAME, find)(a, o, 0); + FIO_ASSERT(found == (uint32_t)-1, + "seeking for an object that doesn't exist should fail."); + FIO_ARRAY_TEST_OBJ_SET(o, 1); + found = FIO_NAME(FIO_ARRAY_NAME, find)(a, o, 1); + FIO_ASSERT(found == ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), + "seeking for an object returned the wrong index."); + FIO_ASSERT(found == FIO_NAME(FIO_ARRAY_NAME, find)(a, o, -1), + "seeking for an object in reverse returned the wrong index."); + FIO_ARRAY_TEST_OBJ_SET(o, 2); + FIO_ASSERT( + !FIO_NAME(FIO_ARRAY_NAME, find)(a, o, -2), + "seeking for an object in reverse (2) returned the wrong index."); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "find should have side-effects - count error"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) >= + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) + 1, + "find should have side-effects - capa error"); -Map Each + /* test remove */ + FIO_NAME(FIO_ARRAY_NAME, remove)(a, found, &o); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(1), "remove didn't copy old data?"); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(2), "remove removed more?"); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4), + "remove with didn't update count correctly (%d != %s)", + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4)); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); + /* test remove2 */ + FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); + FIO_ASSERT((found = FIO_NAME(FIO_ARRAY_NAME, remove2)(a, o)) == + ((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) - 1, + "remove2 result error, %d != %d items.", + found, + (int)((FIO_ARRAY_EMBEDDED_CAPA + 1) * 4) - 1); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == 1, + "remove2 didn't update count correctly (%d != 1)", + FIO_NAME(FIO_ARRAY_NAME, count)(a)); + /* hopefuly these will end... or crash on error. */ + while (!FIO_NAME(FIO_ARRAY_NAME, pop)(a, NULL)) { + ; + } + while (!FIO_NAME(FIO_ARRAY_NAME, shift)(a, NULL)) { + ; + } -***************************************************************************** */ + /* test push / unshift alternate */ + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + for (int i = 0; i < 4096; ++i) { + FIO_ARRAY_TEST_OBJ_SET(o, (i + 1)); + FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) + 1 == + ((uint32_t)(i + 1) << 1), + "push-unshift[%d.5] cycle count arror (%d != %d)", + i, + FIO_NAME(FIO_ARRAY_NAME, count)(a), + (((uint32_t)(i + 1) << 1)) - 1); + FIO_ARRAY_TEST_OBJ_SET(o, (i + 4097)); + FIO_NAME(FIO_ARRAY_NAME, unshift)(a, o); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, count)(a) == ((uint32_t)(i + 1) << 1), + "push-unshift[%d] cycle count arror (%d != %d)", + i, + FIO_NAME(FIO_ARRAY_NAME, count)(a), + ((uint32_t)(i + 1) << 1)); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, 0); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 4097), + "unshift-push cycle failed (%d)", + i); + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, -1); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS(i + 1), + "push-shift cycle failed (%d)", + i); + } + for (int i = 0; i < 4096; ++i) { + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((4096 * 2) - i), + "item value error at index %d", + i); + } + for (int i = 0; i < 4096; ++i) { + o = FIO_NAME(FIO_ARRAY_NAME, get)(a, i + 4096); + FIO_ASSERT(FIO_ARRAY_TEST_OBJ_IS((1 + i)), + "item value error at index %d", + i + 4096); + } +#if DEBUG + for (int i = 0; i < 2; ++i) { + FIO_LOG_DEBUG2( + "\t- " FIO_MACRO2STR( + FIO_NAME(FIO_ARRAY_NAME, s)) " after push/unshit cycle%s:\n" + "\t\t- item count: %d items\n" + "\t\t- capacity: %d items\n" + "\t\t- memory: %d bytes\n", + (i ? " after compact" : ""), + FIO_NAME(FIO_ARRAY_NAME, count)(a), + FIO_NAME(FIO_ARRAY_NAME, capa)(a), + FIO_NAME(FIO_ARRAY_NAME, capa)(a) * sizeof(FIO_ARRAY_TYPE)); + FIO_NAME(FIO_ARRAY_NAME, compact)(a); + } +#endif /* DEBUG */ -typedef struct { - FIO_NAME(FIO_MAP_NAME, each_s) each; - ssize_t start_at; -} FIO_NAME(FIO_MAP_NAME, __each_info_s); + FIO_ARRAY_TYPE_COPY(o, FIO_ARRAY_TYPE_INVALID); +/* test set with NULL, hopefully a bug will cause a crash */ +#if FIO_ARRAY_TYPE_DESTROY_SIMPLE + for (int i = 0; i < 4096; ++i) { + FIO_NAME(FIO_ARRAY_NAME, set)(a, i, o, NULL); + } +#else + /* + * we need to clear the memory to make sure a cleanup actions don't get + * unexpected values. + */ + for (int i = 0; i < (4096 * 2); ++i) { + FIO_ARRAY_TYPE_COPY((FIO_NAME2(FIO_ARRAY_NAME, ptr)(a)[i]), + FIO_ARRAY_TYPE_INVALID); + } -FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, - __each_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * e) { - int r; - FIO_NAME(FIO_MAP_NAME, __each_info_s) *info = - (FIO_NAME(FIO_MAP_NAME, __each_info_s) *)e->udata; - info->each.key = FIO_MAP_KEY_FROM_INTERNAL(e->node->key); -#ifdef FIO_MAP_VALUE - info->each.value = FIO_MAP_VALUE_FROM_INTERNAL(e->node->value); #endif - r = info->each.task(&info->each); - ++info->each.index; - return r; -} - -FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, __each_task_offset)( - FIO_NAME(FIO_MAP_NAME, __each_node_s) * e) { - FIO_NAME(FIO_MAP_NAME, __each_info_s) *info = - (FIO_NAME(FIO_MAP_NAME, __each_info_s) *)e->udata; - if (FIO_LIKELY(info->each.index < (uint64_t)info->start_at)) { - ++info->each.index; - return 0; - } - return (e->fn = FIO_NAME(FIO_MAP_NAME, __each_task))(e); -} -/** - * Iteration using a callback for each element in the map. - * - * The callback task function must accept an each_s pointer, see above. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. - */ -SFUNC uint32_t FIO_NAME(FIO_MAP_NAME, - each)(FIO_MAP_PTR map, - int (*task)(FIO_NAME(FIO_MAP_NAME, each_s) *), - void *udata, - ssize_t start_at) { - uint32_t r = (uint32_t)-1; - FIO_PTR_TAG_VALID_OR_RETURN(map, r); - FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); - if (start_at < 0) - start_at += m->count; - if (start_at < 0) - return m->count; - FIO_NAME(FIO_MAP_NAME, __each_info_s) - e = { - .each = - { - .parent = map, - .index = 0, - .task = task, - .udata = udata, - }, - .start_at = start_at, - }; + /* TODO: test concat */ - FIO_NAME(FIO_MAP_NAME, __each_node) - (m, - !start_at ? FIO_NAME(FIO_MAP_NAME, __each_task) - : FIO_NAME(FIO_MAP_NAME, __each_task_offset), - &e); - return (uint32_t)e.each.index; -} - -/* ***************************************************************************** -Map Testing -***************************************************************************** */ -#ifdef FIO_MAP_TEST - -#ifdef FIO_MAP_HASH_FN -#define FIO___M_HASH(k) -#else -#define FIO___M_HASH(k) (k), -#endif -#ifdef FIO_MAP_VALUE -#define FIO___M_VAL(v) , (v) -#define FIO___M_OLD , NULL -#else -#define FIO___M_VAL(v) -#define FIO___M_OLD -#endif + /* test each */ + { + struct data_s { + int i; + int va[10]; + } d = {1, {1, 8, 2, 7, 3, 6, 4, 5}}; + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + for (int i = 0; d.va[i]; ++i) { + FIO_ARRAY_TEST_OBJ_SET(o, d.va[i]); + FIO_NAME(FIO_ARRAY_NAME, push)(a, o); + } -FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MAP_NAME)(void) { - /* testing only only works with integer external types */ - fprintf( - stderr, - "* Testing map " FIO_MACRO2STR(FIO_MAP_NAME) " with key " FIO_MACRO2STR( - FIO_MAP_KEY) " (=> " FIO_MACRO2STR(FIO_MAP_VALUE) ").\n"); - size_t test_len_limit = (1UL << (FIO_MAP_ARRAY_LOG_LIMIT + 15)); - { /* test set / get overwrite , FIO_MAP_EACH and evict */ - FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; - for (size_t i = 1; i < test_len_limit; ++i) { - FIO_NAME(FIO_MAP_NAME, set) - (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, - "map `set` failed? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), - i); - for (size_t j = ((i << 2) + 1); j < i; ++j) { /* effects LRU ordering */ - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j) && - FIO_NAME(FIO_MAP_NAME, node2val)( - FIO_NAME(FIO_MAP_NAME, - get_ptr)(&map, FIO___M_HASH(j) j)) == j, - "map `get` failed? %zu/%zu (%p)", - j, + int index = FIO_NAME(FIO_ARRAY_NAME, each)( + a, + FIO_NAME_TEST(stl, FIO_NAME(FIO_ARRAY_NAME, test_task)), + (void *)&d, + d.i); + FIO_ASSERT(index == d.i, + "index rerturned from each should match next object"); + FIO_ASSERT(*(char *)&d.va[d.i], + "array each error (didn't stop in time?)."); + FIO_ASSERT(!(*(char *)&d.va[d.i + 1]), + "array each error (didn't stop in time?)."); + } +#if FIO_ARRAY_TYPE_DESTROY_SIMPLE + { + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + size_t max_items = 63; + FIO_ARRAY_TYPE tmp[64]; + for (size_t i = 0; i < max_items; ++i) { + FIO_MEMSET(tmp + i, i + 1, sizeof(*tmp)); + } + for (size_t items = 0; items <= max_items; items = ((items << 1) | 1)) { + FIO_LOG_DEBUG2("* testing the FIO_ARRAY_EACH macro with %zu items.", + items); + size_t i = 0; + for (i = 0; i < items; ++i) + FIO_NAME(FIO_ARRAY_NAME, push)(a, tmp[i]); + i = 0; + FIO_ARRAY_EACH(FIO_ARRAY_NAME, a, pos) { + FIO_ASSERT(!memcmp(tmp + i, pos, sizeof(*pos)), + "FIO_ARRAY_EACH pos is at wrong index %zu != %zu", + (size_t)(pos - FIO_NAME2(FIO_ARRAY_NAME, ptr)(a)), + i); + ++i; + } + FIO_ASSERT(i == items, + "FIO_ARRAY_EACH macro count error - didn't review all " + "items? %zu != %zu ", i, - FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j)); - FIO_NAME(FIO_MAP_NAME, set) - (&map, FIO___M_HASH(j) j FIO___M_VAL(j) FIO___M_OLD); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, - "map `set` added an item that already exists? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), - i); + items); + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); } } - /* test FIO_MAP_EACH and ordering */ - uint32_t count = FIO_NAME(FIO_MAP_NAME, count)(&map); - uint32_t loop_test = 0; - FIO_MAP_EACH(FIO_MAP_NAME, &map, i) { - /* test ordering */ - ++loop_test; -#ifdef FIO_MAP_LRU - FIO_ASSERT(i.key == loop_test, - "map FIO_MAP_EACH LRU ordering broken? %zu != %zu", - (size_t)(i.key), - (size_t)(count - loop_test)); -#elif FIO_MAP_ORDERED - FIO_ASSERT(i.key == loop_test, - "map FIO_MAP_EACH ordering broken? %zu != %zu", - (size_t)(i.key), - (size_t)(loop_test)); -#else - FIO_ASSERT(i.key < test_len_limit, - "map FIO_MAP_EACH invalid data? %zu !< %zu", - (size_t)(i.key), - (size_t)(test_len_limit)); -#endif - } - FIO_ASSERT(loop_test == count, - "FIO_MAP_EACH failed to iterate all elements? (%zu != %zu", - (size_t)loop_test != (size_t)count); - loop_test = 0; - FIO_MAP_EACH_REVERSED(FIO_MAP_NAME, &map, i) { - /* test reversed ordering */ - ++loop_test; -#ifdef FIO_MAP_LRU - FIO_ASSERT(i.key == (count - (loop_test - 1)), - "map FIO_MAP_EACH_REVERSED LRU ordering broken? %zu != %zu", - (size_t)(i.key), - (size_t)(count - loop_test)); -#elif FIO_MAP_ORDERED - FIO_ASSERT(i.key == (count - (loop_test - 1)), - "map FIO_MAP_EACH_REVERSED ordering broken? %zu != %zu", - (size_t)(i.key), - (size_t)(loop_test)); -#endif - } - FIO_ASSERT( - loop_test == count, - "FIO_MAP_EACH_REVERSED failed to iterate all elements? (%zu != %zu", - (size_t)loop_test != (size_t)count); - /* test `evict` while we're here */ - FIO_NAME(FIO_MAP_NAME, evict)(&map, (count >> 1)); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == (count - (count >> 1)), - "map `evict` count error %zu != %zu", - (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), - (size_t)(count - (count >> 1))); - /* cleanup */ - FIO_NAME(FIO_MAP_NAME, destroy)(&map); - } -#if !FIO_MAP_RECALC_HASH - { /* test full collision guard and zero hash*/ - FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; - fprintf( - stderr, - "* Testing full collision guard for " FIO_MACRO2STR( - FIO_NAME(FIO_MAP_NAME, s)) " - expect SECURITY log messages.\n"); - for (size_t i = 1; i < 4096; ++i) { - FIO_NAME(FIO_MAP_NAME, set) - (&map, FIO___M_HASH(0) i FIO___M_VAL(i) FIO___M_OLD); - } - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map), - "zero hash fails insertion?"); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) <= FIO_MAP_ATTACK_LIMIT, - "map attack guard failed? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), - (size_t)FIO_MAP_ATTACK_LIMIT); - FIO_NAME(FIO_MAP_NAME, destroy)(&map); - } #endif - { /* test reserve, remove */ - FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; - FIO_NAME(FIO_MAP_NAME, reserve)(&map, 4096); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, capa)(&map) == 4096, - "map reserve error? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP_NAME, capa)(&map), - 4096); - for (size_t i = 1; i < test_len_limit; ++i) { - FIO_NAME(FIO_MAP_NAME, set) - (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, "insertion failed?"); - } - for (size_t i = 1; i < test_len_limit; ++i) { - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, get)(&map, FIO___M_HASH(i) i), - "key missing?"); - size_t count = FIO_NAME(FIO_MAP_NAME, count)(&map); - FIO_NAME(FIO_MAP_NAME, remove) - (&map, FIO___M_HASH(i) i, NULL); - FIO_ASSERT(!FIO_NAME(FIO_MAP_NAME, get)(&map, FIO___M_HASH(i) i), - "map_remove error?"); - FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == count - 1, - "map count error after removal? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), - count - 1); - /* see if removal produces errors while rehashing */ - FIO_NAME(FIO_MAP_NAME, compact)(&map); - } - FIO_NAME(FIO_MAP_NAME, destroy)(&map); + /* test destroy */ + FIO_NAME(FIO_ARRAY_NAME, destroy)(a); + FIO_ASSERT(!FIO_NAME(FIO_ARRAY_NAME, count)(a), + "destroy didn't clear count."); + FIO_ASSERT(FIO_NAME(FIO_ARRAY_NAME, capa)(a) == FIO_ARRAY_EMBEDDED_CAPA, + "destroy capa error."); + /* Test end here */ } + FIO_NAME(FIO_ARRAY_NAME, free)(a_array[1]); } -#undef FIO___M_HASH -#undef FIO___M_VAL -#undef FIO___M_OLD +#undef FIO_ARRAY_TEST_OBJ_SET +#undef FIO_ARRAY_TEST_OBJ_IS -#endif /* FIO_MAP_TEST */ +#endif /* FIO_TEST_ALL */ /* ***************************************************************************** -Map Cleanup +Dynamic Arrays - cleanup ***************************************************************************** */ - #endif /* FIO_EXTERN_COMPLETE */ +#endif /* FIO_ARRAY_NAME */ -#undef FIO_MAP_ARRAY_LOG_LIMIT -#undef FIO_MAP_ATTACK_LIMIT -#undef FIO_MAP_CAPA -#undef FIO_MAP_CAPA_BITS_LIMIT -#undef FIO_MAP_CUCKOO_STEPS -#undef FIO_MAP_GET_T -#undef FIO_MAP_HASH_FN -#undef FIO_MAP_IS_SPARSE -#undef FIO_MAP_KEY -#undef FIO_MAP_KEY_CMP -#undef FIO_MAP_KEY_COPY -#undef FIO_MAP_KEY_DESTROY -#undef FIO_MAP_KEY_DESTROY_SIMPLE -#undef FIO_MAP_KEY_DISCARD -#undef FIO_MAP_KEY_FROM_INTERNAL -#undef FIO_MAP_KEY_INTERNAL -#undef FIO_MAP_KEY_IS_GREATER_THAN -#undef FIO_MAP_LRU -#undef FIO_MAP_NAME -#undef FIO_MAP_ORDERED -#undef FIO_MAP_PTR -#undef FIO_MAP_RECALC_HASH -#undef FIO_MAP_SEEK_LIMIT -#undef FIO_MAP_T -#undef FIO_MAP_TEST -#undef FIO_MAP_VALUE -#undef FIO_MAP_VALUE_BSTR -#undef FIO_MAP_VALUE_COPY -#undef FIO_MAP_VALUE_DESTROY -#undef FIO_MAP_VALUE_DESTROY_SIMPLE -#undef FIO_MAP_VALUE_DISCARD -#undef FIO_MAP_VALUE_FROM_INTERNAL -#undef FIO_MAP_VALUE_INTERNAL - -#undef FIO_MAP___MAKE_BITMAP -#undef FIO_MAP___STEP_POS -#undef FIO_MAP___TEST_MATCH -#undef FIO___MAP_UPDATE_ORDER -#undef FIO_MAP_ARRAY_LOG_LIMIT -#undef FIO_MAP_ATTACK_LIMIT -#undef FIO_MAP_CAPA -#undef FIO_MAP_CAPA_BITS_LIMIT -#undef FIO_MAP_CUCKOO_STEPS -#undef FIO_MAP_GET_T -#undef FIO_MAP_HASH_FN -#undef FIO_MAP_IS_SPARSE -#undef FIO_MAP_KEY -#undef FIO_MAP_KEY_BSTR -#undef FIO_MAP_KEY_CMP -#undef FIO_MAP_KEY_COPY -#undef FIO_MAP_KEY_DESTROY -#undef FIO_MAP_KEY_DESTROY_SIMPLE -#undef FIO_MAP_KEY_DISCARD -#undef FIO_MAP_KEY_FROM_INTERNAL -#undef FIO_MAP_KEY_INTERNAL -#undef FIO_MAP_KEY_IS_GREATER_THAN -#undef FIO_MAP_KEY_KSTR -#undef FIO_MAP_LRU -#undef FIO_MAP_MINIMAL_BITS -#undef FIO_MAP_NAME -#undef FIO_MAP_ORDERED -#undef FIO_MAP_PTR -#undef FIO_MAP_RECALC_HASH -#undef FIO_MAP_SEEK_LIMIT -#undef FIO_MAP_T -#undef FIO_MAP_TEST -#undef FIO_MAP_VALUE -#undef FIO_MAP_VALUE_BSTR -#undef FIO_MAP_VALUE_COPY -#undef FIO_MAP_VALUE_DESTROY -#undef FIO_MAP_VALUE_DESTROY_SIMPLE -#undef FIO_MAP_VALUE_DISCARD -#undef FIO_MAP_VALUE_FROM_INTERNAL -#undef FIO_MAP_VALUE_INTERNAL - -#undef FIO_OMAP_NAME -#undef FIO_UMAP_NAME - -#endif /* FIO_MAP_NAME */ +#undef FIO_ARRAY_NAME +#undef FIO_ARRAY_TYPE +#undef FIO_ARRAY_ENABLE_EMBEDDED +#undef FIO_ARRAY_TYPE_INVALID +#undef FIO_ARRAY_TYPE_INVALID_SIMPLE +#undef FIO_ARRAY_TYPE_COPY +#undef FIO_ARRAY_TYPE_COPY_SIMPLE +#undef FIO_ARRAY_TYPE_CONCAT_COPY +#undef FIO_ARRAY_TYPE_CONCAT_COPY_SIMPLE +#undef FIO_ARRAY_TYPE_DESTROY +#undef FIO_ARRAY_TYPE_DESTROY_SIMPLE +#undef FIO_ARRAY_DESTROY_AFTER_COPY +#undef FIO_ARRAY_TYPE_CMP +#undef FIO_ARRAY_TYPE_CMP_SIMPLE +#undef FIO_ARRAY_PADDING +#undef FIO_ARRAY_SIZE2WORDS +#undef FIO_ARRAY_POS2ABS +#undef FIO_ARRAY_AB_CT +#undef FIO_ARRAY_PTR +#undef FIO_ARRAY_EXPONENTIAL +#undef FIO_ARRAY_ADD2CAPA +#undef FIO_ARRAY_IS_EMBEDDED +#undef FIO_ARRAY_IS_EMBEDDED_PTR +#undef FIO_ARRAY_EMBEDDED_CAPA +#undef FIO_ARRAY2EMBEDDED /* ************************************************************************* */ #if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ #define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_MAP2_NAME map /* Development inclusion - ignore line */ -#define FIO_MAP2_TEST /* Development inclusion - ignore line */ -#define FIO_MAP2_KEY size_t /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ +#define FIO_MAP_NAME map /* Development inclusion - ignore line */ +#define FIO_MAP_KEY size_t /* Development inclusion - ignore line */ +// #define FIO_MAP_VALUE_BSTR /* Development inclusion - ignore line */ +// #define FIO_MAP_ORDERED /* Development inclusion - ignore line */ +#define FIO_MAP_TEST /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ /* ***************************************************************************** @@ -28103,137 +27772,137 @@ Map Cleanup Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ -#if defined(FIO_MAP2_NAME) +#if defined(FIO_MAP_NAME) /* ***************************************************************************** Map Settings - Sets have only keys (value == key) - Hash Maps have values ***************************************************************************** */ -/* if FIO_MAP2_KEY_KSTR is defined, use fio_keystr_s keys */ -#ifdef FIO_MAP2_KEY_KSTR -#define FIO_MAP2_KEY fio_str_info_s -#define FIO_MAP2_KEY_INTERNAL fio_keystr_s -#define FIO_MAP2_KEY_FROM_INTERNAL(k) fio_keystr_info(&(k)) -#define FIO_MAP2_KEY_COPY(dest, src) \ - (dest) = fio_keystr_init((src), FIO_NAME(FIO_MAP2_NAME, __key_alloc)) -#define FIO_MAP2_KEY_CMP(a, b) fio_keystr_is_eq2((a), (b)) -#define FIO_MAP2_KEY_DESTROY(key) \ - fio_keystr_destroy(&(key), FIO_NAME(FIO_MAP2_NAME, __key_free)) -#define FIO_MAP2_KEY_DISCARD(key) -FIO_SFUNC void *FIO_NAME(FIO_MAP2_NAME, __key_alloc)(size_t len) { +/* if FIO_MAP_KEY_KSTR is defined, use fio_keystr_s keys */ +#ifdef FIO_MAP_KEY_KSTR +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL fio_keystr_s +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_keystr_info(&(k)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio_keystr_init((src), FIO_NAME(FIO_MAP_NAME, __key_alloc)) +#define FIO_MAP_KEY_CMP(a, b) fio_keystr_is_eq2((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) \ + fio_keystr_destroy(&(key), FIO_NAME(FIO_MAP_NAME, __key_free)) +#define FIO_MAP_KEY_DISCARD(key) +FIO_SFUNC void *FIO_NAME(FIO_MAP_NAME, __key_alloc)(size_t len) { return FIO_MEM_REALLOC_(NULL, 0, len, 0); } -FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, __key_free)(void *ptr, size_t len) { +FIO_SFUNC void FIO_NAME(FIO_MAP_NAME, __key_free)(void *ptr, size_t len) { FIO_MEM_FREE_(ptr, len); (void)len; /* if unused */ } -#undef FIO_MAP2_KEY_KSTR +#undef FIO_MAP_KEY_KSTR -/* if FIO_MAP2_KEY is undefined, assume String keys (using `fio_bstr`). */ -#elif !defined(FIO_MAP2_KEY) || defined(FIO_MAP2_KEY_BSTR) -#define FIO_MAP2_KEY fio_str_info_s -#define FIO_MAP2_KEY_INTERNAL char * -#define FIO_MAP2_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) -#define FIO_MAP2_KEY_COPY(dest, src) \ +/* if FIO_MAP_KEY is undefined, assume String keys (using `fio_bstr`). */ +#elif !defined(FIO_MAP_KEY) || defined(FIO_MAP_KEY_BSTR) +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL char * +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP_KEY_COPY(dest, src) \ (dest) = fio_bstr_write(NULL, (src).buf, (src).len) -#define FIO_MAP2_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) -#define FIO_MAP2_KEY_DESTROY(key) fio_bstr_free((key)) -#define FIO_MAP2_KEY_DISCARD(key) +#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP_KEY_DISCARD(key) #endif -#undef FIO_MAP2_KEY_BSTR +#undef FIO_MAP_KEY_BSTR -#ifndef FIO_MAP2_KEY_INTERNAL -#define FIO_MAP2_KEY_INTERNAL FIO_MAP2_KEY +#ifndef FIO_MAP_KEY_INTERNAL +#define FIO_MAP_KEY_INTERNAL FIO_MAP_KEY #endif -#ifndef FIO_MAP2_KEY_FROM_INTERNAL -#define FIO_MAP2_KEY_FROM_INTERNAL(o) o +#ifndef FIO_MAP_KEY_FROM_INTERNAL +#define FIO_MAP_KEY_FROM_INTERNAL(o) o #endif -#ifndef FIO_MAP2_KEY_COPY -#define FIO_MAP2_KEY_COPY(dest, src) ((dest) = (src)) +#ifndef FIO_MAP_KEY_COPY +#define FIO_MAP_KEY_COPY(dest, src) ((dest) = (src)) #endif -#ifndef FIO_MAP2_KEY_CMP -#define FIO_MAP2_KEY_CMP(a, b) ((a) == (b)) +#ifndef FIO_MAP_KEY_CMP +#define FIO_MAP_KEY_CMP(a, b) ((a) == (b)) #endif -#ifndef FIO_MAP2_KEY_DESTROY -#define FIO_MAP2_KEY_DESTROY(o) -#define FIO_MAP2_KEY_DESTROY_SIMPLE 1 +#ifndef FIO_MAP_KEY_DESTROY +#define FIO_MAP_KEY_DESTROY(o) +#define FIO_MAP_KEY_DESTROY_SIMPLE 1 #endif -#ifndef FIO_MAP2_KEY_DISCARD -#define FIO_MAP2_KEY_DISCARD(o) +#ifndef FIO_MAP_KEY_DISCARD +#define FIO_MAP_KEY_DISCARD(o) #endif -/* FIO_MAP2_HASH_FN(key) - used instead of providing a hash value. */ -#ifndef FIO_MAP2_HASH_FN -#undef FIO_MAP2_RECALC_HASH +/* FIO_MAP_HASH_FN(key) - used instead of providing a hash value. */ +#ifndef FIO_MAP_HASH_FN +#undef FIO_MAP_RECALC_HASH #endif -/* FIO_MAP2_RECALC_HASH - if true, hash values won't be cached. */ -#ifndef FIO_MAP2_RECALC_HASH -#define FIO_MAP2_RECALC_HASH 0 +/* FIO_MAP_RECALC_HASH - if true, hash values won't be cached. */ +#ifndef FIO_MAP_RECALC_HASH +#define FIO_MAP_RECALC_HASH 0 #endif -#ifdef FIO_MAP2_VALUE_BSTR -#define FIO_MAP2_VALUE fio_str_info_s -#define FIO_MAP2_VALUE_INTERNAL char * -#define FIO_MAP2_VALUE_FROM_INTERNAL(v) fio_bstr_info((v)) -#define FIO_MAP2_VALUE_COPY(dest, src) \ +#ifdef FIO_MAP_VALUE_BSTR +#define FIO_MAP_VALUE fio_str_info_s +#define FIO_MAP_VALUE_INTERNAL char * +#define FIO_MAP_VALUE_FROM_INTERNAL(v) fio_bstr_info((v)) +#define FIO_MAP_VALUE_COPY(dest, src) \ (dest) = fio_bstr_write(NULL, (src).buf, (src).len) -#define FIO_MAP2_VALUE_DESTROY(v) fio_bstr_free((v)) -#define FIO_MAP2_VALUE_DISCARD(v) +#define FIO_MAP_VALUE_DESTROY(v) fio_bstr_free((v)) +#define FIO_MAP_VALUE_DISCARD(v) #endif -#ifdef FIO_MAP2_VALUE -#define FIO_MAP2_GET_T FIO_MAP2_VALUE +#ifdef FIO_MAP_VALUE +#define FIO_MAP_GET_T FIO_MAP_VALUE #else -#define FIO_MAP2_GET_T FIO_MAP2_KEY +#define FIO_MAP_GET_T FIO_MAP_KEY #endif -#ifndef FIO_MAP2_VALUE_INTERNAL -#define FIO_MAP2_VALUE_INTERNAL FIO_MAP2_VALUE +#ifndef FIO_MAP_VALUE_INTERNAL +#define FIO_MAP_VALUE_INTERNAL FIO_MAP_VALUE #endif -#ifndef FIO_MAP2_VALUE_FROM_INTERNAL -#ifdef FIO_MAP2_VALUE -#define FIO_MAP2_VALUE_FROM_INTERNAL(o) o +#ifndef FIO_MAP_VALUE_FROM_INTERNAL +#ifdef FIO_MAP_VALUE +#define FIO_MAP_VALUE_FROM_INTERNAL(o) o #else -#define FIO_MAP2_VALUE_FROM_INTERNAL(o) +#define FIO_MAP_VALUE_FROM_INTERNAL(o) #endif #endif -#ifndef FIO_MAP2_VALUE_COPY -#ifdef FIO_MAP2_VALUE -#define FIO_MAP2_VALUE_COPY(dest, src) (dest) = (src) +#ifndef FIO_MAP_VALUE_COPY +#ifdef FIO_MAP_VALUE +#define FIO_MAP_VALUE_COPY(dest, src) (dest) = (src) #else -#define FIO_MAP2_VALUE_COPY(dest, src) +#define FIO_MAP_VALUE_COPY(dest, src) #endif #endif -#ifndef FIO_MAP2_VALUE_DESTROY -#define FIO_MAP2_VALUE_DESTROY(o) -#define FIO_MAP2_VALUE_DESTROY_SIMPLE 1 +#ifndef FIO_MAP_VALUE_DESTROY +#define FIO_MAP_VALUE_DESTROY(o) +#define FIO_MAP_VALUE_DESTROY_SIMPLE 1 #endif -#ifndef FIO_MAP2_VALUE_DISCARD -#define FIO_MAP2_VALUE_DISCARD(o) +#ifndef FIO_MAP_VALUE_DISCARD +#define FIO_MAP_VALUE_DISCARD(o) #endif -#ifdef FIO_MAP2_LRU -#undef FIO_MAP2_ORDERED -#define FIO_MAP2_ORDERED 1 /* required for least recently used order */ +#ifdef FIO_MAP_LRU +#undef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 1 /* required for least recently used order */ #endif -/* test if FIO_MAP2_ORDERED was defined as an empty macro */ -#if defined(FIO_MAP2_ORDERED) && ((0 - FIO_MAP2_ORDERED - 1) == 1) -#undef FIO_MAP2_ORDERED -#define FIO_MAP2_ORDERED 1 /* assume developer's intention */ +/* test if FIO_MAP_ORDERED was defined as an empty macro */ +#if defined(FIO_MAP_ORDERED) && ((0 - FIO_MAP_ORDERED - 1) == 1) +#undef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 1 /* assume developer's intention */ #endif -#ifndef FIO_MAP2_ORDERED -#define FIO_MAP2_ORDERED 0 +#ifndef FIO_MAP_ORDERED +#define FIO_MAP_ORDERED 0 #endif /* ***************************************************************************** @@ -28241,39 +27910,45 @@ Pointer Tagging Support ***************************************************************************** */ #ifdef FIO_PTR_TAG_TYPE -#define FIO_MAP2_PTR FIO_PTR_TAG_TYPE +#define FIO_MAP_PTR FIO_PTR_TAG_TYPE #else -#define FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, s) * +#define FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, s) * #endif -#define FIO_MAP2_T FIO_NAME(FIO_MAP2_NAME, s) +#define FIO_MAP_T FIO_NAME(FIO_MAP_NAME, s) /* ***************************************************************************** Map Types ***************************************************************************** */ +#ifndef FIO_MAP_INIT +/* Initialization macro. */ +#define FIO_MAP_INIT \ + { 0 } +#endif + /** internal object data representation */ -typedef struct FIO_NAME(FIO_MAP2_NAME, node_s) FIO_NAME(FIO_MAP2_NAME, node_s); +typedef struct FIO_NAME(FIO_MAP_NAME, node_s) FIO_NAME(FIO_MAP_NAME, node_s); /** A Hash Map / Set type */ -typedef struct FIO_NAME(FIO_MAP2_NAME, s) { +typedef struct FIO_NAME(FIO_MAP_NAME, s) { uint32_t bits; uint32_t count; - FIO_NAME(FIO_MAP2_NAME, node_s) * map; -#if FIO_MAP2_ORDERED + FIO_NAME(FIO_MAP_NAME, node_s) * map; +#if FIO_MAP_ORDERED FIO_INDEXED_LIST32_HEAD head; #endif -} FIO_NAME(FIO_MAP2_NAME, s); +} FIO_NAME(FIO_MAP_NAME, s); /** internal object data representation */ -struct FIO_NAME(FIO_MAP2_NAME, node_s) { -#if !FIO_MAP2_RECALC_HASH +struct FIO_NAME(FIO_MAP_NAME, node_s) { +#if !FIO_MAP_RECALC_HASH uint64_t hash; #endif - FIO_MAP2_KEY_INTERNAL key; -#ifdef FIO_MAP2_VALUE - FIO_MAP2_VALUE_INTERNAL value; + FIO_MAP_KEY_INTERNAL key; +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL value; #endif -#if FIO_MAP2_ORDERED +#if FIO_MAP_ORDERED FIO_INDEXED_LIST32_NODE node; #endif }; @@ -28281,14 +27956,14 @@ struct FIO_NAME(FIO_MAP2_NAME, node_s) { /** Map iterator type */ typedef struct { /** the node in the internal map */ - FIO_NAME(FIO_MAP2_NAME, node_s) * node; + FIO_NAME(FIO_MAP_NAME, node_s) * node; /** the key in the current position */ - FIO_MAP2_KEY key; -#ifdef FIO_MAP2_VALUE + FIO_MAP_KEY key; +#ifdef FIO_MAP_VALUE /** the value in the current position */ - FIO_MAP2_VALUE value; + FIO_MAP_VALUE value; #endif -#if !FIO_MAP2_RECALC_HASH +#if !FIO_MAP_RECALC_HASH /** the hash for the current position */ uint64_t hash; #endif @@ -28297,13 +27972,7 @@ typedef struct { uint32_t pos; /* the position in the ordering scheme */ uintptr_t map_validator; /* map mutation guard */ } private_; -} FIO_NAME(FIO_MAP2_NAME, iterator_s); - -#ifndef FIO_MAP2_INIT -/* Initialization macro. */ -#define FIO_MAP2_INIT \ - { 0 } -#endif +} FIO_NAME(FIO_MAP_NAME, iterator_s); /* ***************************************************************************** Construction / Deconstruction @@ -28313,53 +27982,52 @@ Construction / Deconstruction #ifndef FIO_REF_CONSTRUCTOR_ONLY /* Allocates a new object on the heap and initializes it's memory. */ -SFUNC FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, new)(void); +SFUNC FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, new)(void); /* Frees any internal data AND the object's container! */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, free)(FIO_MAP2_PTR map); +SFUNC void FIO_NAME(FIO_MAP_NAME, free)(FIO_MAP_PTR map); #endif /* FIO_REF_CONSTRUCTOR_ONLY */ -/** Destroys the object, reinitializing its container. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, destroy)(FIO_MAP2_PTR map); +/** Destroys the object, re-initializing its container. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, destroy)(FIO_MAP_PTR map); /* ***************************************************************************** Map State ***************************************************************************** */ /** Theoretical map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, capa)(FIO_MAP2_PTR map); +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, capa)(FIO_MAP_PTR map); /** The number of objects in the map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, count)(FIO_MAP2_PTR map); +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, count)(FIO_MAP_PTR map); /** Reserves at minimum the capacity requested. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, reserve)(FIO_MAP2_PTR map, size_t capa); +SFUNC void FIO_NAME(FIO_MAP_NAME, reserve)(FIO_MAP_PTR map, size_t capa); /** Returns the key value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, - node2key)(FIO_NAME(FIO_MAP2_NAME, node_s) * - node); +FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, + node2key)(FIO_NAME(FIO_MAP_NAME, node_s) * node); /** Returns the hash value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, - node2hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * node); +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, + node2hash)(FIO_NAME(FIO_MAP_NAME, node_s) * node); -#ifdef FIO_MAP2_VALUE +#ifdef FIO_MAP_VALUE /** Returns the value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP2_VALUE FIO_NAME(FIO_MAP2_NAME, - node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * - node); +FIO_IFUNC FIO_MAP_VALUE FIO_NAME(FIO_MAP_NAME, + node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * + node); #endif /** Returns the key value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2key_ptr)( - FIO_NAME(FIO_MAP2_NAME, node_s) * node); +FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node); -#ifdef FIO_MAP2_VALUE +#ifdef FIO_MAP_VALUE /** Returns the value associated with the node's pointer (see set_ptr). */ -FIO_IFUNC FIO_MAP2_VALUE_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( - FIO_NAME(FIO_MAP2_NAME, node_s) * node); +FIO_IFUNC FIO_MAP_VALUE_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node); #endif /* ***************************************************************************** @@ -28367,60 +28035,58 @@ Adding / Removing Elements from the Map ***************************************************************************** */ /** Removes an object in the map, returning a pointer to the map data. */ -SFUNC int FIO_NAME(FIO_MAP2_NAME, remove)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +SFUNC int FIO_NAME(FIO_MAP_NAME, remove)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif - FIO_MAP2_KEY key, -#ifdef FIO_MAP2_VALUE - FIO_MAP2_VALUE_INTERNAL *old + FIO_MAP_KEY key, +#if defined(FIO_MAP_VALUE) + FIO_MAP_VALUE_INTERNAL *old #else - FIO_MAP2_KEY_INTERNAL *old + FIO_MAP_KEY_INTERNAL *old #endif ); /** Evicts elements in order least recently used (LRU), FIFO or undefined. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, evict)(FIO_MAP2_PTR map, - size_t number_of_elements); +SFUNC void FIO_NAME(FIO_MAP_NAME, evict)(FIO_MAP_PTR map, + size_t number_of_elements); /** Removes all objects from the map, without releasing the map's resources. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, clear)(FIO_MAP2_PTR map); +SFUNC void FIO_NAME(FIO_MAP_NAME, clear)(FIO_MAP_PTR map); /** Attempts to minimize memory use. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, compact)(FIO_MAP2_PTR map); +SFUNC void FIO_NAME(FIO_MAP_NAME, compact)(FIO_MAP_PTR map); /** Gets a value from the map, if exists. */ -FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, get)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, get)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif - FIO_MAP2_KEY key); + FIO_MAP_KEY key); /** Sets a value in the map, hash maps will overwrite existing data if any. */ -FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, - set)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif -#ifdef FIO_MAP2_VALUE - FIO_MAP2_KEY key, - FIO_MAP2_VALUE obj, - FIO_MAP2_VALUE_INTERNAL *old +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE obj, + FIO_MAP_VALUE_INTERNAL *old #else - FIO_MAP2_KEY key + FIO_MAP_KEY key #endif ); /** Sets a value in the map if not set previously. */ -FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, - set_if_missing)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set_if_missing)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif - FIO_MAP2_KEY key -#ifdef FIO_MAP2_VALUE - , - FIO_MAP2_VALUE obj + FIO_MAP_KEY key +#ifdef FIO_MAP_VALUE + , + FIO_MAP_VALUE obj #endif ); @@ -28435,18 +28101,18 @@ FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, * * NOTE: the function returns a pointer to the map's internal storage. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * - FIO_NAME(FIO_MAP2_NAME, set_ptr)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, set_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif -#ifdef FIO_MAP2_VALUE - FIO_MAP2_KEY key, - FIO_MAP2_VALUE val, - FIO_MAP2_VALUE_INTERNAL *old, - int overwrite +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE val, + FIO_MAP_VALUE_INTERNAL *old, + int overwrite #else - FIO_MAP2_KEY key + FIO_MAP_KEY key #endif ); @@ -28455,16 +28121,50 @@ SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * * * NOTE: the function returns a pointer to the map's internal storage. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * - FIO_NAME(FIO_MAP2_NAME, get_ptr)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, get_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif - FIO_MAP2_KEY key); + FIO_MAP_KEY key); /* ***************************************************************************** Map Iteration and Traversal ***************************************************************************** */ +/** Iteration information structure passed to the callback. */ +typedef struct FIO_NAME(FIO_MAP_NAME, each_s) { + /** The being iterated. Once set, cannot be safely changed. */ + FIO_MAP_PTR const parent; + /** The current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct FIO_NAME(FIO_MAP_NAME, each_s) * info); + /** Opaque user data. */ + void *udata; +#ifdef FIO_MAP_VALUE + /** The object's value at the current index. */ + FIO_MAP_VALUE value; +#endif + /** The object's key the current index. */ + FIO_MAP_KEY key; +} FIO_NAME(FIO_MAP_NAME, each_s); + +/** + * Iteration using a callback for each element in the map. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +SFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + each)(FIO_MAP_PTR map, + int (*task)(FIO_NAME(FIO_MAP_NAME, each_s) *), + void *udata, + ssize_t start_at); + /** * Returns the next iterator object after `current_pos` or the first if `NULL`. * @@ -28476,87 +28176,56 @@ Map Iteration and Traversal * iterator object completely as the ordering may have changed and so the "next" * object might be any object in the map. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) - FIO_NAME(FIO_MAP2_NAME, - get_next)(FIO_MAP2_PTR map, - FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos); +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, + get_next)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos); /** * Returns the next iterator object after `current_pos` or the last if `NULL`. * * See notes in `get_next`. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) - FIO_NAME(FIO_MAP2_NAME, - get_prev)(FIO_MAP2_PTR map, - FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos); +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, + get_prev)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos); /** Returns 1 if the iterator is out of bounds, otherwise returns 0. */ -FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, - iterator_is_valid)(FIO_NAME(FIO_MAP2_NAME, iterator_s) * +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator); /** Returns a pointer to the node object in the internal map. */ -FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * - FIO_NAME(FIO_MAP2_NAME, - iterator2node)(FIO_MAP2_PTR map, - FIO_NAME(FIO_MAP2_NAME, iterator_s) * iterator); +FIO_IFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, + iterator2node)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator); + +#ifndef FIO_MAP_EACH -#ifndef FIO_MAP2_EACH /** Iterates through the map using an iterator object. */ -#define FIO_MAP2_EACH(map_name, map_ptr, i) \ +#define FIO_MAP_EACH(map_name, map_ptr, i) \ for (FIO_NAME(map_name, iterator_s) \ i = FIO_NAME(map_name, get_next)(map_ptr, NULL); \ FIO_NAME(map_name, iterator_is_valid)(&i); \ i = FIO_NAME(map_name, get_next)(map_ptr, &i)) + /** Iterates through the map using an iterator object. */ -#define FIO_MAP2_EACH_REVERSED(map_name, map_ptr, i) \ +#define FIO_MAP_EACH_REVERSED(map_name, map_ptr, i) \ for (FIO_NAME(map_name, iterator_s) \ i = FIO_NAME(map_name, get_prev)(map_ptr, NULL); \ FIO_NAME(map_name, iterator_is_valid)(&i); \ i = FIO_NAME(map_name, get_prev)(map_ptr, &i)) -#endif - -/** Iteration information structure passed to the callback. */ -typedef struct FIO_NAME(FIO_MAP2_NAME, each_s) { - /** The being iterated. Once set, cannot be safely changed. */ - FIO_MAP2_PTR const parent; - /** The current object's index */ - uint64_t index; - /** The callback / task called for each index, may be updated mid-cycle. */ - int (*task)(struct FIO_NAME(FIO_MAP2_NAME, each_s) * info); - /** Opaque user data. */ - void *udata; -#ifdef FIO_MAP2_VALUE - /** The object's value at the current index. */ - FIO_MAP2_VALUE value; -#endif - /** The object's key the current index. */ - FIO_MAP2_KEY key; -} FIO_NAME(FIO_MAP2_NAME, each_s); -/** - * Iteration using a callback for each element in the map. - * - * The callback task function must accept an each_s pointer, see above. - * - * If the callback returns -1, the loop is broken. Any other value is ignored. - * - * Returns the relative "stop" position, i.e., the number of items processed + - * the starting point. - */ -SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, - each)(FIO_MAP2_PTR map, - int (*task)(FIO_NAME(FIO_MAP2_NAME, each_s) *), - void *udata, - ssize_t start_at); +#endif /* FIO_MAP_EACH */ /* ***************************************************************************** Optional Sorting Support - TODO? (convert to array, sort, rehash) ***************************************************************************** */ -#if defined(FIO_MAP2_KEY_IS_GREATER_THAN) && !defined(FIO_SORT_TYPE) && \ - FIO_MAP2_ORDERED +#if defined(FIO_MAP_KEY_IS_GREATER_THAN) && !defined(FIO_SORT_TYPE) && \ + FIO_MAP_ORDERED #undef FIO_SORT_NAME #endif @@ -28564,52 +28233,52 @@ Optional Sorting Support - TODO? (convert to array, sort, rehash) Map Implementation - inlined static functions ***************************************************************************** */ -#ifndef FIO_MAP2_CAPA_BITS_LIMIT +#ifndef FIO_MAP_CAPA_BITS_LIMIT /* Note: cannot be more than 31 bits unless some of the code is rewritten. */ -#define FIO_MAP2_CAPA_BITS_LIMIT 31 +#define FIO_MAP_CAPA_BITS_LIMIT 31 #endif /* Theoretical map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, capa)(FIO_MAP2_PTR map) { +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, capa)(FIO_MAP_PTR map) { FIO_PTR_TAG_VALID_OR_RETURN(map, 0); - FIO_MAP2_T *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + FIO_MAP_T *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); if (o->map) return (uint32_t)((size_t)1ULL << o->bits); return 0; } /* The number of objects in the map capacity. */ -FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, count)(FIO_MAP2_PTR map) { +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, count)(FIO_MAP_PTR map) { FIO_PTR_TAG_VALID_OR_RETURN(map, 0); - return ((FIO_NAME(FIO_MAP2_NAME, s) *)FIO_PTR_UNTAG(map))->count; + return FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map)->count; } /** Returns 1 if the iterator points to a valid object, otherwise returns 0. */ -FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, - iterator_is_valid)(FIO_NAME(FIO_MAP2_NAME, iterator_s) * +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator) { return (iterator && iterator->private_.map_validator); } /** Returns the key value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, - node2key)(FIO_NAME(FIO_MAP2_NAME, node_s) * - node) { - FIO_MAP2_KEY r = (FIO_MAP2_KEY){0}; +FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, + node2key)(FIO_NAME(FIO_MAP_NAME, node_s) * + node) { + FIO_MAP_KEY r = (FIO_MAP_KEY){0}; if (!node) return r; - return FIO_MAP2_KEY_FROM_INTERNAL(node->key); + return FIO_MAP_KEY_FROM_INTERNAL(node->key); } /** Returns the hash value associated with the node's pointer. */ -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, - node2hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * node) { +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, + node2hash)(FIO_NAME(FIO_MAP_NAME, node_s) * node) { uint32_t r = (uint32_t){0}; if (!node) return r; -#if FIO_MAP2_RECALC_HASH - FIO_MAP2_KEY k = FIO_MAP2_KEY_FROM_INTERNAL(node->key); - uint64_t hash = FIO_MAP2_HASH_FN(k); +#if FIO_MAP_RECALC_HASH + FIO_MAP_KEY k = FIO_MAP_KEY_FROM_INTERNAL(node->key); + uint64_t hash = FIO_MAP_HASH_FN(k); hash += !hash; return hash; #else @@ -28617,129 +28286,128 @@ FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, #endif } -#ifdef FIO_MAP2_VALUE +#ifdef FIO_MAP_VALUE /** Returns the value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP2_VALUE FIO_NAME(FIO_MAP2_NAME, - node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * - node) { - FIO_MAP2_VALUE r = (FIO_MAP2_VALUE){0}; +FIO_IFUNC FIO_MAP_VALUE FIO_NAME(FIO_MAP_NAME, + node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * + node) { + FIO_MAP_VALUE r = (FIO_MAP_VALUE){0}; if (!node) return r; - return FIO_MAP2_VALUE_FROM_INTERNAL(node->value); + return FIO_MAP_VALUE_FROM_INTERNAL(node->value); } #else -/* If called for a node without a value, returns the key (simplifies stuff). */ -FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, - node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * - node) { - return FIO_NAME(FIO_MAP2_NAME, node2key)(node); +/* If called for a node without a + * value, returns the key (simplifies + * stuff). */ +FIO_IFUNC FIO_MAP_KEY FIO_NAME(FIO_MAP_NAME, + node2val)(FIO_NAME(FIO_MAP_NAME, node_s) * + node) { + return FIO_NAME(FIO_MAP_NAME, node2key)(node); } #endif /** Returns the key value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2key_ptr)( - FIO_NAME(FIO_MAP2_NAME, node_s) * node) { +FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node) { if (!node) return NULL; return &(node->key); } -#ifdef FIO_MAP2_VALUE +#ifdef FIO_MAP_VALUE /** Returns the value associated with the node's pointer. */ -FIO_IFUNC FIO_MAP2_VALUE_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( - FIO_NAME(FIO_MAP2_NAME, node_s) * node) { +FIO_IFUNC FIO_MAP_VALUE_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node) { if (!node) return NULL; return &(node->value); } #else -/* If called for a node without a value, returns the key (simplifies stuff). */ -FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( - FIO_NAME(FIO_MAP2_NAME, node_s) * node) { - return FIO_NAME(FIO_MAP2_NAME, node2key_ptr)(node); +/* If called for a node without a + * value, returns the key (simplifies + * stuff). */ +FIO_IFUNC FIO_MAP_KEY_INTERNAL *FIO_NAME(FIO_MAP_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP_NAME, node_s) * node) { + return FIO_NAME(FIO_MAP_NAME, node2key_ptr)(node); } #endif /** Gets a value from the map, if exists. */ -FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, get)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, get)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif - FIO_MAP2_KEY key) { - return FIO_NAME(FIO_MAP2_NAME, - node2val)(FIO_NAME(FIO_MAP2_NAME, get_ptr)(map, -#if !defined(FIO_MAP2_HASH_FN) - hash, + FIO_MAP_KEY key) { + return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, get_ptr)(map, +#if !defined(FIO_MAP_HASH_FN) + hash, #endif - key)); + key)); } /** Sets a value in the map, hash maps will overwrite existing data if any. */ -FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, - set)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif -#ifdef FIO_MAP2_VALUE - FIO_MAP2_KEY key, - FIO_MAP2_VALUE obj, - FIO_MAP2_VALUE_INTERNAL *old +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE obj, + FIO_MAP_VALUE_INTERNAL *old #else - FIO_MAP2_KEY key + FIO_MAP_KEY key #endif ) { - return FIO_NAME(FIO_MAP2_NAME, - node2val)(FIO_NAME(FIO_MAP2_NAME, set_ptr)(map, -#if !defined(FIO_MAP2_HASH_FN) - hash, + return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, set_ptr)(map, +#if !defined(FIO_MAP_HASH_FN) + hash, #endif - key -#ifdef FIO_MAP2_VALUE - , - obj, - old, - 1 + key +#ifdef FIO_MAP_VALUE + , + obj, + old, + 1 #endif - )); + )); } /** Sets a value in the map if not set previously. */ -FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, - set_if_missing)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC FIO_MAP_GET_T FIO_NAME(FIO_MAP_NAME, set_if_missing)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif - FIO_MAP2_KEY key -#ifdef FIO_MAP2_VALUE - , - FIO_MAP2_VALUE obj + FIO_MAP_KEY key +#ifdef FIO_MAP_VALUE + , + FIO_MAP_VALUE obj #endif ) { - return FIO_NAME(FIO_MAP2_NAME, - node2val)(FIO_NAME(FIO_MAP2_NAME, set_ptr)(map, -#if !defined(FIO_MAP2_HASH_FN) - hash, + return FIO_NAME(FIO_MAP_NAME, node2val)(FIO_NAME(FIO_MAP_NAME, set_ptr)(map, +#if !defined(FIO_MAP_HASH_FN) + hash, #endif - key -#ifdef FIO_MAP2_VALUE - , - obj, - NULL, - 0 + key +#ifdef FIO_MAP_VALUE + , + obj, + NULL, + 0 #endif - )); + )); } /** Returns a pointer to the node object in the internal map. */ -FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * - FIO_NAME(FIO_MAP2_NAME, - iterator2node)(FIO_MAP2_PTR map, - FIO_NAME(FIO_MAP2_NAME, iterator_s) * iterator) { - FIO_NAME(FIO_MAP2_NAME, node_s) *node = NULL; +FIO_IFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, + iterator2node)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * iterator) { + FIO_NAME(FIO_MAP_NAME, node_s) *node = NULL; if (!iterator || !iterator->private_.map_validator) return node; FIO_PTR_TAG_VALID_OR_RETURN(map, node); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + FIO_NAME(FIO_MAP_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); node = o->map + iterator->private_.index; return node; } @@ -28749,83 +28417,116 @@ Map Implementation - possibly externed functions. ***************************************************************************** */ #if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP2_NAME, s)) -FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP2_NAME, destroy)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP_NAME, destroy)) /* ***************************************************************************** Constructors ***************************************************************************** */ /* do we have a constructor? */ #ifndef FIO_REF_CONSTRUCTOR_ONLY +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP_NAME, s)) /* Allocates a new object on the heap and initializes it's memory. */ -FIO_IFUNC FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, new)(void) { - FIO_NAME(FIO_MAP2_NAME, s) *o = - (FIO_NAME(FIO_MAP2_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); +FIO_IFUNC FIO_MAP_PTR FIO_NAME(FIO_MAP_NAME, new)(void) { + FIO_NAME(FIO_MAP_NAME, s) *o = + (FIO_NAME(FIO_MAP_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); if (!o) - return (FIO_MAP2_PTR)NULL; - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP2_NAME, s)); - *o = (FIO_NAME(FIO_MAP2_NAME, s))FIO_MAP2_INIT; - return (FIO_MAP2_PTR)FIO_PTR_TAG(o); + return (FIO_MAP_PTR)NULL; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP_NAME, s)); + *o = (FIO_NAME(FIO_MAP_NAME, s))FIO_MAP_INIT; + return (FIO_MAP_PTR)FIO_PTR_TAG(o); } /* Frees any internal data AND the object's container! */ -FIO_IFUNC void FIO_NAME(FIO_MAP2_NAME, free)(FIO_MAP2_PTR map) { +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, free)(FIO_MAP_PTR map) { FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP2_NAME, destroy)(map); - FIO_NAME(FIO_MAP2_NAME, s) *o = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP2_NAME, s), map); - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP2_NAME, s)); + FIO_NAME(FIO_MAP_NAME, destroy)(map); + FIO_NAME(FIO_MAP_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP_NAME, s), map); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP_NAME, s)); FIO_MEM_FREE_(o, sizeof(*o)); } #endif /* FIO_REF_CONSTRUCTOR_ONLY */ /* ***************************************************************************** -Internal Helpers + + + + +Internal Helpers (Core) + + + + ***************************************************************************** */ -#ifndef FIO_MAP2_ATTACK_LIMIT -#define FIO_MAP2_ATTACK_LIMIT 16 +/** internal object data representation */ +struct FIO_NAME(FIO_MAP_NAME, __imap_s) { + uint8_t h[64]; +}; + +#ifndef FIO_MAP_ATTACK_LIMIT +#define FIO_MAP_ATTACK_LIMIT 16 #endif -#ifndef FIO_MAP2_CUCKOO_STEPS -/* Prime numbers are better */ -#define FIO_MAP2_CUCKOO_STEPS (0x43F82D0BUL) /* a big high prime */ +#ifndef FIO_MAP_MINIMAL_BITS +#define FIO_MAP_MINIMAL_BITS 1 #endif -#ifndef FIO_MAP2_SEEK_LIMIT -#define FIO_MAP2_SEEK_LIMIT 13U +#ifndef FIO_MAP_CUCKOO_STEPS +/* Prime numbers are better */ +#define FIO_MAP_CUCKOO_STEPS (0x43F82D0BUL) /* a big high prime */ #endif -#ifndef FIO_MAP2_ARRAY_LOG_LIMIT -#define FIO_MAP2_ARRAY_LOG_LIMIT 3 +#ifndef FIO_MAP_SEEK_LIMIT +#define FIO_MAP_SEEK_LIMIT 13U #endif -#ifndef FIO_MAP2_CAPA -#define FIO_MAP2_CAPA(bits) ((size_t)1ULL << (bits)) +#ifndef FIO_MAP_ARRAY_LOG_LIMIT +#define FIO_MAP_ARRAY_LOG_LIMIT 3 +#endif +#ifndef FIO_MAP_CAPA +#define FIO_MAP_CAPA(bits) ((size_t)1ULL << (bits)) #endif -#ifndef FIO_MAP2_IS_SPARSE -#define FIO_MAP2_IS_SPARSE(map) \ - (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT && ((capa >> 2) > o->count)) +#ifndef FIO_MAP_IS_SPARSE +#define FIO_MAP_IS_SPARSE(map) \ + (o->bits > FIO_MAP_ARRAY_LOG_LIMIT && ((capa >> 2) > o->count)) #endif +/* Allocates resources for a new (clean) map. */ +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + __allocate_map)(FIO_NAME(FIO_MAP_NAME, s) * o, + uint32_t bits) { + if (bits < FIO_MAP_MINIMAL_BITS) + bits = FIO_MAP_MINIMAL_BITS; + if (bits > FIO_MAP_CAPA_BITS_LIMIT) + return -1; + size_t s = (sizeof(o->map[0]) + 1) << bits; + FIO_NAME(FIO_MAP_NAME, node_s) *n = + (FIO_NAME(FIO_MAP_NAME, node_s) *)FIO_MEM_REALLOC_(NULL, 0, s, 0); + if (!n) + return -1; + if (!FIO_MEM_REALLOC_IS_SAFE_) /* set only imap to zero */ + FIO_MEMSET((n + (1ULL << bits)), 0, (1ULL << bits)); + *o = (FIO_NAME(FIO_MAP_NAME, s)){.map = n, .bits = bits}; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP_NAME, destroy)); + return 0; +} + /* The number of objects in the map capacity. */ -FIO_IFUNC uint8_t *FIO_NAME(FIO_MAP2_NAME, - __imap)(FIO_NAME(FIO_MAP2_NAME, s) * o) { - // FIO_ASSERT(o && o->map, "shouldn't have been called."); - return (uint8_t *)(o->map + FIO_MAP2_CAPA(o->bits)); +FIO_IFUNC uint8_t *FIO_NAME(FIO_MAP_NAME, + __imap)(FIO_NAME(FIO_MAP_NAME, s) const *o) { + return (uint8_t *)(o->map + FIO_MAP_CAPA(o->bits)); } -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, - __byte_hash)(FIO_NAME(FIO_MAP2_NAME, s) * o, - uint64_t hash) { - hash = (hash >> o->bits); +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, __byte_hash)(uint64_t hash) { + hash = (hash >> 48) ^ (hash >> 56); hash &= 0xFF; hash += !(hash); hash -= (hash == 255); return hash; } -FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, - __is_eq_hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * o, +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP_NAME, + __is_eq_hash)(FIO_NAME(FIO_MAP_NAME, node_s) * o, uint64_t hash) { -#if FIO_MAP2_RECALC_HASH && defined(FIO_MAP2_HASH_FN) - uint64_t khash = FIO_MAP2_HASH_FN(FIO_MAP2_KEY_FROM_INTERNAL(o->key)); +#if FIO_MAP_RECALC_HASH && defined(FIO_MAP_HASH_FN) + uint64_t khash = FIO_MAP_HASH_FN(FIO_MAP_KEY_FROM_INTERNAL(o->key)); khash += !khash; #else const uint64_t khash = o->hash; @@ -28833,899 +28534,1053 @@ FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, return (khash == hash); } -FIO_SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, - __index)(FIO_NAME(FIO_MAP2_NAME, s) * o, - FIO_MAP2_KEY key, - uint64_t hash) { - uint32_t r = (uint32_t)-1; - if (!o->map) - return r; - static int guard_print = 0; - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - size_t capa = FIO_MAP2_CAPA(o->bits); - size_t bhash = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(o, hash); - size_t guard = FIO_MAP2_ATTACK_LIMIT + 1; - if (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT) { /* treat as map */ - uint64_t bhash64 = bhash | (bhash << 8); - bhash64 |= bhash64 << 16; - bhash64 |= bhash64 << 32; - bhash64 = ~bhash64; - const uintptr_t pos_mask = capa - 1; - const uint_fast8_t offsets[8] = {0, 3, 8, 17, 28, 41, 58, 60}; - for (uintptr_t pos = hash, c = 0; c < FIO_MAP2_SEEK_LIMIT; - (pos += FIO_MAP2_CUCKOO_STEPS), ++c) { - uint64_t comb = imap[(pos + offsets[0]) & pos_mask]; - comb |= ((uint64_t)imap[(pos + offsets[1]) & pos_mask]) << (1 * 8); - comb |= ((uint64_t)imap[(pos + offsets[2]) & pos_mask]) << (2 * 8); - comb |= ((uint64_t)imap[(pos + offsets[3]) & pos_mask]) << (3 * 8); - comb |= ((uint64_t)imap[(pos + offsets[4]) & pos_mask]) << (4 * 8); - comb |= ((uint64_t)imap[(pos + offsets[5]) & pos_mask]) << (5 * 8); - comb |= ((uint64_t)imap[(pos + offsets[6]) & pos_mask]) << (6 * 8); - comb |= ((uint64_t)imap[(pos + offsets[7]) & pos_mask]) << (7 * 8); - const uint64_t has_possible_match = - (((comb ^ bhash64) & 0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & - 0x8080808080808080ULL; - if (has_possible_match) { - /* there was a 7 bit match in one of the bytes in this 8 byte group */ - for (size_t i = 0; i < 8; ++i) { - const uint32_t tmp = (uint32_t)((pos + offsets[i]) & pos_mask); - if (imap[tmp] != bhash) - continue; - /* test key and hash equality */ - if (FIO_NAME(FIO_MAP2_NAME, __is_eq_hash)(o->map + tmp, hash)) { - if (FIO_MAP2_KEY_CMP(o->map[tmp].key, key)) { - guard_print = 0; - return (r = tmp); - } - if (!(--guard)) { - if (!guard_print) - FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( - FIO_NAME(FIO_MAP2_NAME, s)) " under attack?"); - guard_print = 1; - return (r = tmp); - } - } - } - } - const uint64_t has_possible_full_byte = - (((comb)&0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & - 0x8080808080808080ULL; - const uint64_t has_possible_empty_byte = - (((~comb) & 0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & - 0x8080808080808080ULL; - if (!(has_possible_full_byte | has_possible_empty_byte)) - continue; - /* there was a 7 bit match for a possible free space in this group */ - for (int i = 0; i < 8; ++i) { - const uint32_t tmp = (uint32_t)((pos + offsets[i]) & pos_mask); - if (!imap[tmp]) - return (r = tmp); /* empty slot always ends search */ - if (r > pos_mask && imap[tmp] == 255) - r = tmp; /* mark hole to be filled */ - } - } - return r; - } /* treat as array */ - for (size_t i = 0; i < capa; ++i) { - if (!imap[i]) - return (r = (uint32_t)i); - if (imap[i] == bhash) { - /* test key and hash equality */ - if (FIO_NAME(FIO_MAP2_NAME, __is_eq_hash)(o->map + i, hash)) { - if (FIO_MAP2_KEY_CMP(o->map[i].key, key)) { - guard_print = 0; - return (r = (uint32_t)i); - } - if (!(--guard)) { - if (!guard_print) - FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( - FIO_NAME(FIO_MAP2_NAME, s)) " under attack?"); - guard_print = 1; - return (r = (uint32_t)i); - } - } - } - if (imap[i] == 0xFF) - r = (uint32_t)i; /* a free spot is available*/ - } - return r; -} -/* deallocate the map's memory. */ -FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, - __dealloc_map)(FIO_NAME(FIO_MAP2_NAME, s) * o) { - if (!o->map) - return; - const size_t capa = FIO_MAP2_CAPA(o->bits); - FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP2_NAME, destroy)); - FIO_MEM_FREE_(o->map, (capa * sizeof(*o->map)) + capa); - (void)capa; -} +typedef struct FIO_NAME(FIO_MAP_NAME, __each_node_s) { + FIO_NAME(FIO_MAP_NAME, s) * map; + FIO_NAME(FIO_MAP_NAME, node_s) * node; + int (*fn)(struct FIO_NAME(FIO_MAP_NAME, __each_node_s) *); + void *udata; +} FIO_NAME(FIO_MAP_NAME, __each_node_s); -/** duplicates an objects between two maps. */ -FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, - __copy_obj)(FIO_NAME(FIO_MAP2_NAME, s) * dest, - FIO_NAME(FIO_MAP2_NAME, node_s) * o, - uint32_t internal) { - FIO_MAP2_KEY key = FIO_MAP2_KEY_FROM_INTERNAL(o->key); - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(dest); -#if FIO_MAP2_RECALC_HASH - uint64_t ohash = FIO_MAP2_HASH_FN(key); - ohash += !ohash; -#else - const uint64_t ohash = o->hash; -#endif - uint32_t i = FIO_NAME(FIO_MAP2_NAME, __index)(dest, key, ohash); - if (i == (uint32_t)-1 || (imap[i] + 1) > 1) - return -1; - if (internal) { - dest->map[i] = *o; - imap[i] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(dest, ohash); -#if FIO_MAP2_ORDERED - if (dest->count) { /* update ordering */ - FIO_INDEXED_LIST_PUSH(dest->map, node, dest->head, i); - } else { /* set first order */ - dest->map[i].node.next = dest->map[i].node.prev = i; - dest->head = i; - } -#endif - ++dest->count; +/* perform task for each node. */ +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + __each_node)(FIO_NAME(FIO_MAP_NAME, s) * o, + int (*fn)(FIO_NAME(FIO_MAP_NAME, + __each_node_s) *), + void *udata) { + FIO_NAME(FIO_MAP_NAME, __each_node_s) + each = {.map = o, .fn = fn, .udata = udata}; + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + size_t counter = o->count; + if (!counter) return 0; +#if FIO_MAP_ORDERED + FIO_INDEXED_LIST_EACH(o->map, node, o->head, pos) { + each.node = o->map + pos; + if (each.fn(&each)) + return -1; + --counter; + if (FIO_UNLIKELY(imap != FIO_NAME(FIO_MAP_NAME, __imap)(o))) + return -1; } - imap[i] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(dest, ohash); - FIO_MAP2_KEY_COPY(dest->map[i].key, FIO_MAP2_KEY_FROM_INTERNAL(o->key)); - FIO_MAP2_VALUE_COPY(dest->map[i].value, - FIO_MAP2_VALUE_FROM_INTERNAL(o->value)); -#if !FIO_MAP2_RECALC_HASH - dest->map[i].hash = o->hash; -#endif -#if FIO_MAP2_ORDERED - if (dest->count) { /* update ordering */ - FIO_INDEXED_LIST_PUSH(dest->map, node, dest->head, i); - } else { /* set first order */ - dest->map[i].node.next = dest->map[i].node.prev = i; - dest->head = i; +#else + const size_t len = FIO_MAP_CAPA(o->bits); + if (FIO_UNLIKELY(o->bits > 5 && (FIO_MAP_CAPA(o->bits) >> 2) > o->count)) + goto sparse_map; + for (size_t i = 0; counter; ++i) { + if (!imap[i] || imap[i] == 255) + continue; + each.node = o->map + i; + if (FIO_UNLIKELY(each.fn(&each))) + return -1; + --counter; + if (FIO_UNLIKELY(imap != FIO_NAME(FIO_MAP_NAME, __imap)(o))) + return -1; } -#endif - ++dest->count; + FIO_ASSERT_DEBUG( + !counter, + "detected error while looping over all elements in map (%zu/%zu)", + counter, + (size_t)FIO_MAP_CAPA(o->bits)); return 0; -} -/** duplicates a map to a new copy (usually for rehashing / reserving space). */ -FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, s) - FIO_NAME(FIO_MAP2_NAME, __duplicate)(FIO_NAME(FIO_MAP2_NAME, s) * o, - uint32_t bits, - uint32_t internal) { - FIO_NAME(FIO_MAP2_NAME, s) cpy = {0}; - if (bits > FIO_MAP2_CAPA_BITS_LIMIT) - return cpy; - size_t capa = FIO_MAP2_CAPA(bits); - cpy.map = (FIO_NAME(FIO_MAP2_NAME, node_s) *) - FIO_MEM_REALLOC_(NULL, 0, ((capa * sizeof(*cpy.map)) + capa), 0); - if (!cpy.map) - return cpy; - FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP2_NAME, destroy)); - if (!FIO_MEM_REALLOC_IS_SAFE_) { - /* set only the imap, the rest can be junk data */ - FIO_MEMSET((cpy.map + capa), 0, capa); - } - cpy.bits = bits; - if (!o->count) - return cpy; -#if FIO_MAP2_ORDERED - /* copy objects in order */ - FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { - if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + i, internal)) - goto error; - } -#else - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - capa = FIO_MAP2_CAPA(o->bits); - if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ - for (size_t i = 0; i < capa; i += 8) { - uint64_t comb = *((uint64_t *)(imap + i)); - if (!comb || comb == 0xFFFFFFFFFFFFFFFFULL) - continue; - for (size_t j = 0; j < 8; ++j) { - const size_t tmp = j + i; - if (!imap[tmp] || imap[tmp] == 0xFF) - continue; - if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + tmp, internal)) - goto error; +sparse_map: + for (size_t i = 0; counter && i < len; i += 64) { + uint64_t bitmap = 0; + for (size_t j = 0; j < 64; j += 8) { + uint64_t tmp = *((uint64_t *)(imap + i + j)); + uint64_t inv = ~tmp; + tmp = FIO_HAS_FULL_BYTE64(tmp); + inv = FIO_HAS_FULL_BYTE64(inv); + tmp |= inv; + FIO_HAS_BYTE2BITMAP(tmp, 7); + bitmap |= (tmp << j); + } + bitmap = ~bitmap; /* where 1 was a free slot, now it's an occupied one */ + for (size_t j = 0; bitmap; ++j) { + if ((bitmap & 1)) { + each.node = o->map + i + j; + if (each.fn(&each)) + return -1; + --counter; + if (imap != FIO_NAME(FIO_MAP_NAME, __imap)(o)) + return -1; } + bitmap >>= 1; } - return cpy; - } /* review as array */ - for (size_t i = 0; i < capa; ++i) { - if (!imap[i] || imap[i] == 0xFF) - continue; - if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + i, internal)) - goto error; } #endif - return cpy; -error: - FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(&cpy); - cpy = (FIO_NAME(FIO_MAP2_NAME, s)){0}; - return cpy; + FIO_ASSERT_DEBUG( + !counter, + "detected error while looping over all elements in map (%zu/%zu)", + counter, + (size_t)FIO_MAP_CAPA(o->bits)); + return 0; } -/* destroys all objects in the map, without(!) resetting the `imap`. */ -FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, - __destroy_objects)(FIO_NAME(FIO_MAP2_NAME, s) * o) { -#if FIO_MAP2_VALUE_DESTROY_SIMPLE && FIO_MAP2_KEY_DESTROY_SIMPLE - (void)o; - return; -#else - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - const size_t capa = FIO_MAP2_CAPA(o->bits); - if (FIO_MAP2_IS_SPARSE(o)) { - for (size_t i = 0; i < capa; i += 8) { - uint64_t comb = *((uint64_t *)(imap + i)); - if (!comb || comb == 0xFFFFFFFFFFFFFFFFULL) - continue; - for (size_t j = i; j < i + 8; ++j) { - FIO_MAP2_KEY_DESTROY(o->map[j].key); - FIO_MAP2_VALUE_DESTROY(o->map[j].value); - } - } - } else { /* review as array */ - for (size_t i = 0; i < capa; ++i) { - if (!imap[i] || imap[i] == 0xFF) - continue; - FIO_MAP2_KEY_DESTROY(o->map[i].key); - FIO_MAP2_VALUE_DESTROY(o->map[i].value); - } - } -#endif /* FIO_MAP2_VALUE_DESTROY_SIMPLE */ +#if !FIO_MAP_KEY_DESTROY_SIMPLE || !FIO_MAP_VALUE_DESTROY_SIMPLE +static int FIO_NAME(FIO_MAP_NAME, + __destroy_map_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * + e) { + FIO_MAP_KEY_DESTROY(e->node->key); + FIO_MAP_VALUE_DESTROY(e->node->value); + return 0; } +#endif -/* ***************************************************************************** -API implementation -***************************************************************************** */ +/* Destroys and exsiting map. */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, + __destroy_map)(FIO_NAME(FIO_MAP_NAME, s) * o, + _Bool should_zero) { +#if !FIO_MAP_KEY_DESTROY_SIMPLE || !FIO_MAP_VALUE_DESTROY_SIMPLE + FIO_NAME(FIO_MAP_NAME, __each_node) + (o, FIO_NAME(FIO_MAP_NAME, __destroy_map_task), NULL); +#endif + if (should_zero) /* set only imap to zero */ + FIO_MEMSET((o->map + (1ULL << o->bits)), 0, (1ULL << o->bits)); + o->count = 0; +} -/** Reserves at minimum the capacity requested. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, reserve)(FIO_MAP2_PTR map, size_t capa) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - capa += o->count; - if (FIO_MAP2_CAPA(o->bits) >= capa || (capa >> FIO_MAP2_CAPA_BITS_LIMIT)) - return; - uint_fast8_t bits = o->bits + 1; - while (FIO_MAP2_CAPA(bits) < capa) - ++bits; - FIO_NAME(FIO_MAP2_NAME, s) - cpy = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, bits, 1); - if (!cpy.map) +/* Destroys and exsiting map. */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, __free_map)(FIO_NAME(FIO_MAP_NAME, s) * o, + _Bool should_destroy) { + if (!o->map) return; - FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); - *o = cpy; + if (should_destroy) + FIO_NAME(FIO_MAP_NAME, __destroy_map)(o, 0); + FIO_MEM_FREE_(o->map, ((sizeof(o->map[0]) + 1) << o->bits)); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP_NAME, destroy)); + *o = (FIO_NAME(FIO_MAP_NAME, s)){0}; } -/* Removes all objects from the map, without releasing the map's resources. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, clear)(FIO_MAP2_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (!o->map || !o->count) - return; - FIO_NAME(FIO_MAP2_NAME, __destroy_objects)(o); - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - const size_t capa = FIO_MAP2_CAPA(o->bits); - FIO_MEMSET(imap, 0, capa); - o->count = 0; -#if FIO_MAP2_ORDERED - o->head = 0; +#ifndef H___FIO_MAP_INDEX_TYPE___H +#define H___FIO_MAP_INDEX_TYPE___H +typedef struct { + uint32_t home; + uint32_t act; + uint32_t alt; + uint32_t bhash; +} fio___map_node_info_s; #endif -} -/** Attempts to minimize memory use. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, compact)(FIO_MAP2_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (!o->map || !o->count) - return; - uint32_t bits = o->bits; - while (FIO_MAP2_CAPA(bits >> 1) > o->count) - bits >>= 1; - ++bits; - for (;;) { - if (bits >= o->bits) - return; - FIO_NAME(FIO_MAP2_NAME, s) - cpy = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, bits, 1); - if (!cpy.map) { - ++bits; - continue; +/** internal object data representation */ +typedef struct FIO_NAME(FIO_MAP_NAME, __o_node_s) { + uint64_t hash; + FIO_MAP_KEY key; +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE value; +#endif +} FIO_NAME(FIO_MAP_NAME, __o_node_s); + +/* seek a node for very small collections 8 item capacity at most */ +FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_mini)( + FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { + // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); + fio___map_node_info_s r = { + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; + if (!o->bits) + return r; + const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + const uint32_t capa = (uint32_t)FIO_MAP_CAPA(o->bits); + const uint32_t mask = capa - 1; + size_t pos = node->hash & 0xFF; + for (uint32_t i = 0; i < capa; ++i) { + pos = (pos + i) & mask; + if (imap[pos] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash) && + FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { + r.act = (uint32_t)pos; + return r; + } else if (!imap[pos]) { + r.alt = r.home = (uint32_t)pos; + return r; + } else if (imap[pos] == 255U) { /* "home" has been occupied before */ + r.alt = r.home = (uint32_t)pos; } - FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); - *o = cpy; - return; } + return r; } -/* Frees any internal data AND the object's container! */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, destroy)(FIO_MAP2_PTR map) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (o->map && o->count) - FIO_NAME(FIO_MAP2_NAME, __destroy_objects)(o); - FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); - *o = (FIO_NAME(FIO_MAP2_NAME, s))FIO_MAP2_INIT; - return; -} - -/** Evicts elements least recently used (LRU), FIFO or undefined. */ -SFUNC void FIO_NAME(FIO_MAP2_NAME, evict)(FIO_MAP2_PTR map, - size_t number_of_elements) { - FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (!o->count) - return; - if (number_of_elements >= o->count) { - FIO_NAME(FIO_MAP2_NAME, clear)(map); - return; +/* seek a node for medium sized collections, 16-512 item capacity. */ +FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_med)( + FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { + // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); + static int guard_print = 0; + fio___map_node_info_s r = { + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; + const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + const uint32_t mask = (uint32_t)(FIO_MAP_CAPA(o->bits) - 1); + uint32_t guard = FIO_MAP_ATTACK_LIMIT + 1; + uint32_t pos = r.home = (node->hash & mask); + uint32_t step = 2; + uint32_t attempts = (mask < 511) ? ((mask >> 2) | 8) : 127; + for (; r.alt == (uint32_t)-1 && attempts; --attempts) { + if (!imap[pos]) { + r.alt = pos; + return r; + } else if (imap[pos] == 255U) { /* "home" has been occupied before */ + r.alt = pos; + } else if (imap[pos] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { + r.act = pos; + return r; + } + if (!--guard) { + r.act = pos; + goto possible_attack; + } + } + pos = ((pos + (step++)) & mask); } - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); -#ifdef FIO_MAP2_LRU /* remove last X elements from the list */ - FIO_INDEXED_LIST_EACH_REVERSED(o->map, node, o->head, i) { - FIO_MAP2_KEY_DESTROY(o->map[i].key); - FIO_MAP2_VALUE_DESTROY(o->map[i].value); - FIO_INDEXED_LIST_REMOVE(o->map, node, i); - imap[i] = 0xFF; - --o->count; - if (!(--number_of_elements)) - return; + for (; attempts; --attempts) { + if (imap[pos] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + pos, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[pos].key, node->key)) { + r.act = pos; + return r; + } + if (!--guard) { + r.act = pos; + goto possible_attack; + } + } + if (!imap[pos]) + return r; + pos = ((pos + (step++)) & mask); } -#elif FIO_MAP2_ORDERED /* remove first X elements from the list */ - FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { - FIO_MAP2_KEY_DESTROY(o->map[i].key); - FIO_MAP2_VALUE_DESTROY(o->map[i].value); - FIO_INDEXED_LIST_REMOVE(o->map, node, i); - imap[i] = 0xFF; - --o->count; - if (!(--number_of_elements)) { - o->head = o->map[i].node.next; - return; + if (r.alt == (uint32_t)-1) + r.home = r.alt; + return r; + +possible_attack: + if (!guard_print) { + guard_print = 1; + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP_NAME, s)) " under attack? (full collision guard)"); + } + return r; +} + +/* seek a node for larger collections, where 8 byte grouping is meaningless */ +FIO_SFUNC fio___map_node_info_s FIO_NAME(FIO_MAP_NAME, __node_info_full)( + FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { + // FIO_LOG_INFO("seek as linear array for h %llu", node->hash); + static int guard_print = 0; + fio___map_node_info_s r = { + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)-1, + (uint32_t)FIO_NAME(FIO_MAP_NAME, __byte_hash)(node->hash)}; + const uint32_t mask = (FIO_MAP_CAPA(o->bits) - 1) & (~(uint32_t)7ULL); + const uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + const size_t attempt_limit = o->bits + 7; + const uint64_t mbyte64 = ~(UINT64_C(0x0101010101010101) * (uint64_t)r.bhash); + uint32_t guard = FIO_MAP_ATTACK_LIMIT + 1; + uint32_t pos = r.home = (node->hash & mask); + size_t attempt = 0; + for (; r.alt == (uint32_t)-1 && attempt < attempt_limit; ++attempt) { + uint64_t group = fio_buf2u64_le(imap + pos); + group ^= mbyte64; + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (imap[offset] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + offset, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[offset].key, node->key)) { + r.act = offset; + return r; + } + if (!--guard) { + r.act = offset; + goto possible_attack; + } + } + } + group = fio_buf2u64_le(imap + pos); + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (imap[offset] == 255U) { + r.alt = offset; + break; + } + } + group = ~fio_buf2u64_le(imap + pos); + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (!imap[offset]) { + r.alt = offset; + return r; + } } + pos += (attempt + 2) << 3; + pos &= mask; } -#else /* remove whatever... */ - if (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT) { - /* map is scattered */ - uint32_t pos_mask = (uint32_t)(FIO_MAP2_CAPA(o->bits) - 1); - uint32_t pos = *(uint32_t *)o->map; - for (int i = 0; i < 3; ++i) { - struct timespec t = {0}; - clock_gettime(CLOCK_MONOTONIC, &t); - pos *= t.tv_nsec ^ t.tv_sec ^ (uintptr_t)imap; - pos ^= pos >> 7; + for (; attempt < attempt_limit; ++attempt) { + uint64_t group = fio_buf2u64_le(imap + pos); + group ^= mbyte64; + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (imap[offset] == r.bhash && + FIO_NAME(FIO_MAP_NAME, __is_eq_hash)(o->map + offset, node->hash)) { + if (FIO_MAP_KEY_CMP(o->map[offset].key, node->key)) { + r.act = offset; + return r; + } + if (!--guard) { + r.act = offset; + goto possible_attack; + } + } } - for (;;) { /* a bit of non-random randomness... */ - uint32_t offset = ((pos << 3)) & pos_mask; - for (uint_fast8_t i = 0; i < 8; ++i) { /* ordering bias? vs performance */ - const uint32_t tmp = offset + i; - if (!imap[tmp] || imap[tmp] == 0xFF) - continue; - FIO_MAP2_KEY_DESTROY(o->map[tmp].key); - FIO_MAP2_VALUE_DESTROY(o->map[tmp].value); - imap[tmp] = 0xFF; - --o->count; - if (!(--number_of_elements)) - return; + group = ~fio_buf2u64_le(imap + pos); + group &= UINT64_C(0x7F7F7F7F7F7F7F7F); + group += UINT64_C(0x0101010101010101); + group &= UINT64_C(0x8080808080808080); + while (group) { + uint32_t offset = (uint32_t)fio_lsb_index_unsafe(group); + group ^= (uint64_t)1ULL << offset; + offset >>= 3; + offset += pos; + if (!imap[offset]) { + return r; } - pos += FIO_MAP2_CUCKOO_STEPS; } + pos += (attempt + 2) << 3; + pos &= mask; } - /* map is a simple array */ - while (number_of_elements--) { - FIO_MAP2_KEY_DESTROY(o->map[number_of_elements].key); - FIO_MAP2_VALUE_DESTROY(o->map[number_of_elements].value); - imap[number_of_elements] = 0xFF; + if (r.alt == (uint32_t)-1) + r.home = r.alt; + return r; +possible_attack: + if (!guard_print) { + guard_print = 1; + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP_NAME, s)) " under attack? (full collision guard)"); } -#endif /* FIO_MAP2_LRU / FIO_MAP2_ORDERED */ + return r; } -/* ***************************************************************************** -The Map set/get functions -***************************************************************************** */ +FIO_IFUNC fio___map_node_info_s +FIO_NAME(FIO_MAP_NAME, __node_info)(FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_NAME(FIO_MAP_NAME, __o_node_s) * node) { +#if defined(FIO_MAP_HASH_FN) + if (!node->hash) + node->hash = FIO_MAP_HASH_FN(node->key); +#endif + node->hash += !node->hash; + if (o->bits < 4) + return FIO_NAME(FIO_MAP_NAME, __node_info_mini)(o, node); + else if (o->bits < 9) + return FIO_NAME(FIO_MAP_NAME, __node_info_med)(o, node); + else + return FIO_NAME(FIO_MAP_NAME, __node_info_full)(o, node); +} -/** - * The core get function. This function returns NULL if item is missing. - * - * NOTE: the function returns the internal representation of objects. - */ -SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * - FIO_NAME(FIO_MAP2_NAME, get_ptr)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +#if FIO_MAP_ORDERED +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, + __update_order)(FIO_NAME(FIO_MAP_NAME, s) * o, + uint32_t i) { + if (o->count == 1) { + o->head = i; + o->map[i].node.next = o->map[i].node.prev = i; + } else { + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, i); + } +} +#define FIO___MAP_UPDATE_ORDER(map, at) \ + FIO_NAME(FIO_MAP_NAME, __update_order)(map, at) +#else +#define FIO___MAP_UPDATE_ORDER(map, at) #endif - FIO_MAP2_KEY key) { - FIO_NAME(FIO_MAP2_NAME, node_s) *r = NULL; - FIO_PTR_TAG_VALID_OR_RETURN(map, r); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (!o->count) - return r; -#if defined(FIO_MAP2_HASH_FN) - uint64_t hash = FIO_MAP2_HASH_FN(key); + +static int FIO_NAME(FIO_MAP_NAME, + __move2map_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * + e) { + FIO_NAME(FIO_MAP_NAME, s) *dest = (FIO_NAME(FIO_MAP_NAME, s) *)e->udata; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + n = { + .key = FIO_MAP_KEY_FROM_INTERNAL(e->node->key), +#if !FIO_MAP_RECALC_HASH + .hash = e->node->hash, #endif - hash += !hash; - uint32_t pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - if (pos == (uint32_t)-1 || !imap[pos] || imap[pos] == 0xFF) - return r; -#ifdef FIO_MAP2_LRU - if (o->head != pos) { - FIO_INDEXED_LIST_REMOVE(o->map, node, pos); - FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); - o->head = pos; + }; + fio___map_node_info_s i = FIO_NAME(FIO_MAP_NAME, __node_info)(dest, &n); + if (i.home == (uint32_t)-1) { + FIO_LOG_ERROR("move2map FAILED (%zu/%zu)!", + e->node - e->map->map, + FIO_MAP_CAPA(dest->bits)); + return -1; } -#endif - r = o->map + pos; - return r; + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(dest); + ++dest->count; + /* insert at best position */ + imap[i.alt] = i.bhash; + dest->map[i.alt] = e->node[0]; + FIO___MAP_UPDATE_ORDER(dest, i.alt); + return 0; } -/** sets / removes an object in the map, returning a pointer to the map data. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * - FIO_NAME(FIO_MAP2_NAME, set_ptr)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +FIO_IFUNC int FIO_NAME(FIO_MAP_NAME, + __move2map)(FIO_NAME(FIO_MAP_NAME, s) * dest, + FIO_NAME(FIO_MAP_NAME, s) * src) { + return FIO_NAME( + FIO_MAP_NAME, + __each_node)(src, FIO_NAME(FIO_MAP_NAME, __move2map_task), dest); +} + +/* Inserts a node to the map. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + __node_insert)(FIO_NAME(FIO_MAP_NAME, s) * o, +#ifndef FIO_MAP_HASH_FN + uint64_t hash, #endif -#ifdef FIO_MAP2_VALUE - FIO_MAP2_KEY key, - FIO_MAP2_VALUE val, - FIO_MAP2_VALUE_INTERNAL *old, - int overwrite + FIO_MAP_KEY key, +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE value, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL *old, #else - FIO_MAP2_KEY key + FIO_MAP_KEY_INTERNAL *old, #endif - ) { - FIO_NAME(FIO_MAP2_NAME, node_s) *r = NULL; -#ifdef FIO_MAP2_VALUE - if (old) - *old = (FIO_MAP2_VALUE_INTERNAL){0}; + _Bool overwrite) { + static FIO_NAME(FIO_MAP_NAME, s) *last_collision = NULL; + uint32_t r = -1; + FIO_NAME(FIO_MAP_NAME, s) tmp; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + node = { + .key = key, +#ifdef FIO_MAP_VALUE + .value = value, #endif - FIO_NAME(FIO_MAP2_NAME, s) * o; -#if defined(FIO_MAP2_HASH_FN) - uint64_t hash; +#ifndef FIO_MAP_HASH_FN + .hash = hash, #endif - uint32_t pos; - uint8_t *imap = NULL; + }; + fio___map_node_info_s info; + info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); + if (info.act != r) + goto perform_overwrite; + if (info.home == r) + goto reallocate_map; - FIO_PTR_TAG_VALID_OR_GOTO(map, relinquish_attempt); - o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); -#if defined(FIO_MAP2_HASH_FN) - hash = FIO_MAP2_HASH_FN(key); +insert: + ++o->count; + r = info.alt; + FIO_NAME(FIO_MAP_NAME, __imap)(o)[r] = info.bhash; +#if !FIO_MAP_RECALC_HASH + o->map[r].hash = node.hash, #endif - hash += !hash; /* hash is never zero */ - if (!o->bits) { /* minimal space is 8 objects... */ - *o = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, 3, 1); - } - /* find the object's (potential) position in the array */ - for (int i = 0;;) { - pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); - if (pos != (uint32_t)-1) - break; - if (i == 2) - goto internal_error; - FIO_NAME(FIO_MAP2_NAME, s) - tmp = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, o->bits + (++i), 1); - if (!tmp.map) /* no memory? something bad? */ - goto internal_error; - FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); - *o = tmp; - } - /* imap may have been reallocated, collect info now. */ - imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - /* set return value */ - r = o->map + pos; + FIO_MAP_KEY_COPY(o->map[r].key, node.key); + FIO_MAP_VALUE_COPY(o->map[r].value, node.value); + FIO___MAP_UPDATE_ORDER(o, r); + return r; - if (!imap[pos] || imap[pos] == 0xFF) { - /* insert new object */ - imap[pos] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(o, hash); -#if !FIO_MAP2_RECALC_HASH - r->hash = hash; -#endif - FIO_MAP2_KEY_COPY(r->key, key); - FIO_MAP2_VALUE_COPY(r->value, val); -#if FIO_MAP2_ORDERED - if (o->count) { /* update ordering */ - FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); -#ifdef FIO_MAP2_LRU - o->head = pos; /* update LRU head */ - if (FIO_MAP2_LRU == o->count) { /* limit reached - evict 1 LRU element */ - uint32_t to_evict = o->map[pos].node.prev; - FIO_MAP2_KEY_DESTROY(o->map[to_evict].key); - FIO_MAP2_VALUE_DESTROY(o->map[to_evict].value); - FIO_INDEXED_LIST_REMOVE(o->map, node, to_evict); - imap[to_evict] = 0xFF; - --o->count; - } -#endif /* FIO_MAP2_LRU */ - } else { /* set first order */ - o->map[pos].node.next = o->map[pos].node.prev = pos; - o->head = pos; - } -#endif /* FIO_MAP2_ORDERED */ - ++o->count; +perform_overwrite: + r = info.act; + FIO_MAP_KEY_DISCARD(node.key); + if (!overwrite) { + FIO_MAP_VALUE_DISCARD(value); return r; } - -#ifdef FIO_MAP2_LRU - /* update ordering (even if not overwriting) */ - if (o->head != pos) { - FIO_INDEXED_LIST_REMOVE(o->map, node, pos); - FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); - o->head = pos; +#ifdef FIO_MAP_VALUE + if (old) + *old = o->map[r].value; + else { + FIO_MAP_VALUE_DESTROY(o->map[r].value); } + FIO_MAP_VALUE_COPY(o->map[r].value, node.value); +#else + (void)old; #endif - -#ifdef FIO_MAP2_VALUE - if (overwrite) { - /* overwrite existing object (only relevant for hash maps) */ - FIO_MAP2_KEY_DISCARD(key); - if (!old) { - FIO_MAP2_VALUE_DESTROY(o->map[pos].value); - FIO_MAP2_VALUE_COPY(o->map[pos].value, val); - return r; - } - *old = o->map[pos].value; - o->map[pos].value = (FIO_MAP2_VALUE_INTERNAL){0}; - FIO_MAP2_VALUE_COPY(o->map[pos].value, val); - return r; - } +#if FIO_MAP_ORDERED + if (o->head == r) + o->head = o->map[o->head].node.prev; + FIO_INDEXED_LIST_REMOVE(o->map, node, r); + FIO___MAP_UPDATE_ORDER(o, r); #endif -relinquish_attempt: - /* discard attempt */ - FIO_MAP2_KEY_DISCARD(key); - FIO_MAP2_VALUE_DISCARD(val); - return r; -internal_error: - FIO_MAP2_KEY_DISCARD(key); - FIO_MAP2_VALUE_DISCARD(val); - FIO_LOG_ERROR("unknown error occurred trying to add an entry to the map"); - FIO_ASSERT_DEBUG(0, "these errors shouldn't happen"); return r; -} -/* ***************************************************************************** -The Map remove function -***************************************************************************** */ +reallocate_map: + /* reallocate map */ + if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&tmp, o->bits + 1)) + goto no_memory; + if (FIO_NAME(FIO_MAP_NAME, __move2map)(&tmp, o)) { + FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); + goto security_partial; + } + info = FIO_NAME(FIO_MAP_NAME, __node_info)(&tmp, &node); + if (info.home != r) { + FIO_NAME(FIO_MAP_NAME, __free_map)(o, 0); + *o = tmp; + goto insert; + } + FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); + goto security_partial; -/** Removes an object in the map, returning a pointer to the map data. */ -SFUNC int FIO_NAME(FIO_MAP2_NAME, remove)(FIO_MAP2_PTR map, -#if !defined(FIO_MAP2_HASH_FN) - uint64_t hash, +no_memory: + FIO_MAP_KEY_DISCARD(key); + FIO_MAP_VALUE_DISCARD(value); + FIO_LOG_ERROR( + "unknown error occurred trying to add an entry to the map (capa: %zu)", + (size_t)FIO_MAP_CAPA(o->bits)); + FIO_ASSERT_DEBUG(0, "these errors shouldn't happen - no memory?"); + return r; + +security_partial: + if (last_collision != o) { + FIO_LOG_SECURITY( + "hash map " FIO_MACRO2STR(FIO_NAME( + FIO_MAP_NAME, + s)) " under attack? (partial/full collision guard) - capa: %zu.", + (size_t)FIO_MAP_CAPA(o->bits)); + last_collision = o; + } + return r; +} + +/* Inserts a node to the map. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + __node_find)(FIO_NAME(FIO_MAP_NAME, s) * o, +#ifndef FIO_MAP_HASH_FN + uint64_t hash, #endif - FIO_MAP2_KEY key, -#ifdef FIO_MAP2_VALUE - FIO_MAP2_VALUE_INTERNAL *old + FIO_MAP_KEY key) { + uint32_t r = -1; + fio___map_node_info_s info; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + node = { + .key = key, +#ifndef FIO_MAP_HASH_FN + .hash = hash, +#endif + }; + if (!o->map) + return r; + info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); + return (r = info.act); +} + +/* Deletes a known node from the map. */ +FIO_IFUNC void FIO_NAME(FIO_MAP_NAME, + __node_delete_at)(FIO_NAME(FIO_MAP_NAME, s) * o, + uint32_t at, +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL *old #else - FIO_MAP2_KEY_INTERNAL *old + FIO_MAP_KEY_INTERNAL *old #endif ) { -#ifdef FIO_MAP2_VALUE - if (old) - *old = (FIO_MAP2_VALUE_INTERNAL){0}; + --o->count; + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(o); + imap[at] = 255U; /* mark as deleted */ + if (old) { +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY_DESTROY(o->map[at].key); + *old = o->map[at].value; #else - if (old) - *old = (FIO_MAP2_KEY_INTERNAL){0}; + *old = o->map[at].key; #endif + } else { + FIO_MAP_KEY_DESTROY(o->map[at].key); + FIO_MAP_VALUE_DESTROY(o->map[at].value); + } - FIO_PTR_TAG_VALID_OR_RETURN(map, -1); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); -#if defined(FIO_MAP2_HASH_FN) - uint64_t hash = FIO_MAP2_HASH_FN(key); +#if FIO_MAP_ORDERED + if (o->head == at) { + o->head = (o->count ? o->map[o->head].node.next : 0); + } + FIO_INDEXED_LIST_REMOVE(o->map, node, at); #endif - hash += !hash; /* hash is never zero */ - uint32_t pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); +} - if (pos == (uint32_t)-1 || !imap[pos] || imap[pos] == 0xFF) - return -1; +/* Deletes a node from the map. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + __node_delete)(FIO_NAME(FIO_MAP_NAME, s) * o, + FIO_MAP_KEY key, +#ifndef FIO_MAP_HASH_FN + uint64_t hash, +#endif +#ifdef FIO_MAP_VALUE + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY_INTERNAL *old +#endif +) { + uint32_t r = (uint32_t)-1; + if (!o->map) + return r; + fio___map_node_info_s info; + FIO_NAME(FIO_MAP_NAME, __o_node_s) + node = { + .key = key, +#ifndef FIO_MAP_HASH_FN + .hash = hash, +#endif + }; + info = FIO_NAME(FIO_MAP_NAME, __node_info)(o, &node); + if (info.act == r) + return r; + r = 0; + FIO_NAME(FIO_MAP_NAME, __node_delete_at)(o, info.act, old); + return r; +} - imap[pos] = 0xFF; /* mark hole and update count */ - --o->count; +/* ***************************************************************************** -#if FIO_MAP2_ORDERED - /* update ordering */ - if (o->head == pos) - o->head = o->map[pos].node.next; - if (o->head == pos) - o->head = 0; - else { - FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + + + + + +Map API + + + + + + +***************************************************************************** */ + +/** Destroys the object, re-initializing its container. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, destroy)(FIO_MAP_PTR map) { + // FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + FIO_NAME(FIO_MAP_NAME, __free_map)(m, 1); + *m = (FIO_NAME(FIO_MAP_NAME, s)){0}; +} + +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, reserve)(FIO_MAP_PTR map, size_t capa) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (capa <= FIO_MAP_CAPA(m->bits)) + return; + uint32_t bits = m->bits; + for (; capa > FIO_MAP_CAPA(bits); ++bits) + ; + FIO_NAME(FIO_MAP_NAME, s) tmp; + if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&tmp, bits)) + goto no_memory; + if (m->count && FIO_NAME(FIO_MAP_NAME, __move2map)(&tmp, m)) { + FIO_NAME(FIO_MAP_NAME, __free_map)(&tmp, 0); + goto no_memory; } + FIO_NAME(FIO_MAP_NAME, __free_map)(m, 0); + *m = tmp; + return; +no_memory: + FIO_LOG_ERROR("unknown error occurred trying to rehash the map"); + FIO_ASSERT_DEBUG(0, "these errors shouldn't happen - no memory?"); + return; +} + +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP_NAME, remove)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key, +#if defined(FIO_MAP_VALUE) + FIO_MAP_VALUE_INTERNAL *old +#else + FIO_MAP_KEY_INTERNAL *old +#endif +) { + FIO_PTR_TAG_VALID_OR_RETURN(map, -1); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (!m->count) + return -1; + return FIO_NAME(FIO_MAP_NAME, __node_delete)(m, + key, +#if !defined(FIO_MAP_HASH_FN) + hash, #endif + old); +} -/* destroy data, copy to `old` pointer if necessary. */ -#ifdef FIO_MAP2_VALUE - FIO_MAP2_KEY_DESTROY(o->map[pos].key); - o->map[pos].key = (FIO_MAP2_KEY_INTERNAL){0}; - if (!old) { - FIO_MAP2_VALUE_DESTROY(o->map[pos].value); - } else { - *old = o->map[pos].value; +FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, + __evict_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * + e) { + size_t *counter = (size_t *)e->udata; + FIO_NAME(FIO_MAP_NAME, __node_delete_at) + (e->map, (uint32_t)(e->node - e->map->map), NULL); + if ((counter[0] -= 1)) + return 0; + return -1; +} +/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, evict)(FIO_MAP_PTR map, + size_t number_of_elements) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + if (!number_of_elements) + return; + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (m->count <= number_of_elements) { + FIO_NAME(FIO_MAP_NAME, __destroy_map)(m, 1); + return; } - o->map[pos].value = (FIO_MAP2_VALUE_INTERNAL){0}; -#else - if (!old) { - FIO_MAP2_KEY_DESTROY(o->map[pos].key); - } else { - *old = o->map[pos].key; + FIO_NAME(FIO_MAP_NAME, __each_node) + (m, FIO_NAME(FIO_MAP_NAME, __evict_task), &number_of_elements); +} + +/** Removes all objects from the map, without releasing the map's resources. + */ +SFUNC void FIO_NAME(FIO_MAP_NAME, clear)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (m->map) + FIO_NAME(FIO_MAP_NAME, __destroy_map)(m, 1); +} + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP_NAME, compact)(FIO_MAP_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (!o->map || !o->count) + return; + FIO_NAME(FIO_MAP_NAME, s) cpy = {0}; + uint32_t bits = o->bits; + while (FIO_MAP_CAPA(bits >> 1) > o->count) + bits >>= 1; + ++bits; + if (bits >= o->bits) + return; + for (size_t i = 0; i < 2; ++i) { + if (FIO_NAME(FIO_MAP_NAME, __allocate_map)(&cpy, bits)) + return; + if (!FIO_NAME(FIO_MAP_NAME, __move2map)(&cpy, o)) + goto finish; + FIO_NAME(FIO_MAP_NAME, __free_map)(&cpy, 0); + ++bits; } - o->map[pos].key = (FIO_MAP2_KEY_INTERNAL){0}; + return; + +finish: + FIO_NAME(FIO_MAP_NAME, __free_map)(o, 0); + o[0] = cpy; + return; +} + +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, set_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, #endif -#if !FIO_MAP2_RECALC_HASH && defined(DEBUG) - o->map[pos].hash = 0; /* not necessary, but ... good for debugging? */ +#ifdef FIO_MAP_VALUE + FIO_MAP_KEY key, + FIO_MAP_VALUE val, + FIO_MAP_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP_KEY key #endif - return 0; + ) { + FIO_PTR_TAG_VALID_OR_RETURN(map, NULL); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + uint32_t i = FIO_NAME(FIO_MAP_NAME, __node_insert)(m, +#ifndef FIO_MAP_HASH_FN + hash, +#endif + key, +#ifdef FIO_MAP_VALUE + val, + old, + overwrite +#else + NULL, + 1 +#endif + ); + if (i == (uint32_t)-1) + return NULL; + return m->map + i; +} + +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP_NAME, node_s) * + FIO_NAME(FIO_MAP_NAME, get_ptr)(FIO_MAP_PTR map, +#if !defined(FIO_MAP_HASH_FN) + uint64_t hash, +#endif + FIO_MAP_KEY key) { + FIO_PTR_TAG_VALID_OR_RETURN(map, NULL); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + uint32_t i = FIO_NAME(FIO_MAP_NAME, __node_find)(m, +#ifndef FIO_MAP_HASH_FN + hash, +#endif + key); + if (i == (uint32_t)-1) + return NULL; + return m->map + i; } /* ***************************************************************************** -Map Iteration + + + +Map Iterators + + + ***************************************************************************** */ -/** Returns the next iterator position after `current_pos`, first if `NULL`. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) - FIO_NAME(FIO_MAP2_NAME, - get_next)(FIO_MAP2_PTR map, - FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos) { - FIO_NAME(FIO_MAP2_NAME, iterator_s) r = {.private_ = {.pos = 0}}; +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, + get_next)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * current_pos) { + FIO_NAME(FIO_MAP_NAME, iterator_s) r = {0}; FIO_PTR_TAG_VALID_OR_RETURN(map, r); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (!o->count) + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(m); + if (!m->count) return r; - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - size_t capa = FIO_MAP2_CAPA(o->bits); - size_t pos_counter = 0; - if (!current_pos || !current_pos->private_.map_validator) { - goto find_pos; - } - if (current_pos->private_.pos + 1 == o->count) + if (!current_pos) + goto empty; + if (current_pos->private_.pos + 1 == m->count) return r; - r.private_.pos = current_pos->private_.pos + 1; - if (current_pos->private_.map_validator != (uintptr_t)o) { - goto refind_pos; + if (current_pos->private_.map_validator != (uintptr_t)(m->map)) + return r; /* mutation stops iteration */ +#if FIO_MAP_ORDERED + if (current_pos->node->node.next == m->head) + return r; + r.node = m->map + current_pos->node->node.next; +#else + for (size_t i = current_pos->node - m->map + 1; i < FIO_MAP_CAPA(m->bits); + ++i) { + if (!imap[i] || imap[i] == 0xFFU) + continue; + r.node = m->map + i; + break; } - r.private_.index = current_pos->private_.index; + if (!r.node) + return r; +#endif -#if !FIO_MAP2_RECALC_HASH -#define FIO_MAP2___EACH_COPY_HASH() r.hash = o->map[r.private_.index].hash -#else -#define FIO_MAP2___EACH_COPY_HASH() + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, #endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = current_pos->private_.pos + 1, + .map_validator = (uintptr_t)m->map}, + }; + return r; +empty: -#ifdef FIO_MAP2_VALUE -#define FIO_MAP2___EACH_COPY_DATA() \ - FIO_MAP2___EACH_COPY_HASH(); \ - r.private_.map_validator = (uintptr_t)o; \ - r.node = o->map + r.private_.index; \ - r.key = FIO_MAP2_KEY_FROM_INTERNAL(o->map[r.private_.index].key); \ - r.value = FIO_MAP2_VALUE_FROM_INTERNAL(o->map[r.private_.index].value) +#if FIO_MAP_ORDERED + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = m->map + m->head, + .key = FIO_MAP_KEY_FROM_INTERNAL(m->map[m->head].key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(m->map[m->head].value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = m->map[m->head].hash, +#endif + .private_ = {.index = m->head, + .pos = 0, + .map_validator = (uintptr_t)m->map}, + }; #else -#define FIO_MAP2___EACH_COPY_DATA() \ - FIO_MAP2___EACH_COPY_HASH(); \ - r.private_.map_validator = (uintptr_t)o; \ - r.node = o->map + r.private_.index; \ - r.key = FIO_MAP2_KEY_FROM_INTERNAL(o->map[r.private_.index].key) + for (size_t i = 0; i < FIO_MAP_CAPA(m->bits); ++i) { + if (!imap[i] || imap[i] == 0xFFU) + continue; + r.node = m->map + i; + break; + } + + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, #endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = 0, + .map_validator = (uintptr_t)m->map}, + }; -/* start seeking at the position inherited from current_pos */ -#if FIO_MAP2_ORDERED - (void)imap; /* unused in ordered maps */ - (void)capa; /* unused in ordered maps */ - r.private_.index = o->map[r.private_.index].node.next; - if (r.private_.index == o->head) - goto not_found; - FIO_MAP2___EACH_COPY_DATA(); +#endif return r; + (void)imap; /* if unused */ +} + +SFUNC FIO_NAME(FIO_MAP_NAME, iterator_s) + FIO_NAME(FIO_MAP_NAME, get_prev)(FIO_MAP_PTR map, + FIO_NAME(FIO_MAP_NAME, iterator_s) * + current_pos) { // TODO! + FIO_NAME(FIO_MAP_NAME, iterator_s) r = {0}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); +#if FIO_MAP_ORDERED + uint32_t ipos; #else - if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ - while ((++r.private_.index) & 7) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - while (r.private_.index < capa) { - uint64_t simd = *(uint64_t *)(imap + r.private_.index); - if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { - r.private_.index += 8; - continue; - } - for (int i = 0; i < 8; (++i), (++r.private_.index)) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - } - goto not_found; - } - /* review as array */ - while ((++r.private_.index) < capa) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - FIO_MAP2___EACH_COPY_DATA(); + uint8_t *imap = FIO_NAME(FIO_MAP_NAME, __imap)(m); +#endif + if (!m->count) return r; - } - goto not_found; -#endif /* FIO_MAP2_ORDERED */ - -refind_pos: - if (current_pos->private_.index) - goto not_found; -find_pos: -/* first seek... re-start seeking */ -#if FIO_MAP2_ORDERED - FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { - if (pos_counter != r.private_.pos) { - ++pos_counter; - continue; - } - r.private_.index = (uint32_t)i; - FIO_MAP2___EACH_COPY_DATA(); + if (!current_pos) + goto empty; + if (!current_pos->private_.pos) return r; - } - goto not_found; + if (current_pos->private_.map_validator != (uintptr_t)(m->map)) + return r; /* mutation stops iteration */ +#if FIO_MAP_ORDERED + if (current_pos->private_.index == m->head) + return r; + r.node = m->map + current_pos->node->node.prev; #else - if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ - while (r.private_.index < capa) { - uint64_t simd = *(uint64_t *)(imap + r.private_.index); - if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { - r.private_.index += 8; - continue; - } - for (int i = 0; i < 8; (++i), (++r.private_.index)) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - if (pos_counter != r.private_.pos) { - ++pos_counter; - continue; - } - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - } - goto not_found; - } - /* review as array */ - while (r.private_.index < capa) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) { - ++r.private_.index; + for (size_t i = current_pos->node - m->map; i;) { + --i; + if (!imap[i] || imap[i] == 0xFFU) continue; - } - FIO_MAP2___EACH_COPY_DATA(); - return r; + r.node = m->map + i; + break; } -#endif /* FIO_MAP2_ORDERED */ +#endif -not_found: - return (r = (FIO_NAME(FIO_MAP2_NAME, iterator_s)){.private_ = {.pos = 0}}); - FIO_ASSERT_DEBUG(0, "should this happen? ever?"); -} + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, +#endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = current_pos->private_.pos - 1, + .map_validator = (uintptr_t)m->map}, + }; + return r; +empty: -/** Returns the next iterator position after `current_pos`, first if `NULL`. */ -SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) - FIO_NAME(FIO_MAP2_NAME, - get_prev)(FIO_MAP2_PTR map, - FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos) { - FIO_NAME(FIO_MAP2_NAME, iterator_s) r = {.private_ = {.pos = 0}}; - FIO_PTR_TAG_VALID_OR_RETURN(map, r); - FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); - if (!o->count) - return r; -#if !FIO_MAP2_ORDERED - uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); - size_t capa = FIO_MAP2_CAPA(o->bits); +#if FIO_MAP_ORDERED + + ipos = m->map[m->head].node.prev; + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = m->map + ipos, .key = FIO_MAP_KEY_FROM_INTERNAL(m->map[ipos].key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(m->map[ipos].value), #endif - size_t pos_counter = o->count; - if (!current_pos || !current_pos->private_.map_validator) { - r.private_.map_validator = (uintptr_t)o; - r.private_.pos = o->count; - goto find_pos; - } - if (!current_pos->private_.pos) - return r; - r.private_.pos = current_pos->private_.pos - 1; - r.private_.map_validator = (uintptr_t)o; - if (current_pos->private_.map_validator != (uintptr_t)o) { - goto refind_pos; - } - r.private_.index = current_pos->private_.index; +#if !FIO_MAP_RECALC_HASH + .hash = m->map[ipos].hash, +#endif + .private_ = {.index = ipos, + .pos = m->count - 1, + .map_validator = (uintptr_t)m->map}, + }; -/* start seeking at the position inherited from current_pos */ -#if FIO_MAP2_ORDERED - if (r.private_.index == o->head) - goto not_found; - r.private_.index = o->map[r.private_.index].node.prev; - FIO_MAP2___EACH_COPY_DATA(); - return r; #else - if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ - while ((--r.private_.index) & 7) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - while (r.private_.index) { - uint64_t simd = *(uint64_t *)(imap + (r.private_.index - 8)); - if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { - r.private_.index -= 8; - continue; - } - for (int i = 0; i < 8; ++i) { - --r.private_.index; - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - } - goto not_found; - } - /* review as array */ - while (r.private_.index--) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + for (size_t i = FIO_MAP_CAPA(m->bits); i;) { + --i; + if (!imap[i] || imap[i] == 0xFFU) continue; - FIO_MAP2___EACH_COPY_DATA(); - return r; + r.node = m->map + i; + break; } - goto not_found; -#endif /* FIO_MAP2_ORDERED */ -refind_pos: - if (current_pos->private_.index) - goto not_found; -find_pos: -/* first seek... re-start seeking */ -#if FIO_MAP2_ORDERED - FIO_INDEXED_LIST_EACH_REVERSED(o->map, node, o->head, i) { - if (pos_counter != r.private_.pos) { - --pos_counter; - continue; - } - r.private_.index = (uint32_t)i; - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - goto not_found; -#else - r.private_.index = (uint32_t)capa; - if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ - while (r.private_.index) { - uint64_t simd = *(uint64_t *)(imap + r.private_.index); - if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { - r.private_.index -= 8; - continue; - } - for (int i = 0; i < 8; (++i), (--r.private_.index)) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - if (pos_counter != r.private_.pos) { - ++pos_counter; - continue; - } - FIO_MAP2___EACH_COPY_DATA(); - return r; - } - } - goto not_found; - } - /* review as array */ - while ((r.private_.index--)) { - if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) - continue; - FIO_MAP2___EACH_COPY_DATA(); - return r; - } -#endif /* FIO_MAP2_ORDERED */ + r = (FIO_NAME(FIO_MAP_NAME, iterator_s)) { + .node = r.node, .key = FIO_MAP_KEY_FROM_INTERNAL(r.node->key), +#ifdef FIO_MAP_VALUE + .value = FIO_MAP_VALUE_FROM_INTERNAL(r.node->value), +#endif +#if !FIO_MAP_RECALC_HASH + .hash = r.node->hash, +#endif + .private_ = {.index = (uint32_t)(r.node - m->map), + .pos = m->count - 1, + .map_validator = (uintptr_t)m->map}, + }; -not_found: - return (r = (FIO_NAME(FIO_MAP2_NAME, iterator_s)){.private_ = {.pos = 0}}); - FIO_ASSERT_DEBUG(0, "should this happen? ever?"); +#endif + + return r; +} + +/* ***************************************************************************** + + + +Map Each + + + +***************************************************************************** */ + +typedef struct { + FIO_NAME(FIO_MAP_NAME, each_s) each; + ssize_t start_at; +} FIO_NAME(FIO_MAP_NAME, __each_info_s); + +FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, + __each_task)(FIO_NAME(FIO_MAP_NAME, __each_node_s) * e) { + int r; + FIO_NAME(FIO_MAP_NAME, __each_info_s) *info = + (FIO_NAME(FIO_MAP_NAME, __each_info_s) *)e->udata; + info->each.key = FIO_MAP_KEY_FROM_INTERNAL(e->node->key); +#ifdef FIO_MAP_VALUE + info->each.value = FIO_MAP_VALUE_FROM_INTERNAL(e->node->value); +#endif + r = info->each.task(&info->each); + ++info->each.index; + return r; +} + +FIO_SFUNC int FIO_NAME(FIO_MAP_NAME, __each_task_offset)( + FIO_NAME(FIO_MAP_NAME, __each_node_s) * e) { + FIO_NAME(FIO_MAP_NAME, __each_info_s) *info = + (FIO_NAME(FIO_MAP_NAME, __each_info_s) *)e->udata; + if (FIO_LIKELY(info->each.index < (uint64_t)info->start_at)) { + ++info->each.index; + return 0; + } + return (e->fn = FIO_NAME(FIO_MAP_NAME, __each_task))(e); } -#undef FIO_MAP2___EACH_COPY_HASH -#undef FIO_MAP2___EACH_COPY_DATA /** * Iteration using a callback for each element in the map. @@ -29737,57 +29592,49 @@ SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) * Returns the relative "stop" position, i.e., the number of items processed + * the starting point. */ -SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, - each)(FIO_MAP2_PTR map, - int (*task)(FIO_NAME(FIO_MAP2_NAME, each_s) *), +SFUNC uint32_t FIO_NAME(FIO_MAP_NAME, + each)(FIO_MAP_PTR map, + int (*task)(FIO_NAME(FIO_MAP_NAME, each_s) *), void *udata, ssize_t start_at) { - FIO_PTR_TAG_VALID_OR_RETURN(map, 1); - FIO_NAME(FIO_MAP2_NAME, each_s) + uint32_t r = (uint32_t)-1; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP_NAME, s) *m = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP_T, map); + if (start_at < 0) + start_at += m->count; + if (start_at < 0) + return m->count; + FIO_NAME(FIO_MAP_NAME, __each_info_s) e = { - .parent = map, - .task = task, - .udata = udata, + .each = + { + .parent = map, + .index = 0, + .task = task, + .udata = udata, + }, + .start_at = start_at, }; - FIO_NAME(FIO_MAP2_NAME, s) *o = - FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP2_NAME, s), map); - if (start_at < 0) { - start_at += o->count; - if (start_at < 0) - start_at = 0; - } else if (start_at > o->count) - return o->count; - FIO_NAME(FIO_MAP2_NAME, iterator_s) i = {.private_ = {.pos = 0}}; - for (;;) { - i = FIO_NAME(FIO_MAP2_NAME, get_next)(map, &i); - if (!FIO_NAME(FIO_MAP2_NAME, iterator_is_valid)(&i)) - return o->count; - e.index = i.private_.pos; - e.key = i.key; -#ifdef FIO_MAP2_VALUE - e.value = i.value; -#endif - if (e.task(&e)) - return (uint32_t)(e.index + 1); - } - return o->count; -} -/* ***************************************************************************** -Speed Testing -***************************************************************************** */ + FIO_NAME(FIO_MAP_NAME, __each_node) + (m, + !start_at ? FIO_NAME(FIO_MAP_NAME, __each_task) + : FIO_NAME(FIO_MAP_NAME, __each_task_offset), + &e); + return (uint32_t)e.each.index; +} /* ***************************************************************************** Map Testing ***************************************************************************** */ -#ifdef FIO_MAP2_TEST +#ifdef FIO_MAP_TEST -#ifdef FIO_MAP2_HASH_FN +#ifdef FIO_MAP_HASH_FN #define FIO___M_HASH(k) #else #define FIO___M_HASH(k) (k), #endif -#ifdef FIO_MAP2_VALUE +#ifdef FIO_MAP_VALUE #define FIO___M_VAL(v) , (v) #define FIO___M_OLD , NULL #else @@ -29795,2080 +29642,2116 @@ Map Testing #define FIO___M_OLD #endif -FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MAP2_NAME)(void) { +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MAP_NAME)(void) { /* testing only only works with integer external types */ - fprintf(stderr, - "* Testing maps with key " FIO_MACRO2STR( - FIO_MAP2_KEY) " (=> " FIO_MACRO2STR(FIO_MAP2_VALUE) ").\n"); - { /* test set / get overwrite , FIO_MAP2_EACH and evict */ - FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; - for (size_t i = 1; i < (1UL << (FIO_MAP2_ARRAY_LOG_LIMIT + 5)); ++i) { - FIO_NAME(FIO_MAP2_NAME, set) + fprintf( + stderr, + "* Testing map " FIO_MACRO2STR(FIO_MAP_NAME) " with key " FIO_MACRO2STR( + FIO_MAP_KEY) " (=> " FIO_MACRO2STR(FIO_MAP_VALUE) ").\n"); + size_t test_len_limit = (1UL << (FIO_MAP_ARRAY_LOG_LIMIT + 15)); + { /* test set / get overwrite , FIO_MAP_EACH and evict */ + FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; + for (size_t i = 1; i < test_len_limit; ++i) { + FIO_NAME(FIO_MAP_NAME, set) (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, "map `set` failed? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), i); for (size_t j = ((i << 2) + 1); j < i; ++j) { /* effects LRU ordering */ - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, get_ptr)(&map, FIO___M_HASH(j) j) && - FIO_NAME(FIO_MAP2_NAME, node2val)( - FIO_NAME(FIO_MAP2_NAME, + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j) && + FIO_NAME(FIO_MAP_NAME, node2val)( + FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j)) == j, "map `get` failed? %zu/%zu (%p)", j, i, - FIO_NAME(FIO_MAP2_NAME, get_ptr)(&map, FIO___M_HASH(j) j)); - FIO_NAME(FIO_MAP2_NAME, set) + FIO_NAME(FIO_MAP_NAME, get_ptr)(&map, FIO___M_HASH(j) j)); + FIO_NAME(FIO_MAP_NAME, set) (&map, FIO___M_HASH(j) j FIO___M_VAL(j) FIO___M_OLD); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, "map `set` added an item that already exists? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), i); } } - /* test FIO_MAP2_EACH and ordering */ - uint32_t count = FIO_NAME(FIO_MAP2_NAME, count)(&map); + /* test FIO_MAP_EACH and ordering */ + uint32_t count = FIO_NAME(FIO_MAP_NAME, count)(&map); uint32_t loop_test = 0; - FIO_MAP2_EACH(FIO_MAP2_NAME, &map, i) { + FIO_MAP_EACH(FIO_MAP_NAME, &map, i) { /* test ordering */ -#ifdef FIO_MAP2_LRU - FIO_ASSERT(i.key == (count - loop_test), - "map FIO_MAP2_EACH LRU ordering broken? %zu != %zu", + ++loop_test; +#ifdef FIO_MAP_LRU + FIO_ASSERT(i.key == loop_test, + "map FIO_MAP_EACH LRU ordering broken? %zu != %zu", (size_t)(i.key), (size_t)(count - loop_test)); - ++loop_test; -#elif FIO_MAP2_ORDERED - ++loop_test; +#elif FIO_MAP_ORDERED FIO_ASSERT(i.key == loop_test, - "map FIO_MAP2_EACH LRU ordering broken? %zu != %zu", + "map FIO_MAP_EACH ordering broken? %zu != %zu", (size_t)(i.key), (size_t)(loop_test)); #else - ++loop_test; + FIO_ASSERT(i.key < test_len_limit, + "map FIO_MAP_EACH invalid data? %zu !< %zu", + (size_t)(i.key), + (size_t)(test_len_limit)); #endif } FIO_ASSERT(loop_test == count, - "FIO_MAP2_EACH failed to iterate all elements? (%zu != %zu", + "FIO_MAP_EACH failed to iterate all elements? (%zu != %zu", (size_t)loop_test != (size_t)count); loop_test = 0; - FIO_MAP2_EACH_REVERSED(FIO_MAP2_NAME, &map, i) { ++loop_test; } + FIO_MAP_EACH_REVERSED(FIO_MAP_NAME, &map, i) { + /* test reversed ordering */ + ++loop_test; +#ifdef FIO_MAP_LRU + FIO_ASSERT(i.key == (count - (loop_test - 1)), + "map FIO_MAP_EACH_REVERSED LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(count - loop_test)); +#elif FIO_MAP_ORDERED + FIO_ASSERT(i.key == (count - (loop_test - 1)), + "map FIO_MAP_EACH_REVERSED ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(loop_test)); +#endif + } FIO_ASSERT( loop_test == count, - "FIO_MAP2_EACH_REVERSED failed to iterate all elements? (%zu != %zu", + "FIO_MAP_EACH_REVERSED failed to iterate all elements? (%zu != %zu", (size_t)loop_test != (size_t)count); /* test `evict` while we're here */ - FIO_NAME(FIO_MAP2_NAME, evict)(&map, (count >> 1)); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == (count - (count >> 1)), + FIO_NAME(FIO_MAP_NAME, evict)(&map, (count >> 1)); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == (count - (count >> 1)), "map `evict` count error %zu != %zu", - (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), (size_t)(count - (count >> 1))); /* cleanup */ - FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + FIO_NAME(FIO_MAP_NAME, destroy)(&map); } -#ifndef FIO_MAP2_HASH_FN +#if !FIO_MAP_RECALC_HASH { /* test full collision guard and zero hash*/ - FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; fprintf( stderr, "* Testing full collision guard for " FIO_MACRO2STR( - FIO_NAME(FIO_MAP2_NAME, s)) " - expect SECURITY log messages.\n"); + FIO_NAME(FIO_MAP_NAME, s)) " - expect SECURITY log messages.\n"); for (size_t i = 1; i < 4096; ++i) { - FIO_NAME(FIO_MAP2_NAME, set) + FIO_NAME(FIO_MAP_NAME, set) (&map, FIO___M_HASH(0) i FIO___M_VAL(i) FIO___M_OLD); } - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map), + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map), "zero hash fails insertion?"); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) <= FIO_MAP2_ATTACK_LIMIT, + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) <= FIO_MAP_ATTACK_LIMIT, "map attack guard failed? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), - (size_t)FIO_MAP2_ATTACK_LIMIT); - FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + (size_t)FIO_MAP_ATTACK_LIMIT); + FIO_NAME(FIO_MAP_NAME, destroy)(&map); } #endif { /* test reserve, remove */ - FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; - FIO_NAME(FIO_MAP2_NAME, reserve)(&map, 4096); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, capa)(&map) == 4096, + FIO_NAME(FIO_MAP_NAME, s) map = FIO_MAP_INIT; + FIO_NAME(FIO_MAP_NAME, reserve)(&map, 4096); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, capa)(&map) == 4096, "map reserve error? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP2_NAME, capa)(&map), + (size_t)FIO_NAME(FIO_MAP_NAME, capa)(&map), 4096); - for (size_t i = 1; i < 4096; ++i) { - FIO_NAME(FIO_MAP2_NAME, set) + for (size_t i = 1; i < test_len_limit; ++i) { + FIO_NAME(FIO_MAP_NAME, set) (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, - "insertion failed?"); + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == i, "insertion failed?"); } - for (size_t i = 1; i < 4096; ++i) { - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, get)(&map, FIO___M_HASH(i) i), + for (size_t i = 1; i < test_len_limit; ++i) { + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, get)(&map, FIO___M_HASH(i) i), "key missing?"); - FIO_NAME(FIO_MAP2_NAME, remove) + size_t count = FIO_NAME(FIO_MAP_NAME, count)(&map); + FIO_NAME(FIO_MAP_NAME, remove) (&map, FIO___M_HASH(i) i, NULL); - FIO_ASSERT(!FIO_NAME(FIO_MAP2_NAME, get)(&map, FIO___M_HASH(i) i), + FIO_ASSERT(!FIO_NAME(FIO_MAP_NAME, get)(&map, FIO___M_HASH(i) i), "map_remove error?"); - FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == 4095 - i, + FIO_ASSERT(FIO_NAME(FIO_MAP_NAME, count)(&map) == count - 1, "map count error after removal? %zu != %zu", - (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), - i); + (size_t)FIO_NAME(FIO_MAP_NAME, count)(&map), + count - 1); + /* see if removal produces errors while rehashing */ + FIO_NAME(FIO_MAP_NAME, compact)(&map); } - FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + FIO_NAME(FIO_MAP_NAME, destroy)(&map); } } #undef FIO___M_HASH #undef FIO___M_VAL #undef FIO___M_OLD -#endif /* FIO_MAP2_TEST */ +#endif /* FIO_MAP_TEST */ /* ***************************************************************************** Map Cleanup ***************************************************************************** */ #endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_MAP2_ARRAY_LOG_LIMIT -#undef FIO_MAP2_ATTACK_LIMIT -#undef FIO_MAP2_CAPA -#undef FIO_MAP2_CAPA_BITS_LIMIT -#undef FIO_MAP2_CUCKOO_STEPS -#undef FIO_MAP2_GET_T -#undef FIO_MAP2_HASH_FN -#undef FIO_MAP2_IS_SPARSE -#undef FIO_MAP2_KEY -#undef FIO_MAP2_KEY_CMP -#undef FIO_MAP2_KEY_COPY -#undef FIO_MAP2_KEY_DESTROY -#undef FIO_MAP2_KEY_DESTROY_SIMPLE -#undef FIO_MAP2_KEY_DISCARD -#undef FIO_MAP2_KEY_FROM_INTERNAL -#undef FIO_MAP2_KEY_INTERNAL -#undef FIO_MAP2_KEY_IS_GREATER_THAN -#undef FIO_MAP2_LRU -#undef FIO_MAP2_NAME -#undef FIO_MAP2_ORDERED -#undef FIO_MAP2_PTR -#undef FIO_MAP2_RECALC_HASH -#undef FIO_MAP2_SEEK_LIMIT -#undef FIO_MAP2_T -#undef FIO_MAP2_TEST -#undef FIO_MAP2_VALUE -#undef FIO_MAP2_VALUE_BSTR -#undef FIO_MAP2_VALUE_COPY -#undef FIO_MAP2_VALUE_DESTROY -#undef FIO_MAP2_VALUE_DESTROY_SIMPLE -#undef FIO_MAP2_VALUE_DISCARD -#undef FIO_MAP2_VALUE_FROM_INTERNAL -#undef FIO_MAP2_VALUE_INTERNAL -#undef FIO_OMAP_NAME -#undef FIO_UMAP_NAME - -#endif /* FIO_MAP2_NAME */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_REF_NAME long_ref /* Development inclusion - ignore line */ -#define FIO_REF_TYPE long /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** - - - +#undef FIO_MAP_ARRAY_LOG_LIMIT +#undef FIO_MAP_ATTACK_LIMIT +#undef FIO_MAP_CAPA +#undef FIO_MAP_CAPA_BITS_LIMIT +#undef FIO_MAP_CUCKOO_STEPS +#undef FIO_MAP_GET_T +#undef FIO_MAP_HASH_FN +#undef FIO_MAP_IS_SPARSE +#undef FIO_MAP_KEY +#undef FIO_MAP_KEY_CMP +#undef FIO_MAP_KEY_COPY +#undef FIO_MAP_KEY_DESTROY +#undef FIO_MAP_KEY_DESTROY_SIMPLE +#undef FIO_MAP_KEY_DISCARD +#undef FIO_MAP_KEY_FROM_INTERNAL +#undef FIO_MAP_KEY_INTERNAL +#undef FIO_MAP_KEY_IS_GREATER_THAN +#undef FIO_MAP_LRU +#undef FIO_MAP_NAME +#undef FIO_MAP_ORDERED +#undef FIO_MAP_PTR +#undef FIO_MAP_RECALC_HASH +#undef FIO_MAP_SEEK_LIMIT +#undef FIO_MAP_T +#undef FIO_MAP_TEST +#undef FIO_MAP_VALUE +#undef FIO_MAP_VALUE_BSTR +#undef FIO_MAP_VALUE_COPY +#undef FIO_MAP_VALUE_DESTROY +#undef FIO_MAP_VALUE_DESTROY_SIMPLE +#undef FIO_MAP_VALUE_DISCARD +#undef FIO_MAP_VALUE_FROM_INTERNAL +#undef FIO_MAP_VALUE_INTERNAL + +#undef FIO_MAP___MAKE_BITMAP +#undef FIO_MAP___STEP_POS +#undef FIO_MAP___TEST_MATCH +#undef FIO___MAP_UPDATE_ORDER +#undef FIO_MAP_ARRAY_LOG_LIMIT +#undef FIO_MAP_ATTACK_LIMIT +#undef FIO_MAP_CAPA +#undef FIO_MAP_CAPA_BITS_LIMIT +#undef FIO_MAP_CUCKOO_STEPS +#undef FIO_MAP_GET_T +#undef FIO_MAP_HASH_FN +#undef FIO_MAP_IS_SPARSE +#undef FIO_MAP_KEY +#undef FIO_MAP_KEY_BSTR +#undef FIO_MAP_KEY_CMP +#undef FIO_MAP_KEY_COPY +#undef FIO_MAP_KEY_DESTROY +#undef FIO_MAP_KEY_DESTROY_SIMPLE +#undef FIO_MAP_KEY_DISCARD +#undef FIO_MAP_KEY_FROM_INTERNAL +#undef FIO_MAP_KEY_INTERNAL +#undef FIO_MAP_KEY_IS_GREATER_THAN +#undef FIO_MAP_KEY_KSTR +#undef FIO_MAP_LRU +#undef FIO_MAP_MINIMAL_BITS +#undef FIO_MAP_NAME +#undef FIO_MAP_ORDERED +#undef FIO_MAP_PTR +#undef FIO_MAP_RECALC_HASH +#undef FIO_MAP_SEEK_LIMIT +#undef FIO_MAP_T +#undef FIO_MAP_TEST +#undef FIO_MAP_VALUE +#undef FIO_MAP_VALUE_BSTR +#undef FIO_MAP_VALUE_COPY +#undef FIO_MAP_VALUE_DESTROY +#undef FIO_MAP_VALUE_DESTROY_SIMPLE +#undef FIO_MAP_VALUE_DISCARD +#undef FIO_MAP_VALUE_FROM_INTERNAL +#undef FIO_MAP_VALUE_INTERNAL + +#undef FIO_OMAP_NAME +#undef FIO_UMAP_NAME + +#endif /* FIO_MAP_NAME */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_MAP2_NAME map /* Development inclusion - ignore line */ +#define FIO_MAP2_TEST /* Development inclusion - ignore line */ +#define FIO_MAP2_KEY size_t /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + Unordered/Ordered Map Implementation - Reference Counting / Wrapper - (must be placed after all type macros) Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ -#ifdef FIO_REF_NAME +#if defined(FIO_MAP2_NAME) +/* ***************************************************************************** +Map Settings - Sets have only keys (value == key) - Hash Maps have values +***************************************************************************** */ -#ifndef FIO_REF_TYPE -#define FIO_REF_TYPE FIO_NAME(FIO_REF_NAME, s) +/* if FIO_MAP2_KEY_KSTR is defined, use fio_keystr_s keys */ +#ifdef FIO_MAP2_KEY_KSTR +#define FIO_MAP2_KEY fio_str_info_s +#define FIO_MAP2_KEY_INTERNAL fio_keystr_s +#define FIO_MAP2_KEY_FROM_INTERNAL(k) fio_keystr_info(&(k)) +#define FIO_MAP2_KEY_COPY(dest, src) \ + (dest) = fio_keystr_init((src), FIO_NAME(FIO_MAP2_NAME, __key_alloc)) +#define FIO_MAP2_KEY_CMP(a, b) fio_keystr_is_eq2((a), (b)) +#define FIO_MAP2_KEY_DESTROY(key) \ + fio_keystr_destroy(&(key), FIO_NAME(FIO_MAP2_NAME, __key_free)) +#define FIO_MAP2_KEY_DISCARD(key) +FIO_SFUNC void *FIO_NAME(FIO_MAP2_NAME, __key_alloc)(size_t len) { + return FIO_MEM_REALLOC_(NULL, 0, len, 0); +} +FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, __key_free)(void *ptr, size_t len) { + FIO_MEM_FREE_(ptr, len); + (void)len; /* if unused */ +} +#undef FIO_MAP2_KEY_KSTR + +/* if FIO_MAP2_KEY is undefined, assume String keys (using `fio_bstr`). */ +#elif !defined(FIO_MAP2_KEY) || defined(FIO_MAP2_KEY_BSTR) +#define FIO_MAP2_KEY fio_str_info_s +#define FIO_MAP2_KEY_INTERNAL char * +#define FIO_MAP2_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP2_KEY_COPY(dest, src) \ + (dest) = fio_bstr_write(NULL, (src).buf, (src).len) +#define FIO_MAP2_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP2_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP2_KEY_DISCARD(key) #endif +#undef FIO_MAP2_KEY_BSTR -#ifndef FIO_REF_INIT -#define FIO_REF_INIT(obj) \ - do { \ - if (!FIO_MEM_REALLOC_IS_SAFE_) \ - (obj) = (FIO_REF_TYPE){0}; \ - } while (0) +#ifndef FIO_MAP2_KEY_INTERNAL +#define FIO_MAP2_KEY_INTERNAL FIO_MAP2_KEY #endif -#ifndef FIO_REF_DESTROY -#define FIO_REF_DESTROY(obj) +#ifndef FIO_MAP2_KEY_FROM_INTERNAL +#define FIO_MAP2_KEY_FROM_INTERNAL(o) o #endif -#ifndef FIO_REF_METADATA_INIT -#ifdef FIO_REF_METADATA -#define FIO_REF_METADATA_INIT(meta) \ - do { \ - if (!FIO_MEM_REALLOC_IS_SAFE_) \ - (meta) = (FIO_REF_METADATA){0}; \ - } while (0) -#else -#define FIO_REF_METADATA_INIT(meta) +#ifndef FIO_MAP2_KEY_COPY +#define FIO_MAP2_KEY_COPY(dest, src) ((dest) = (src)) #endif + +#ifndef FIO_MAP2_KEY_CMP +#define FIO_MAP2_KEY_CMP(a, b) ((a) == (b)) #endif -#ifndef FIO_REF_METADATA_DESTROY -#define FIO_REF_METADATA_DESTROY(meta) +#ifndef FIO_MAP2_KEY_DESTROY +#define FIO_MAP2_KEY_DESTROY(o) +#define FIO_MAP2_KEY_DESTROY_SIMPLE 1 #endif -/** - * FIO_REF_CONSTRUCTOR_ONLY allows the reference counter constructor (TYPE_new) - * to be the only constructor function. - * - * When set, the reference counting functions will use `X_new` and `X_free`. - * Otherwise (assuming `X_new` and `X_free` are already defined), the reference - * counter will define `X_new2` and `X_free2` instead. - */ -#ifdef FIO_REF_CONSTRUCTOR_ONLY -#define FIO_REF_CONSTRUCTOR new -#define FIO_REF_DESTRUCTOR free -#define FIO_REF_DUPNAME dup -#else -#define FIO_REF_CONSTRUCTOR new2 -#define FIO_REF_DESTRUCTOR free2 -#define FIO_REF_DUPNAME dup2 +#ifndef FIO_MAP2_KEY_DISCARD +#define FIO_MAP2_KEY_DISCARD(o) #endif -typedef struct { - volatile size_t ref; -#ifdef FIO_REF_METADATA - FIO_REF_METADATA metadata; +/* FIO_MAP2_HASH_FN(key) - used instead of providing a hash value. */ +#ifndef FIO_MAP2_HASH_FN +#undef FIO_MAP2_RECALC_HASH #endif -} FIO_NAME(FIO_REF_NAME, _wrapper_s); -#ifdef FIO_PTR_TAG_TYPE -#define FIO_REF_TYPE_PTR FIO_PTR_TAG_TYPE -#else -#define FIO_REF_TYPE_PTR FIO_REF_TYPE * +/* FIO_MAP2_RECALC_HASH - if true, hash values won't be cached. */ +#ifndef FIO_MAP2_RECALC_HASH +#define FIO_MAP2_RECALC_HASH 0 #endif -/* ***************************************************************************** -Reference Counter (Wrapper) API -***************************************************************************** */ +#ifdef FIO_MAP2_VALUE_BSTR +#define FIO_MAP2_VALUE fio_str_info_s +#define FIO_MAP2_VALUE_INTERNAL char * +#define FIO_MAP2_VALUE_FROM_INTERNAL(v) fio_bstr_info((v)) +#define FIO_MAP2_VALUE_COPY(dest, src) \ + (dest) = fio_bstr_write(NULL, (src).buf, (src).len) +#define FIO_MAP2_VALUE_DESTROY(v) fio_bstr_free((v)) +#define FIO_MAP2_VALUE_DISCARD(v) +#endif -/** Allocates a reference counted object. */ -#ifdef FIO_REF_FLEX_TYPE -IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, - FIO_REF_CONSTRUCTOR)(size_t members); +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2_GET_T FIO_MAP2_VALUE #else -IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, FIO_REF_CONSTRUCTOR)(void); -#endif /* FIO_REF_FLEX_TYPE */ - -/** Increases the reference count. */ -FIO_IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, - FIO_REF_DUPNAME)(FIO_REF_TYPE_PTR wrapped); - -/** Frees a reference counted object (or decreases the reference count). */ -IFUNC void FIO_NAME(FIO_REF_NAME, FIO_REF_DESTRUCTOR)(FIO_REF_TYPE_PTR wrapped); +#define FIO_MAP2_GET_T FIO_MAP2_KEY +#endif -#ifdef FIO_REF_METADATA -/** Returns a pointer to the object's metadata, if defined. */ -IFUNC FIO_REF_METADATA *FIO_NAME(FIO_REF_NAME, - metadata)(FIO_REF_TYPE_PTR wrapped); +#ifndef FIO_MAP2_VALUE_INTERNAL +#define FIO_MAP2_VALUE_INTERNAL FIO_MAP2_VALUE #endif -/* ***************************************************************************** -Inline Implementation -***************************************************************************** */ -/** Increases the reference count. */ -FIO_IFUNC FIO_REF_TYPE_PTR -FIO_NAME(FIO_REF_NAME, FIO_REF_DUPNAME)(FIO_REF_TYPE_PTR wrapped_) { - FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); - if (!wrapped || !wrapped_) - return 0; - FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = - ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; - fio_atomic_add(&o->ref, 1); - return wrapped_; -} +#ifndef FIO_MAP2_VALUE_FROM_INTERNAL +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2_VALUE_FROM_INTERNAL(o) o +#else +#define FIO_MAP2_VALUE_FROM_INTERNAL(o) +#endif +#endif -/** Debugging helper, do not use for data, as returned value is unstable. */ -FIO_IFUNC size_t FIO_NAME(FIO_REF_NAME, references)(FIO_REF_TYPE_PTR wrapped_) { - FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); - if (!wrapped || !wrapped_) - return 0; - FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = - ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; - return o->ref; -} +#ifndef FIO_MAP2_VALUE_COPY +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2_VALUE_COPY(dest, src) (dest) = (src) +#else +#define FIO_MAP2_VALUE_COPY(dest, src) +#endif +#endif -/* ***************************************************************************** -Reference Counter (Wrapper) Implementation -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +#ifndef FIO_MAP2_VALUE_DESTROY +#define FIO_MAP2_VALUE_DESTROY(o) +#define FIO_MAP2_VALUE_DESTROY_SIMPLE 1 +#endif -FIO_LEAK_COUNTER_DEF(FIO_REF_NAME) +#ifndef FIO_MAP2_VALUE_DISCARD +#define FIO_MAP2_VALUE_DISCARD(o) +#endif -/** Allocates a reference counted object. */ -#ifdef FIO_REF_FLEX_TYPE -IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, - FIO_REF_CONSTRUCTOR)(size_t members) { - FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = - (FIO_NAME(FIO_REF_NAME, _wrapper_s) *)FIO_MEM_REALLOC_( - NULL, - 0, - sizeof(*o) + sizeof(FIO_REF_TYPE) + - (sizeof(FIO_REF_FLEX_TYPE) * members), - 0); -#else -IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, FIO_REF_CONSTRUCTOR)(void) { - FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = (FIO_NAME(FIO_REF_NAME, _wrapper_s) *) - FIO_MEM_REALLOC_(NULL, 0, sizeof(*o) + sizeof(FIO_REF_TYPE), 0); -#endif /* FIO_REF_FLEX_TYPE */ - if (!o) - return (FIO_REF_TYPE_PTR)(o); - FIO_LEAK_COUNTER_ON_ALLOC(FIO_REF_NAME); - o->ref = 1; - FIO_REF_METADATA_INIT((o->metadata)); - FIO_REF_TYPE *ret = (FIO_REF_TYPE *)(o + 1); - FIO_REF_INIT((ret[0])); - return (FIO_REF_TYPE_PTR)(FIO_PTR_TAG(ret)); - (void)FIO_NAME(FIO_REF_NAME, references); -} +#ifdef FIO_MAP2_LRU +#undef FIO_MAP2_ORDERED +#define FIO_MAP2_ORDERED 1 /* required for least recently used order */ +#endif -/** Frees a reference counted object (or decreases the reference count). */ -IFUNC void FIO_NAME(FIO_REF_NAME, - FIO_REF_DESTRUCTOR)(FIO_REF_TYPE_PTR wrapped_) { - FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); - if (!wrapped || !wrapped_) - return; - FIO_PTR_TAG_VALID_OR_RETURN_VOID(wrapped_); - FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = - ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; - if (!o) - return; - if (fio_atomic_sub_fetch(&o->ref, 1)) - return; - FIO_REF_DESTROY((wrapped[0])); - FIO_REF_METADATA_DESTROY((o->metadata)); - FIO_LEAK_COUNTER_ON_FREE(FIO_REF_NAME); - FIO_MEM_FREE_(o, sizeof(*o) + sizeof(FIO_REF_TYPE)); -} +/* test if FIO_MAP2_ORDERED was defined as an empty macro */ +#if defined(FIO_MAP2_ORDERED) && ((0 - FIO_MAP2_ORDERED - 1) == 1) +#undef FIO_MAP2_ORDERED +#define FIO_MAP2_ORDERED 1 /* assume developer's intention */ +#endif -#ifdef FIO_REF_METADATA -/** Returns a pointer to the object's metadata, if defined. */ -IFUNC FIO_REF_METADATA *FIO_NAME(FIO_REF_NAME, - metadata)(FIO_REF_TYPE_PTR wrapped_) { - FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); - FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = - ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; - return &o->metadata; -} +#ifndef FIO_MAP2_ORDERED +#define FIO_MAP2_ORDERED 0 #endif /* ***************************************************************************** -Reference Counter (Wrapper) Cleanup +Pointer Tagging Support ***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_REF_NAME -#undef FIO_REF_FLEX_TYPE -#undef FIO_REF_TYPE -#undef FIO_REF_INIT -#undef FIO_REF_DESTROY -#undef FIO_REF_METADATA -#undef FIO_REF_METADATA_INIT -#undef FIO_REF_METADATA_DESTROY -#undef FIO_REF_TYPE_PTR -#undef FIO_REF_CONSTRUCTOR_ONLY -#undef FIO_REF_CONSTRUCTOR -#undef FIO_REF_DUPNAME -#undef FIO_REF_DESTRUCTOR +#ifdef FIO_PTR_TAG_TYPE +#define FIO_MAP2_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, s) * #endif +#define FIO_MAP2_T FIO_NAME(FIO_MAP2_NAME, s) + /* ***************************************************************************** -Pointer Tagging Cleanup +Map Types ***************************************************************************** */ -#ifndef FIO___DEV___ -#undef FIO_PTR_TAG -#undef FIO_PTR_UNTAG -#undef FIO_PTR_TAG_TYPE -#undef FIO_PTR_TAG_VALIDATE -#undef FIO_PTR_TAG_VALID_OR_RETURN -#undef FIO_PTR_TAG_VALID_OR_RETURN_VOID -#undef FIO_PTR_TAG_VALID_OR_GOTO + +/** internal object data representation */ +typedef struct FIO_NAME(FIO_MAP2_NAME, node_s) FIO_NAME(FIO_MAP2_NAME, node_s); + +/** A Hash Map / Set type */ +typedef struct FIO_NAME(FIO_MAP2_NAME, s) { + uint32_t bits; + uint32_t count; + FIO_NAME(FIO_MAP2_NAME, node_s) * map; +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST32_HEAD head; #endif -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_CRYPTO_CORE /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** +} FIO_NAME(FIO_MAP2_NAME, s); +/** internal object data representation */ +struct FIO_NAME(FIO_MAP2_NAME, node_s) { +#if !FIO_MAP2_RECALC_HASH + uint64_t hash; +#endif + FIO_MAP2_KEY_INTERNAL key; +#ifdef FIO_MAP2_VALUE + FIO_MAP2_VALUE_INTERNAL value; +#endif +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST32_NODE node; +#endif +}; +/** Map iterator type */ +typedef struct { + /** the node in the internal map */ + FIO_NAME(FIO_MAP2_NAME, node_s) * node; + /** the key in the current position */ + FIO_MAP2_KEY key; +#ifdef FIO_MAP2_VALUE + /** the value in the current position */ + FIO_MAP2_VALUE value; +#endif +#if !FIO_MAP2_RECALC_HASH + /** the hash for the current position */ + uint64_t hash; +#endif + struct { /* internal usage, do not access */ + uint32_t index; /* the index in the internal map */ + uint32_t pos; /* the position in the ordering scheme */ + uintptr_t map_validator; /* map mutation guard */ + } private_; +} FIO_NAME(FIO_MAP2_NAME, iterator_s); +#ifndef FIO_MAP2_INIT +/* Initialization macro. */ +#define FIO_MAP2_INIT \ + { 0 } +#endif - A Template for New Types / Modules +/* ***************************************************************************** +Construction / Deconstruction +***************************************************************************** */ +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/* Allocates a new object on the heap and initializes it's memory. */ +SFUNC FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, new)(void); +/* Frees any internal data AND the object's container! */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, free)(FIO_MAP2_PTR map); -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_CRYPTO_CORE) && !defined(H___FIO_CRYPTO_CORE___H) -#define H___FIO_CRYPTO_CORE___H +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ -/* ***************************************************************************** -Module Implementation - inlined functions -***************************************************************************** */ +/** Destroys the object, reinitializing its container. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, destroy)(FIO_MAP2_PTR map); /* ***************************************************************************** -Module Implementation - possibly externed functions. +Map State ***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/* ***************************************************************************** -Module Cleanup -***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_CRYPTO_CORE -#endif /* FIO_CRYPTO_CORE */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_CHACHA /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** +/** Theoretical map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, capa)(FIO_MAP2_PTR map); +/** The number of objects in the map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, count)(FIO_MAP2_PTR map); +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, reserve)(FIO_MAP2_PTR map, size_t capa); +/** Returns the key value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, + node2key)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node); - ChaCha20 & Poly1305 +/** Returns the hash value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + node2hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * node); +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_VALUE FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node); +#endif +/** Returns the key value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node); -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_CHACHA) && !defined(H___FIO_CHACHA___H) -#define H___FIO_CHACHA___H 1 +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer (see set_ptr). */ +FIO_IFUNC FIO_MAP2_VALUE_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node); +#endif /* ***************************************************************************** -ChaCha20Poly1305 API +Adding / Removing Elements from the Map ***************************************************************************** */ -/** - * Performs an in-place encryption of `data` using ChaCha20 with additional - * data, producing a 16 byte message authentication code (MAC) using Poly1305. - * - * * `key` MUST point to a 256 bit long memory address (32 Bytes). - * * `nounce` MUST point to a 96 bit long memory address (12 Bytes). - * * `ad` MAY be omitted, will NOT be encrypted. - * * `data` MAY be omitted, WILL be encrypted. - * * `mac` MUST point to a buffer with (at least) 16 available bytes. - */ -SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, - void *restrict data, - size_t len, - const void *ad, /* additional data */ - size_t adlen, - const void *key, - const void *nounce); +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP2_NAME, remove)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key, +#ifdef FIO_MAP2_VALUE + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY_INTERNAL *old +#endif +); + +/** Evicts elements in order least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, evict)(FIO_MAP2_PTR map, + size_t number_of_elements); + +/** Removes all objects from the map, without releasing the map's resources. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, clear)(FIO_MAP2_PTR map); + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, compact)(FIO_MAP2_PTR map); + +/** Gets a value from the map, if exists. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, get)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key); + +/** Sets a value in the map, hash maps will overwrite existing data if any. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE obj, + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY key +#endif +); + +/** Sets a value in the map if not set previously. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set_if_missing)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key +#ifdef FIO_MAP2_VALUE + , + FIO_MAP2_VALUE obj +#endif +); /** - * Performs an in-place decryption of `data` using ChaCha20 after authenticating - * the message authentication code (MAC) using Poly1305. + * The core set function. * - * * `key` MUST point to a 256 bit long memory address (32 Bytes). - * * `nounce` MUST point to a 96 bit long memory address (12 Bytes). - * * `ad` MAY be omitted ONLY IF originally omitted. - * * `data` MAY be omitted, WILL be decrypted. - * * `mac` MUST point to a buffer where the 16 byte MAC is placed. + * This function returns `NULL` on error (errors are logged). * - * Returns `-1` on error (authentication failed). + * If the map is a hash map, overwriting the value (while keeping the key) is + * possible. In this case the `old` pointer is optional, and if set than the old + * data will be copied to over during an overwrite. + * + * NOTE: the function returns a pointer to the map's internal storage. */ -SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, - void *restrict data, - size_t len, - const void *ad, /* additional data */ - size_t adlen, - const void *key, - const void *nounce); +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, set_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE val, + FIO_MAP2_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP2_KEY key +#endif + ); +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns a pointer to the map's internal storage. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, get_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key); /* ***************************************************************************** -Using ChaCha20 and Poly1305 separately +Map Iteration and Traversal ***************************************************************************** */ /** - * Performs an in-place encryption/decryption of `data` using ChaCha20. + * Returns the next iterator object after `current_pos` or the first if `NULL`. * - * * `key` MUST point to a 256 bit long memory address (32 Bytes). - * * `nounce` MUST point to a 96 bit long memory address (12 Bytes). - * * `counter` is the block counter, usually 1 unless `data` is mid-cyphertext. + * Note that adding objects to the map or rehashing between iterations could + * incur performance penalties when re-setting and re-seeking the previous + * iterator position. + * + * Adding objects to, or rehashing, an unordered maps could invalidate the + * iterator object completely as the ordering may have changed and so the "next" + * object might be any object in the map. */ -SFUNC void fio_chacha20(void *restrict data, - size_t len, - const void *key, - const void *nounce, - uint32_t counter); +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_next)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos); /** - * Given a Poly1305 256bit (32 byte) key, writes the authentication code for the - * poly message and additional data into `mac_dest`. + * Returns the next iterator object after `current_pos` or the last if `NULL`. * - * * `key` MUST point to a 256 bit long memory address (32 Bytes). + * See notes in `get_next`. */ -SFUNC void fio_poly1305_auth(void *restrict mac_dest, - const void *key256bits, - void *restrict message, - size_t len, - const void *additional_data, - size_t additional_data_len); +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_prev)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos); -/* ***************************************************************************** -ChaCha20Poly1305 Implementation -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/** Returns 1 if the iterator is out of bounds, otherwise returns 0. */ +FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP2_NAME, iterator_s) * + iterator); -/* ***************************************************************************** -Poly1305 (authentication) -Prime 2^130-5 = 0x3FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFB -The math is mostly copied from: https://github.com/floodyberry/poly1305-donna -***************************************************************************** */ -/* - * Math copied from https://github.com/floodyberry/poly1305-donna +/** Returns a pointer to the node object in the internal map. */ +FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, + iterator2node)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * iterator); + +#ifndef FIO_MAP2_EACH +/** Iterates through the map using an iterator object. */ +#define FIO_MAP2_EACH(map_name, map_ptr, i) \ + for (FIO_NAME(map_name, iterator_s) \ + i = FIO_NAME(map_name, get_next)(map_ptr, NULL); \ + FIO_NAME(map_name, iterator_is_valid)(&i); \ + i = FIO_NAME(map_name, get_next)(map_ptr, &i)) +/** Iterates through the map using an iterator object. */ +#define FIO_MAP2_EACH_REVERSED(map_name, map_ptr, i) \ + for (FIO_NAME(map_name, iterator_s) \ + i = FIO_NAME(map_name, get_prev)(map_ptr, NULL); \ + FIO_NAME(map_name, iterator_is_valid)(&i); \ + i = FIO_NAME(map_name, get_prev)(map_ptr, &i)) +#endif + +/** Iteration information structure passed to the callback. */ +typedef struct FIO_NAME(FIO_MAP2_NAME, each_s) { + /** The being iterated. Once set, cannot be safely changed. */ + FIO_MAP2_PTR const parent; + /** The current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct FIO_NAME(FIO_MAP2_NAME, each_s) * info); + /** Opaque user data. */ + void *udata; +#ifdef FIO_MAP2_VALUE + /** The object's value at the current index. */ + FIO_MAP2_VALUE value; +#endif + /** The object's key the current index. */ + FIO_MAP2_KEY key; +} FIO_NAME(FIO_MAP2_NAME, each_s); + +/** + * Iteration using a callback for each element in the map. * - * With thanks to Andrew Moon. + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. */ -typedef struct { - /* r (cycle key addition) is 128 bits */ - uint64_t r[3]; - /* s (final key addition) is 128 bits */ - uint64_t s[2]; - /* Accumulator should not exceed 131 bits at the end of every cycle. */ - uint64_t a[3]; -} FIO_ALIGN(16) fio___poly_s; +SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, + each)(FIO_MAP2_PTR map, + int (*task)(FIO_NAME(FIO_MAP2_NAME, each_s) *), + void *udata, + ssize_t start_at); -FIO_IFUNC fio___poly_s fio___poly_init(const void *key256b) { - static const uint64_t defkey[4] = {0}; - if (!key256b) - key256b = (const void *)defkey; - uint64_t t0, t1; - /* r &= 0xffffffc0ffffffc0ffffffc0fffffff */ - t0 = fio_buf2u64_le((uint8_t *)key256b + 0); - t1 = fio_buf2u64_le((uint8_t *)key256b + 8); - fio___poly_s pl = { - .r = - { - ((t0)&0xffc0fffffff), - (((t0 >> 44) | (t1 << 20)) & 0xfffffc0ffff), - (((t1 >> 24)) & 0x00ffffffc0f), - }, - .s = - { - fio_buf2u64_le(((uint8_t *)key256b + 16)), - fio_buf2u64_le(((uint8_t *)key256b + 24)), - }, - }; - return pl; -} -FIO_IFUNC void fio___poly_consume128bit(fio___poly_s *pl, - const void *msg, - uint64_t is_full) { - uint64_t r0, r1, r2; - uint64_t s1, s2; - uint64_t a0, a1, a2; - uint64_t c; - uint64_t d0[2], d1[2], d2[2], d[2]; +/* ***************************************************************************** +Optional Sorting Support - TODO? (convert to array, sort, rehash) +***************************************************************************** */ - r0 = pl->r[0]; - r1 = pl->r[1]; - r2 = pl->r[2]; +#if defined(FIO_MAP2_KEY_IS_GREATER_THAN) && !defined(FIO_SORT_TYPE) && \ + FIO_MAP2_ORDERED +#undef FIO_SORT_NAME +#endif - a0 = pl->a[0]; - a1 = pl->a[1]; - a2 = pl->a[2]; +/* ***************************************************************************** +Map Implementation - inlined static functions +***************************************************************************** */ - s1 = r1 * (5 << 2); - s2 = r2 * (5 << 2); +#ifndef FIO_MAP2_CAPA_BITS_LIMIT +/* Note: cannot be more than 31 bits unless some of the code is rewritten. */ +#define FIO_MAP2_CAPA_BITS_LIMIT 31 +#endif - { - uint64_t t0, t1; - t0 = fio_buf2u64_le(msg); - t1 = fio_buf2u64_le(((uint8_t *)msg + 8)); - /* a += msg */ - a0 += ((t0)&0xFFFFFFFFFFF); - a1 += (((t0 >> 44) | (t1 << 20)) & 0xFFFFFFFFFFF); - a2 += (((t1 >> 24)) & 0x3FFFFFFFFFF) | (is_full << 40); - } +/* Theoretical map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, capa)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + FIO_MAP2_T *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (o->map) + return (uint32_t)((size_t)1ULL << o->bits); + return 0; +} - /* a *= r */ - d0[0] = fio_math_mulc64(a0, r0, d0 + 1); - d[0] = fio_math_mulc64(a1, s2, d + 1); - d0[0] = fio_math_addc64(d0[0], d[0], 0, &c); - d0[1] += d[1] + c; +/* The number of objects in the map capacity. */ +FIO_IFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, count)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 0); + return ((FIO_NAME(FIO_MAP2_NAME, s) *)FIO_PTR_UNTAG(map))->count; +} - d[0] = fio_math_mulc64(a2, s1, d + 1); - d0[0] = fio_math_addc64(d0[0], d[0], 0, &c); - d0[1] += d[1] + c; +/** Returns 1 if the iterator points to a valid object, otherwise returns 0. */ +FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, + iterator_is_valid)(FIO_NAME(FIO_MAP2_NAME, iterator_s) * + iterator) { + return (iterator && iterator->private_.map_validator); +} - d1[0] = fio_math_mulc64(a0, r1, d1 + 1); - d[0] = fio_math_mulc64(a1, r0, d + 1); - d1[0] = fio_math_addc64(d1[0], d[0], 0, &c); - d1[1] += d[1] + c; +/** Returns the key value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, + node2key)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node) { + FIO_MAP2_KEY r = (FIO_MAP2_KEY){0}; + if (!node) + return r; + return FIO_MAP2_KEY_FROM_INTERNAL(node->key); +} - d[0] = fio_math_mulc64(a2, s2, d + 1); - d1[0] = fio_math_addc64(d1[0], d[0], 0, &c); - d1[1] += d[1] + c; +/** Returns the hash value associated with the node's pointer. */ +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + node2hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + uint32_t r = (uint32_t){0}; + if (!node) + return r; +#if FIO_MAP2_RECALC_HASH + FIO_MAP2_KEY k = FIO_MAP2_KEY_FROM_INTERNAL(node->key); + uint64_t hash = FIO_MAP2_HASH_FN(k); + hash += !hash; + return hash; +#else + return node->hash; +#endif +} - d2[0] = fio_math_mulc64(a0, r2, d2 + 1); - d[0] = fio_math_mulc64(a1, r1, d + 1); - d2[0] = fio_math_addc64(d2[0], d[0], 0, &c); - d2[1] += d[1] + c; +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_VALUE FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node) { + FIO_MAP2_VALUE r = (FIO_MAP2_VALUE){0}; + if (!node) + return r; + return FIO_MAP2_VALUE_FROM_INTERNAL(node->value); +} +#else +/* If called for a node without a value, returns the key (simplifies stuff). */ +FIO_IFUNC FIO_MAP2_KEY FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, node_s) * + node) { + return FIO_NAME(FIO_MAP2_NAME, node2key)(node); +} +#endif - d[0] = fio_math_mulc64(a2, r0, d + 1); - d2[0] = fio_math_addc64(d2[0], d[0], 0, &c); - d2[1] += d[1] + c; +/** Returns the key value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2key_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + if (!node) + return NULL; + return &(node->key); +} - /* (partial) a %= p */ - c = (d0[0] >> 44) | (d0[1] << 20); - a0 = d0[0] & 0xfffffffffff; - d1[0] = fio_math_addc64(d1[0], c, 0, &c); - d1[1] += c; +#ifdef FIO_MAP2_VALUE +/** Returns the value associated with the node's pointer. */ +FIO_IFUNC FIO_MAP2_VALUE_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + if (!node) + return NULL; + return &(node->value); +} +#else +/* If called for a node without a value, returns the key (simplifies stuff). */ +FIO_IFUNC FIO_MAP2_KEY_INTERNAL *FIO_NAME(FIO_MAP2_NAME, node2val_ptr)( + FIO_NAME(FIO_MAP2_NAME, node_s) * node) { + return FIO_NAME(FIO_MAP2_NAME, node2key_ptr)(node); +} +#endif - c = (d1[0] >> 44) | (d1[1] << 20); - a1 = d1[0] & 0xfffffffffff; - d2[0] = fio_math_addc64(d2[0], c, 0, &c); - d2[1] += c; +/** Gets a value from the map, if exists. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, get)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key) { + return FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, get_ptr)(map, +#if !defined(FIO_MAP2_HASH_FN) + hash, +#endif + key)); +} - c = (d2[0] >> 42) | (d2[1] << 22); - a2 = d2[0] & 0x3ffffffffff; - a0 += c * 5; - c = a0 >> 44; - a0 = a0 & 0xfffffffffff; - a1 += c; +/** Sets a value in the map, hash maps will overwrite existing data if any. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE obj, + FIO_MAP2_VALUE_INTERNAL *old +#else + FIO_MAP2_KEY key +#endif +) { + return FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, set_ptr)(map, +#if !defined(FIO_MAP2_HASH_FN) + hash, +#endif + key +#ifdef FIO_MAP2_VALUE + , + obj, + old, + 1 +#endif + )); +} - pl->a[0] = a0; - pl->a[1] = a1; - pl->a[2] = a2; +/** Sets a value in the map if not set previously. */ +FIO_IFUNC FIO_MAP2_GET_T FIO_NAME(FIO_MAP2_NAME, + set_if_missing)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key +#ifdef FIO_MAP2_VALUE + , + FIO_MAP2_VALUE obj +#endif +) { + return FIO_NAME(FIO_MAP2_NAME, + node2val)(FIO_NAME(FIO_MAP2_NAME, set_ptr)(map, +#if !defined(FIO_MAP2_HASH_FN) + hash, +#endif + key +#ifdef FIO_MAP2_VALUE + , + obj, + NULL, + 0 +#endif + )); } -FIO_IFUNC void fio___poly_finilize(fio___poly_s *pl) { - uint64_t a0, a1, a2, c; - uint64_t g0, g1, g2; - uint64_t t0, t1; +/** Returns a pointer to the node object in the internal map. */ +FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, + iterator2node)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * iterator) { + FIO_NAME(FIO_MAP2_NAME, node_s) *node = NULL; + if (!iterator || !iterator->private_.map_validator) + return node; + FIO_PTR_TAG_VALID_OR_RETURN(map, node); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + node = o->map + iterator->private_.index; + return node; +} - /* fully carry a */ - a0 = pl->a[0]; - a1 = pl->a[1]; - a2 = pl->a[2]; +/* ***************************************************************************** +Map Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - c = (a1 >> 44); - a1 &= 0xFFFFFFFFFFF; - a2 += c; - c = (a2 >> 42); - a2 &= 0x3FFFFFFFFFF; - a0 += c * 5; - c = (a0 >> 44); - a0 &= 0xFFFFFFFFFFF; - a1 += c; - c = (a1 >> 44); - a1 &= 0xFFFFFFFFFFF; - a2 += c; - c = (a2 >> 42); - a2 &= 0x3FFFFFFFFFF; - a0 += c * 5; - c = (a0 >> 44); - a0 &= 0xFFFFFFFFFFF; - a1 += c; +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP2_NAME, s)) +FIO_LEAK_COUNTER_DEF(FIO_NAME(FIO_MAP2_NAME, destroy)) +/* ***************************************************************************** +Constructors +***************************************************************************** */ - /* compute a + -p */ - g0 = a0 + 5; - c = (g0 >> 44); - g0 &= 0xFFFFFFFFFFF; - g1 = a1 + c; - c = (g1 >> 44); - g1 &= 0xFFFFFFFFFFF; - g2 = a2 + c - ((uint64_t)1 << 42); +/* do we have a constructor? */ +#ifndef FIO_REF_CONSTRUCTOR_ONLY +/* Allocates a new object on the heap and initializes it's memory. */ +FIO_IFUNC FIO_MAP2_PTR FIO_NAME(FIO_MAP2_NAME, new)(void) { + FIO_NAME(FIO_MAP2_NAME, s) *o = + (FIO_NAME(FIO_MAP2_NAME, s) *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*o), 0); + if (!o) + return (FIO_MAP2_PTR)NULL; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP2_NAME, s)); + *o = (FIO_NAME(FIO_MAP2_NAME, s))FIO_MAP2_INIT; + return (FIO_MAP2_PTR)FIO_PTR_TAG(o); +} +/* Frees any internal data AND the object's container! */ +FIO_IFUNC void FIO_NAME(FIO_MAP2_NAME, free)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, destroy)(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP2_NAME, s), map); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP2_NAME, s)); + FIO_MEM_FREE_(o, sizeof(*o)); +} +#endif /* FIO_REF_CONSTRUCTOR_ONLY */ - /* select h if h < p, or h + -p if h >= p */ - c = (g2 >> ((sizeof(uint64_t) * 8) - 1)) - 1; - g0 &= c; - g1 &= c; - g2 &= c; - c = ~c; - a0 = (a0 & c) | g0; - a1 = (a1 & c) | g1; - a2 = (a2 & c) | g2; +/* ***************************************************************************** +Internal Helpers +***************************************************************************** */ - /* a = (a + Poly S key) */ - t0 = pl->s[0]; - t1 = pl->s[1]; +#ifndef FIO_MAP2_ATTACK_LIMIT +#define FIO_MAP2_ATTACK_LIMIT 16 +#endif +#ifndef FIO_MAP2_CUCKOO_STEPS +/* Prime numbers are better */ +#define FIO_MAP2_CUCKOO_STEPS (0x43F82D0BUL) /* a big high prime */ +#endif +#ifndef FIO_MAP2_SEEK_LIMIT +#define FIO_MAP2_SEEK_LIMIT 13U +#endif +#ifndef FIO_MAP2_ARRAY_LOG_LIMIT +#define FIO_MAP2_ARRAY_LOG_LIMIT 3 +#endif +#ifndef FIO_MAP2_CAPA +#define FIO_MAP2_CAPA(bits) ((size_t)1ULL << (bits)) +#endif - a0 += ((t0)&0xFFFFFFFFFFF); - c = (a0 >> 44); - a0 &= 0xFFFFFFFFFFF; - a1 += (((t0 >> 44) | (t1 << 20)) & 0xFFFFFFFFFFF) + c; - c = (a1 >> 44); - a1 &= 0xFFFFFFFFFFF; - a2 += (((t1 >> 24)) & 0x3FFFFFFFFFF) + c; - a2 &= 0x3FFFFFFFFFF; +#ifndef FIO_MAP2_IS_SPARSE +#define FIO_MAP2_IS_SPARSE(map) \ + (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT && ((capa >> 2) > o->count)) +#endif - /* mac = a % (2^128) */ - a0 = ((a0) | (a1 << 44)); - a1 = ((a1 >> 20) | (a2 << 24)); - pl->a[0] = a0; - pl->a[1] = a1; +/* The number of objects in the map capacity. */ +FIO_IFUNC uint8_t *FIO_NAME(FIO_MAP2_NAME, + __imap)(FIO_NAME(FIO_MAP2_NAME, s) * o) { + // FIO_ASSERT(o && o->map, "shouldn't have been called."); + return (uint8_t *)(o->map + FIO_MAP2_CAPA(o->bits)); } -FIO_IFUNC void fio___poly_consume_msg(fio___poly_s *pl, - uint8_t *msg, - size_t len) { - /* read 16 byte blocks */ - uint64_t n[2]; - for (size_t i = 31; i < len; i += 32) { - fio___poly_consume128bit(pl, msg, 1); - fio___poly_consume128bit(pl, msg + 16, 1); - msg += 32; - } - if ((len & 16)) { - fio___poly_consume128bit(pl, msg, 1); - msg += 16; - } - if ((len & 15)) { - n[0] = 0; - n[1] = 0; - fio_memcpy15x(n, msg, len); - n[0] = fio_ltole64(n[0]); - n[1] = fio_ltole64(n[1]); - ((uint8_t *)n)[len & 15] = 0x01; - fio___poly_consume128bit(pl, (void *)n, 0); - } +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + __byte_hash)(FIO_NAME(FIO_MAP2_NAME, s) * o, + uint64_t hash) { + hash = (hash >> o->bits); + hash &= 0xFF; + hash += !(hash); + hash -= (hash == 255); + return hash; } -/* Given a Poly1305 key, writes a MAC into `mac_dest`. */ -SFUNC void fio_poly1305_auth(void *restrict mac, - const void *key, - void *restrict msg, - size_t len, - const void *ad, - size_t ad_len) { - fio___poly_s pl = fio___poly_init(key); - fio___poly_consume_msg(&pl, (uint8_t *)ad, ad_len); - fio___poly_consume_msg(&pl, (uint8_t *)msg, len); - fio___poly_finilize(&pl); - fio_u2buf64_le(mac, pl.a[0]); - fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); +FIO_IFUNC uint64_t FIO_NAME(FIO_MAP2_NAME, + __is_eq_hash)(FIO_NAME(FIO_MAP2_NAME, node_s) * o, + uint64_t hash) { +#if FIO_MAP2_RECALC_HASH && defined(FIO_MAP2_HASH_FN) + uint64_t khash = FIO_MAP2_HASH_FN(FIO_MAP2_KEY_FROM_INTERNAL(o->key)); + khash += !khash; +#else + const uint64_t khash = o->hash; +#endif + return (khash == hash); } -/* ***************************************************************************** -ChaCha20 (encryption) -***************************************************************************** */ - -#define FIO___CHACHA_VROUND(count, a, b, c, d) \ - for (size_t i = 0; i < count; ++i) { \ - a[i] += b[i]; \ - d[i] ^= a[i]; \ - d[i] = (d[i] << 16) | (d[i] >> (32 - 16)); \ - c[i] += d[i]; \ - b[i] ^= c[i]; \ - b[i] = (b[i] << 12) | (b[i] >> (32 - 12)); \ - a[i] += b[i]; \ - d[i] ^= a[i]; \ - d[i] = (d[i] << 8) | (d[i] >> (32 - 8)); \ - c[i] += d[i]; \ - b[i] ^= c[i]; \ - b[i] = (b[i] << 7) | (b[i] >> (32 - 7)); \ +FIO_SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, + __index)(FIO_NAME(FIO_MAP2_NAME, s) * o, + FIO_MAP2_KEY key, + uint64_t hash) { + uint32_t r = (uint32_t)-1; + if (!o->map) + return r; + static int guard_print = 0; + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + size_t capa = FIO_MAP2_CAPA(o->bits); + size_t bhash = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(o, hash); + size_t guard = FIO_MAP2_ATTACK_LIMIT + 1; + if (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT) { /* treat as map */ + uint64_t bhash64 = bhash | (bhash << 8); + bhash64 |= bhash64 << 16; + bhash64 |= bhash64 << 32; + bhash64 = ~bhash64; + const uintptr_t pos_mask = capa - 1; + const uint_fast8_t offsets[8] = {0, 3, 8, 17, 28, 41, 58, 60}; + for (uintptr_t pos = hash, c = 0; c < FIO_MAP2_SEEK_LIMIT; + (pos += FIO_MAP2_CUCKOO_STEPS), ++c) { + uint64_t comb = imap[(pos + offsets[0]) & pos_mask]; + comb |= ((uint64_t)imap[(pos + offsets[1]) & pos_mask]) << (1 * 8); + comb |= ((uint64_t)imap[(pos + offsets[2]) & pos_mask]) << (2 * 8); + comb |= ((uint64_t)imap[(pos + offsets[3]) & pos_mask]) << (3 * 8); + comb |= ((uint64_t)imap[(pos + offsets[4]) & pos_mask]) << (4 * 8); + comb |= ((uint64_t)imap[(pos + offsets[5]) & pos_mask]) << (5 * 8); + comb |= ((uint64_t)imap[(pos + offsets[6]) & pos_mask]) << (6 * 8); + comb |= ((uint64_t)imap[(pos + offsets[7]) & pos_mask]) << (7 * 8); + const uint64_t has_possible_match = + (((comb ^ bhash64) & 0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & + 0x8080808080808080ULL; + if (has_possible_match) { + /* there was a 7 bit match in one of the bytes in this 8 byte group */ + for (size_t i = 0; i < 8; ++i) { + const uint32_t tmp = (uint32_t)((pos + offsets[i]) & pos_mask); + if (imap[tmp] != bhash) + continue; + /* test key and hash equality */ + if (FIO_NAME(FIO_MAP2_NAME, __is_eq_hash)(o->map + tmp, hash)) { + if (FIO_MAP2_KEY_CMP(o->map[tmp].key, key)) { + guard_print = 0; + return (r = tmp); + } + if (!(--guard)) { + if (!guard_print) + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP2_NAME, s)) " under attack?"); + guard_print = 1; + return (r = tmp); + } + } + } + } + const uint64_t has_possible_full_byte = + (((comb)&0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & + 0x8080808080808080ULL; + const uint64_t has_possible_empty_byte = + (((~comb) & 0x7F7F7F7F7F7F7F7FULL) + 0x0101010101010101ULL) & + 0x8080808080808080ULL; + if (!(has_possible_full_byte | has_possible_empty_byte)) + continue; + /* there was a 7 bit match for a possible free space in this group */ + for (int i = 0; i < 8; ++i) { + const uint32_t tmp = (uint32_t)((pos + offsets[i]) & pos_mask); + if (!imap[tmp]) + return (r = tmp); /* empty slot always ends search */ + if (r > pos_mask && imap[tmp] == 255) + r = tmp; /* mark hole to be filled */ + } + } + return r; + } /* treat as array */ + for (size_t i = 0; i < capa; ++i) { + if (!imap[i]) + return (r = (uint32_t)i); + if (imap[i] == bhash) { + /* test key and hash equality */ + if (FIO_NAME(FIO_MAP2_NAME, __is_eq_hash)(o->map + i, hash)) { + if (FIO_MAP2_KEY_CMP(o->map[i].key, key)) { + guard_print = 0; + return (r = (uint32_t)i); + } + if (!(--guard)) { + if (!guard_print) + FIO_LOG_SECURITY("hash map " FIO_MACRO2STR( + FIO_NAME(FIO_MAP2_NAME, s)) " under attack?"); + guard_print = 1; + return (r = (uint32_t)i); + } + } + } + if (imap[i] == 0xFF) + r = (uint32_t)i; /* a free spot is available*/ } - -FIO_IFUNC fio_u512 fio___chacha_init(const void *key, - const void *nounce, - uint32_t counter) { - fio_u512 o = { - .u32 = - { - // clang-format off - 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574, - fio_buf2u32_le(key), - fio_buf2u32_le((uint8_t *)key + 4), - fio_buf2u32_le((uint8_t *)key + 8), - fio_buf2u32_le((uint8_t *)key + 12), - fio_buf2u32_le((uint8_t *)key + 16), - fio_buf2u32_le((uint8_t *)key + 20), - fio_buf2u32_le((uint8_t *)key + 24), - fio_buf2u32_le((uint8_t *)key + 28), - counter, - fio_buf2u32_le(nounce), - fio_buf2u32_le((uint8_t *)nounce + 4), - fio_buf2u32_le((uint8_t *)nounce + 8), - }, // clang-format on - }; - return o; + return r; +} +/* deallocate the map's memory. */ +FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, + __dealloc_map)(FIO_NAME(FIO_MAP2_NAME, s) * o) { + if (!o->map) + return; + const size_t capa = FIO_MAP2_CAPA(o->bits); + FIO_LEAK_COUNTER_ON_FREE(FIO_NAME(FIO_MAP2_NAME, destroy)); + FIO_MEM_FREE_(o->map, (capa * sizeof(*o->map)) + capa); + (void)capa; } -FIO_SFUNC void fio___chacha_vround20(const fio_u512 c, uint8_t *restrict data) { - uint32_t v[16]; - for (size_t i = 0; i < 16; ++i) { - v[i] = c.u32[i]; - } - for (size_t round__ = 0; round__ < 10; ++round__) { /* 2 rounds per loop */ - FIO___CHACHA_VROUND(4, v, (v + 4), (v + 8), (v + 12)); - fio_u32x4_reshuffle((v + 4), 1, 2, 3, 0); - fio_u32x4_reshuffle((v + 8), 2, 3, 0, 1); - fio_u32x4_reshuffle((v + 12), 3, 0, 1, 2); - FIO___CHACHA_VROUND(4, v, (v + 4), (v + 8), (v + 12)); - fio_u32x4_reshuffle((v + 4), 3, 0, 1, 2); - fio_u32x4_reshuffle((v + 8), 2, 3, 0, 1); - fio_u32x4_reshuffle((v + 12), 1, 2, 3, 0); - } - for (size_t i = 0; i < 16; ++i) { - v[i] += c.u32[i]; - } - -#if __BIG_ENDIAN__ - for (size_t i = 0; i < 16; ++i) { - v[i] = fio_bswap32(v[i]); - } +/** duplicates an objects between two maps. */ +FIO_IFUNC int FIO_NAME(FIO_MAP2_NAME, + __copy_obj)(FIO_NAME(FIO_MAP2_NAME, s) * dest, + FIO_NAME(FIO_MAP2_NAME, node_s) * o, + uint32_t internal) { + FIO_MAP2_KEY key = FIO_MAP2_KEY_FROM_INTERNAL(o->key); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(dest); +#if FIO_MAP2_RECALC_HASH + uint64_t ohash = FIO_MAP2_HASH_FN(key); + ohash += !ohash; +#else + const uint64_t ohash = o->hash; #endif - { - uint32_t d[16]; - fio_memcpy64(d, data); - for (size_t i = 0; i < 16; ++i) { - d[i] ^= v[i]; + uint32_t i = FIO_NAME(FIO_MAP2_NAME, __index)(dest, key, ohash); + if (i == (uint32_t)-1 || (imap[i] + 1) > 1) + return -1; + if (internal) { + dest->map[i] = *o; + imap[i] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(dest, ohash); +#if FIO_MAP2_ORDERED + if (dest->count) { /* update ordering */ + FIO_INDEXED_LIST_PUSH(dest->map, node, dest->head, i); + } else { /* set first order */ + dest->map[i].node.next = dest->map[i].node.prev = i; + dest->head = i; } - fio_memcpy64(data, d); +#endif + ++dest->count; + return 0; + } + imap[i] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(dest, ohash); + FIO_MAP2_KEY_COPY(dest->map[i].key, FIO_MAP2_KEY_FROM_INTERNAL(o->key)); + FIO_MAP2_VALUE_COPY(dest->map[i].value, + FIO_MAP2_VALUE_FROM_INTERNAL(o->value)); +#if !FIO_MAP2_RECALC_HASH + dest->map[i].hash = o->hash; +#endif +#if FIO_MAP2_ORDERED + if (dest->count) { /* update ordering */ + FIO_INDEXED_LIST_PUSH(dest->map, node, dest->head, i); + } else { /* set first order */ + dest->map[i].node.next = dest->map[i].node.prev = i; + dest->head = i; } +#endif + ++dest->count; + return 0; } -FIO_SFUNC void fio___chacha_vround20x2(fio_u512 c, uint8_t *restrict data) { - uint32_t v[32]; - for (size_t i = 0; i < 16; ++i) { - v[i + (i & (4 | 8))] = c.u32[i]; - v[i + 4 + (i & (4 | 8))] = c.u32[i]; - } - ++v[28]; - for (size_t round__ = 0; round__ < 10; ++round__) { /* 2 rounds per loop */ - FIO___CHACHA_VROUND(8, v, (v + 8), (v + 16), (v + 24)); - fio_u32x8_reshuffle((v + 8), 1, 2, 3, 0, 5, 6, 7, 4); - fio_u32x8_reshuffle((v + 16), 2, 3, 0, 1, 6, 7, 4, 5); - fio_u32x8_reshuffle((v + 24), 3, 0, 1, 2, 7, 4, 5, 6); - FIO___CHACHA_VROUND(8, v, (v + 8), (v + 16), (v + 24)); - fio_u32x8_reshuffle((v + 8), 3, 0, 1, 2, 7, 4, 5, 6); - fio_u32x8_reshuffle((v + 16), 2, 3, 0, 1, 6, 7, 4, 5); - fio_u32x8_reshuffle((v + 24), 1, 2, 3, 0, 5, 6, 7, 4); +/** duplicates a map to a new copy (usually for rehashing / reserving space). */ +FIO_IFUNC FIO_NAME(FIO_MAP2_NAME, s) + FIO_NAME(FIO_MAP2_NAME, __duplicate)(FIO_NAME(FIO_MAP2_NAME, s) * o, + uint32_t bits, + uint32_t internal) { + FIO_NAME(FIO_MAP2_NAME, s) cpy = {0}; + if (bits > FIO_MAP2_CAPA_BITS_LIMIT) + return cpy; + size_t capa = FIO_MAP2_CAPA(bits); + cpy.map = (FIO_NAME(FIO_MAP2_NAME, node_s) *) + FIO_MEM_REALLOC_(NULL, 0, ((capa * sizeof(*cpy.map)) + capa), 0); + if (!cpy.map) + return cpy; + FIO_LEAK_COUNTER_ON_ALLOC(FIO_NAME(FIO_MAP2_NAME, destroy)); + if (!FIO_MEM_REALLOC_IS_SAFE_) { + /* set only the imap, the rest can be junk data */ + FIO_MEMSET((cpy.map + capa), 0, capa); } - for (size_t i = 0; i < 16; ++i) { - v[i + (i & (4 | 8))] += c.u32[i]; - v[i + 4 + (i & (4 | 8))] += c.u32[i]; + cpy.bits = bits; + if (!o->count) + return cpy; +#if FIO_MAP2_ORDERED + /* copy objects in order */ + FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { + if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + i, internal)) + goto error; } - ++v[28]; - -#if __BIG_ENDIAN__ - for (size_t i = 0; i < 32; ++i) { - v[i] = fio_bswap32(v[i]); +#else + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + capa = FIO_MAP2_CAPA(o->bits); + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + for (size_t i = 0; i < capa; i += 8) { + uint64_t comb = *((uint64_t *)(imap + i)); + if (!comb || comb == 0xFFFFFFFFFFFFFFFFULL) + continue; + for (size_t j = 0; j < 8; ++j) { + const size_t tmp = j + i; + if (!imap[tmp] || imap[tmp] == 0xFF) + continue; + if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + tmp, internal)) + goto error; + } + } + return cpy; + } /* review as array */ + for (size_t i = 0; i < capa; ++i) { + if (!imap[i] || imap[i] == 0xFF) + continue; + if (FIO_NAME(FIO_MAP2_NAME, __copy_obj)(&cpy, o->map + i, internal)) + goto error; } #endif - { - fio_u32x8_reshuffle((v + 4), 4, 5, 6, 7, 0, 1, 2, 3); - fio_u32x8_reshuffle((v + 20), 4, 5, 6, 7, 0, 1, 2, 3); - uint32_t d[8]; - fio_memcpy32(d, data); - for (size_t i = 0; i < 8; ++i) { - d[i] ^= v[i]; - } - fio_memcpy32(data, d); - - fio_memcpy32(d, data + 32); - for (size_t i = 0; i < 8; ++i) { - d[i] ^= v[16 + i]; - } - fio_memcpy32(data + 32, d); + return cpy; +error: + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(&cpy); + cpy = (FIO_NAME(FIO_MAP2_NAME, s)){0}; + return cpy; +} - fio_memcpy32(d, data + 64); - for (size_t i = 0; i < 8; ++i) { - d[i] ^= v[8 + i]; +/* destroys all objects in the map, without(!) resetting the `imap`. */ +FIO_SFUNC void FIO_NAME(FIO_MAP2_NAME, + __destroy_objects)(FIO_NAME(FIO_MAP2_NAME, s) * o) { +#if FIO_MAP2_VALUE_DESTROY_SIMPLE && FIO_MAP2_KEY_DESTROY_SIMPLE + (void)o; + return; +#else + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + const size_t capa = FIO_MAP2_CAPA(o->bits); + if (FIO_MAP2_IS_SPARSE(o)) { + for (size_t i = 0; i < capa; i += 8) { + uint64_t comb = *((uint64_t *)(imap + i)); + if (!comb || comb == 0xFFFFFFFFFFFFFFFFULL) + continue; + for (size_t j = i; j < i + 8; ++j) { + FIO_MAP2_KEY_DESTROY(o->map[j].key); + FIO_MAP2_VALUE_DESTROY(o->map[j].value); + } } - fio_memcpy32(data + 64, d); - - fio_memcpy32(d, data + 96); - for (size_t i = 0; i < 8; ++i) { - d[i] ^= v[24 + i]; + } else { /* review as array */ + for (size_t i = 0; i < capa; ++i) { + if (!imap[i] || imap[i] == 0xFF) + continue; + FIO_MAP2_KEY_DESTROY(o->map[i].key); + FIO_MAP2_VALUE_DESTROY(o->map[i].value); } - fio_memcpy32(data + 96, d); - } -} - -SFUNC void fio_chacha20(void *restrict data, - size_t len, - const void *key, - const void *nounce, - uint32_t counter) { - fio_u512 c = fio___chacha_init(key, nounce, counter); - for (size_t pos = 127; pos < len; pos += 128) { - fio___chacha_vround20x2(c, (uint8_t *)data); - c.u32[12] += 2; /* block counter */ - data = (void *)((uint8_t *)data + 128); - } - if ((len & 64)) { - fio___chacha_vround20(c, (uint8_t *)data); - data = (void *)((uint8_t *)data + 64); - ++c.u32[12]; - } - if ((len & 63)) { - fio_u512 dest; /* no need to initialize, junk data disregarded. */ - fio_memcpy63x(dest.u64, data, len); - fio___chacha_vround20(c, dest.u8); - fio_memcpy63x(data, dest.u64, len); } +#endif /* FIO_MAP2_VALUE_DESTROY_SIMPLE */ } /* ***************************************************************************** -ChaCha20Poly1305 Encryption with Authentication +API implementation ***************************************************************************** */ -FIO_IFUNC fio_u512 fio___chacha20_mixround(fio_u512 c) { - fio_u512 k = {.u64 = {0}}; - fio___chacha_vround20(c, k.u8); - return k; +/** Reserves at minimum the capacity requested. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, reserve)(FIO_MAP2_PTR map, size_t capa) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + capa += o->count; + if (FIO_MAP2_CAPA(o->bits) >= capa || (capa >> FIO_MAP2_CAPA_BITS_LIMIT)) + return; + uint_fast8_t bits = o->bits + 1; + while (FIO_MAP2_CAPA(bits) < capa) + ++bits; + FIO_NAME(FIO_MAP2_NAME, s) + cpy = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, bits, 1); + if (!cpy.map) + return; + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = cpy; } -SFUNC void fio_chacha20_poly1305_enc(void *restrict mac, - void *restrict data, - size_t len, - const void *ad, /* additional data */ - size_t adlen, - const void *key, - const void *nounce) { - fio_u512 c = fio___chacha_init(key, nounce, 0); - fio___poly_s pl; - { - fio_u512 c2 = fio___chacha20_mixround(c); - pl = fio___poly_init(&c2); - } - ++c.u32[12]; /* block counter */ - for (size_t i = 31; i < adlen; i += 32) { - fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); - fio___poly_consume128bit(&pl, (uint8_t *)ad + 16, 1); - ad = (void *)((uint8_t *)ad + 32); - } - if (adlen & 16) { - fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); - ad = (void *)((uint8_t *)ad + 16); - } - if (adlen & 15) { - uint64_t tmp[2] = {0}; /* 16 byte pad */ - fio_memcpy15x(tmp, ad, adlen); - fio___poly_consume128bit(&pl, (uint8_t *)tmp, 1); - } - for (size_t i = 127; i < len; i += 128) { - fio___chacha_vround20x2(c, (uint8_t *)data); - fio___poly_consume128bit(&pl, data, 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 16), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 32), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 48), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 64), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 80), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 96), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 112), 1); - c.u32[12] += 2; /* block counter */ - data = (void *)((uint8_t *)data + 128); - } - if ((len & 64)) { - fio___chacha_vround20(c, (uint8_t *)data); - fio___poly_consume128bit(&pl, data, 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 16), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 32), 1); - fio___poly_consume128bit(&pl, (void *)((uint8_t *)data + 48), 1); - ++c.u32[12]; /* block counter */ - data = (void *)((uint8_t *)data + 64); - } - if ((len & 63)) { - fio_u512 dest; - fio_memcpy63x(dest.u8, data, len); - fio___chacha_vround20(c, dest.u8); - fio_memcpy63x(data, dest.u8, len); - uint8_t *p = dest.u8; - if ((len & 32)) { - fio___poly_consume128bit(&pl, p, 1); - fio___poly_consume128bit(&pl, (p + 16), 1); - p += 32; - } - if ((len & 16)) { - fio___poly_consume128bit(&pl, p, 1); - p += 16; - } - if ((len & 15)) { - /* zero out poly padding */ - for (size_t i = (len & 15UL); i < 16; i++) - p[i] = 0; - fio___poly_consume128bit(&pl, p, 1); + +/* Removes all objects from the map, without releasing the map's resources. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, clear)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->map || !o->count) + return; + FIO_NAME(FIO_MAP2_NAME, __destroy_objects)(o); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + const size_t capa = FIO_MAP2_CAPA(o->bits); + FIO_MEMSET(imap, 0, capa); + o->count = 0; +#if FIO_MAP2_ORDERED + o->head = 0; +#endif +} + +/** Attempts to minimize memory use. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, compact)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->map || !o->count) + return; + uint32_t bits = o->bits; + while (FIO_MAP2_CAPA(bits >> 1) > o->count) + bits >>= 1; + ++bits; + for (;;) { + if (bits >= o->bits) + return; + FIO_NAME(FIO_MAP2_NAME, s) + cpy = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, bits, 1); + if (!cpy.map) { + ++bits; + continue; } + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = cpy; + return; } - { - uint64_t mac_data[2] = {fio_ltole64(adlen), fio_ltole64(len)}; - fio___poly_consume128bit(&pl, (uint8_t *)mac_data, 1); - } - fio___poly_finilize(&pl); - fio_u2buf64_le(mac, pl.a[0]); - fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); } -SFUNC void fio_chacha20_poly1305_auth(void *restrict mac, - void *restrict data, - size_t len, - const void *ad, /* additional data */ - size_t adlen, - const void *key, - const void *nounce) { - fio___poly_s pl; - { - fio_u512 c = fio___chacha_init(key, nounce, 0); - c = fio___chacha20_mixround(c); /* computes poly1305 key */ - pl = fio___poly_init(&c); - } - for (size_t i = 31; i < adlen; i += 32) { - fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); - fio___poly_consume128bit(&pl, (uint8_t *)ad + 16, 1); - ad = (void *)((uint8_t *)ad + 32); +/* Frees any internal data AND the object's container! */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, destroy)(FIO_MAP2_PTR map) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (o->map && o->count) + FIO_NAME(FIO_MAP2_NAME, __destroy_objects)(o); + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = (FIO_NAME(FIO_MAP2_NAME, s))FIO_MAP2_INIT; + return; +} + +/** Evicts elements least recently used (LRU), FIFO or undefined. */ +SFUNC void FIO_NAME(FIO_MAP2_NAME, evict)(FIO_MAP2_PTR map, + size_t number_of_elements) { + FIO_PTR_TAG_VALID_OR_RETURN_VOID(map); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return; + if (number_of_elements >= o->count) { + FIO_NAME(FIO_MAP2_NAME, clear)(map); + return; } - if (adlen & 16) { - fio___poly_consume128bit(&pl, (uint8_t *)ad, 1); - ad = (void *)((uint8_t *)ad + 16); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); +#ifdef FIO_MAP2_LRU /* remove last X elements from the list */ + FIO_INDEXED_LIST_EACH_REVERSED(o->map, node, o->head, i) { + FIO_MAP2_KEY_DESTROY(o->map[i].key); + FIO_MAP2_VALUE_DESTROY(o->map[i].value); + FIO_INDEXED_LIST_REMOVE(o->map, node, i); + imap[i] = 0xFF; + --o->count; + if (!(--number_of_elements)) + return; } - if (adlen & 15) { - uint64_t tmp[2] = {0}; /* 16 byte pad */ - fio_memcpy15x(tmp, ad, adlen); - fio___poly_consume128bit(&pl, (uint8_t *)tmp, 1); +#elif FIO_MAP2_ORDERED /* remove first X elements from the list */ + FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { + FIO_MAP2_KEY_DESTROY(o->map[i].key); + FIO_MAP2_VALUE_DESTROY(o->map[i].value); + FIO_INDEXED_LIST_REMOVE(o->map, node, i); + imap[i] = 0xFF; + --o->count; + if (!(--number_of_elements)) { + o->head = o->map[i].node.next; + return; + } } - fio___poly_consume_msg(&pl, (uint8_t *)data, (len & (~15ULL))); - if ((len & 15)) { - fio_u128 dest = {0}; /* 16 byte pad */ - fio_memcpy15x(dest.u64, (uint8_t *)data + (len & (~15ULL)), len); - fio___poly_consume128bit(&pl, (uint8_t *)(dest.u64), 1); +#else /* remove whatever... */ + if (o->bits > FIO_MAP2_ARRAY_LOG_LIMIT) { + /* map is scattered */ + uint32_t pos_mask = (uint32_t)(FIO_MAP2_CAPA(o->bits) - 1); + uint32_t pos = *(uint32_t *)o->map; + for (int i = 0; i < 3; ++i) { + struct timespec t = {0}; + clock_gettime(CLOCK_MONOTONIC, &t); + pos *= t.tv_nsec ^ t.tv_sec ^ (uintptr_t)imap; + pos ^= pos >> 7; + } + for (;;) { /* a bit of non-random randomness... */ + uint32_t offset = ((pos << 3)) & pos_mask; + for (uint_fast8_t i = 0; i < 8; ++i) { /* ordering bias? vs performance */ + const uint32_t tmp = offset + i; + if (!imap[tmp] || imap[tmp] == 0xFF) + continue; + FIO_MAP2_KEY_DESTROY(o->map[tmp].key); + FIO_MAP2_VALUE_DESTROY(o->map[tmp].value); + imap[tmp] = 0xFF; + --o->count; + if (!(--number_of_elements)) + return; + } + pos += FIO_MAP2_CUCKOO_STEPS; + } } - { - uint64_t mac_data[2] = {fio_ltole64(adlen), fio_ltole64(len)}; - fio___poly_consume128bit(&pl, (uint8_t *)mac_data, 1); + /* map is a simple array */ + while (number_of_elements--) { + FIO_MAP2_KEY_DESTROY(o->map[number_of_elements].key); + FIO_MAP2_VALUE_DESTROY(o->map[number_of_elements].value); + imap[number_of_elements] = 0xFF; } - fio___poly_finilize(&pl); - fio_u2buf64_le(mac, pl.a[0]); - fio_u2buf64_le(&((char *)mac)[8], pl.a[1]); -} - -SFUNC int fio_chacha20_poly1305_dec(void *restrict mac, - void *restrict data, - size_t len, - const void *ad, /* additional data */ - size_t adlen, - const void *key, - const void *nounce) { - uint64_t auth[2]; - fio_chacha20_poly1305_auth(&auth, data, len, ad, adlen, key, nounce); - if (((auth[0] ^ fio_buf2u64u(mac)) | - (auth[1] ^ fio_buf2u64u(((char *)mac + 8))))) - return -1; - fio_chacha20(data, len, key, nounce, 1); - return 0; +#endif /* FIO_MAP2_LRU / FIO_MAP2_ORDERED */ } -/* ***************************************************************************** -Module Cleanup -***************************************************************************** -*/ -#endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_CHACHA -#endif /* FIO_CHACHA */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_SHA1 /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ /* ***************************************************************************** +The Map set/get functions +***************************************************************************** */ +/** + * The core get function. This function returns NULL if item is missing. + * + * NOTE: the function returns the internal representation of objects. + */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, get_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key) { + FIO_NAME(FIO_MAP2_NAME, node_s) *r = NULL; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return r; +#if defined(FIO_MAP2_HASH_FN) + uint64_t hash = FIO_MAP2_HASH_FN(key); +#endif + hash += !hash; + uint32_t pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + if (pos == (uint32_t)-1 || !imap[pos] || imap[pos] == 0xFF) + return r; +#ifdef FIO_MAP2_LRU + if (o->head != pos) { + FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); + o->head = pos; + } +#endif + r = o->map + pos; + return r; +} +/** sets / removes an object in the map, returning a pointer to the map data. */ +SFUNC FIO_NAME(FIO_MAP2_NAME, node_s) * + FIO_NAME(FIO_MAP2_NAME, set_ptr)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY key, + FIO_MAP2_VALUE val, + FIO_MAP2_VALUE_INTERNAL *old, + int overwrite +#else + FIO_MAP2_KEY key +#endif + ) { + FIO_NAME(FIO_MAP2_NAME, node_s) *r = NULL; +#ifdef FIO_MAP2_VALUE + if (old) + *old = (FIO_MAP2_VALUE_INTERNAL){0}; +#endif + FIO_NAME(FIO_MAP2_NAME, s) * o; +#if defined(FIO_MAP2_HASH_FN) + uint64_t hash; +#endif + uint32_t pos; + uint8_t *imap = NULL; + FIO_PTR_TAG_VALID_OR_GOTO(map, relinquish_attempt); + o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); +#if defined(FIO_MAP2_HASH_FN) + hash = FIO_MAP2_HASH_FN(key); +#endif + hash += !hash; /* hash is never zero */ + if (!o->bits) { /* minimal space is 8 objects... */ + *o = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, 3, 1); + } + /* find the object's (potential) position in the array */ + for (int i = 0;;) { + pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); + if (pos != (uint32_t)-1) + break; + if (i == 2) + goto internal_error; + FIO_NAME(FIO_MAP2_NAME, s) + tmp = FIO_NAME(FIO_MAP2_NAME, __duplicate)(o, o->bits + (++i), 1); + if (!tmp.map) /* no memory? something bad? */ + goto internal_error; + FIO_NAME(FIO_MAP2_NAME, __dealloc_map)(o); + *o = tmp; + } + /* imap may have been reallocated, collect info now. */ + imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + /* set return value */ + r = o->map + pos; - SHA 1 + if (!imap[pos] || imap[pos] == 0xFF) { + /* insert new object */ + imap[pos] = FIO_NAME(FIO_MAP2_NAME, __byte_hash)(o, hash); +#if !FIO_MAP2_RECALC_HASH + r->hash = hash; +#endif + FIO_MAP2_KEY_COPY(r->key, key); + FIO_MAP2_VALUE_COPY(r->value, val); +#if FIO_MAP2_ORDERED + if (o->count) { /* update ordering */ + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); +#ifdef FIO_MAP2_LRU + o->head = pos; /* update LRU head */ + if (FIO_MAP2_LRU == o->count) { /* limit reached - evict 1 LRU element */ + uint32_t to_evict = o->map[pos].node.prev; + FIO_MAP2_KEY_DESTROY(o->map[to_evict].key); + FIO_MAP2_VALUE_DESTROY(o->map[to_evict].value); + FIO_INDEXED_LIST_REMOVE(o->map, node, to_evict); + imap[to_evict] = 0xFF; + --o->count; + } +#endif /* FIO_MAP2_LRU */ + } else { /* set first order */ + o->map[pos].node.next = o->map[pos].node.prev = pos; + o->head = pos; + } +#endif /* FIO_MAP2_ORDERED */ + ++o->count; + return r; + } +#ifdef FIO_MAP2_LRU + /* update ordering (even if not overwriting) */ + if (o->head != pos) { + FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + FIO_INDEXED_LIST_PUSH(o->map, node, o->head, pos); + o->head = pos; + } +#endif +#ifdef FIO_MAP2_VALUE + if (overwrite) { + /* overwrite existing object (only relevant for hash maps) */ + FIO_MAP2_KEY_DISCARD(key); + if (!old) { + FIO_MAP2_VALUE_DESTROY(o->map[pos].value); + FIO_MAP2_VALUE_COPY(o->map[pos].value, val); + return r; + } + *old = o->map[pos].value; + o->map[pos].value = (FIO_MAP2_VALUE_INTERNAL){0}; + FIO_MAP2_VALUE_COPY(o->map[pos].value, val); + return r; + } +#endif +relinquish_attempt: + /* discard attempt */ + FIO_MAP2_KEY_DISCARD(key); + FIO_MAP2_VALUE_DISCARD(val); + return r; +internal_error: + FIO_MAP2_KEY_DISCARD(key); + FIO_MAP2_VALUE_DISCARD(val); + FIO_LOG_ERROR("unknown error occurred trying to add an entry to the map"); + FIO_ASSERT_DEBUG(0, "these errors shouldn't happen"); + return r; +} -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_SHA1) && !defined(H___FIO_SHA1___H) -#define H___FIO_SHA1___H /* ***************************************************************************** -SHA 1 +The Map remove function ***************************************************************************** */ -/** The data type containing the SHA1 digest (result). */ -typedef union { -#ifdef __SIZEOF_INT128__ - __uint128_t align__; +/** Removes an object in the map, returning a pointer to the map data. */ +SFUNC int FIO_NAME(FIO_MAP2_NAME, remove)(FIO_MAP2_PTR map, +#if !defined(FIO_MAP2_HASH_FN) + uint64_t hash, +#endif + FIO_MAP2_KEY key, +#ifdef FIO_MAP2_VALUE + FIO_MAP2_VALUE_INTERNAL *old #else - uint64_t align__; + FIO_MAP2_KEY_INTERNAL *old +#endif +) { +#ifdef FIO_MAP2_VALUE + if (old) + *old = (FIO_MAP2_VALUE_INTERNAL){0}; +#else + if (old) + *old = (FIO_MAP2_KEY_INTERNAL){0}; #endif - uint32_t v[5]; - uint8_t digest[20]; -} fio_sha1_s; - -/** - * A simple, non streaming, implementation of the SHA1 hashing algorithm. - * - * Do NOT use - SHA1 is broken... but for some reason some protocols still - * require it's use (i.e., WebSockets), so it's here for your convenience. - */ -SFUNC fio_sha1_s fio_sha1(const void *data, uint64_t len); - -/** Returns the digest length of SHA1 in bytes (20 bytes) */ -FIO_IFUNC size_t fio_sha1_len(void); -/** Returns the 20 Byte long digest of a SHA1 object. */ -FIO_IFUNC uint8_t *fio_sha1_digest(fio_sha1_s *s); + FIO_PTR_TAG_VALID_OR_RETURN(map, -1); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); +#if defined(FIO_MAP2_HASH_FN) + uint64_t hash = FIO_MAP2_HASH_FN(key); +#endif + hash += !hash; /* hash is never zero */ + uint32_t pos = FIO_NAME(FIO_MAP2_NAME, __index)(o, key, hash); + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); -/* ***************************************************************************** -SHA 1 Implementation - inlined static functions -***************************************************************************** */ + if (pos == (uint32_t)-1 || !imap[pos] || imap[pos] == 0xFF) + return -1; -/** returns the digest length of SHA1 in bytes */ -FIO_IFUNC size_t fio_sha1_len(void) { return 20; } + imap[pos] = 0xFF; /* mark hole and update count */ + --o->count; -/** returns the digest of a SHA1 object. */ -FIO_IFUNC uint8_t *fio_sha1_digest(fio_sha1_s *s) { return s->digest; } +#if FIO_MAP2_ORDERED + /* update ordering */ + if (o->head == pos) + o->head = o->map[pos].node.next; + if (o->head == pos) + o->head = 0; + else { + FIO_INDEXED_LIST_REMOVE(o->map, node, pos); + } +#endif -/* ***************************************************************************** -Implementation - possibly externed functions. -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - -FIO_IFUNC void fio___sha1_round512(uint32_t *old, /* state */ - uint32_t *w /* 16 words */) { -#if FIO___HAS_ARM_INTRIN - /* Code adjusted from: - * https://github.com/noloader/SHA-Intrinsics/blob/master/sha1-arm.c - * Credit to Jeffrey Walton. - */ - uint32x4_t w0, w1, w2, w3; - uint32x4_t t0, t1, v0, v_old; - uint32_t e0, e1, e_old; - e0 = e_old = old[4]; - v_old = vld1q_u32(old); - v0 = v_old; - - /* load to vectors */ - w0 = vld1q_u32(w); - w1 = vld1q_u32(w + 4); - w2 = vld1q_u32(w + 8); - w3 = vld1q_u32(w + 12); - /* make little endian */ - w0 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w0))); - w1 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w1))); - w2 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w2))); - w3 = vreinterpretq_u32_u8(vrev32q_u8(vreinterpretq_u8_u32(w3))); - - t0 = vaddq_u32(w0, vdupq_n_u32(0x5A827999)); - t1 = vaddq_u32(w1, vdupq_n_u32(0x5A827999)); - - /* round: 0-3 */ - e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); - v0 = vsha1cq_u32(v0, e0, t0); - t0 = vaddq_u32(w2, vdupq_n_u32(0x5A827999)); - w0 = vsha1su0q_u32(w0, w1, w2); - - /* round: 4-7 */ - e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); - v0 = vsha1cq_u32(v0, e1, t1); - t1 = vaddq_u32(w3, vdupq_n_u32(0x5A827999)); - w0 = vsha1su1q_u32(w0, w3); - w1 = vsha1su0q_u32(w1, w2, w3); - - /* round: 8-11 */ - e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); - v0 = vsha1cq_u32(v0, e0, t0); - t0 = vaddq_u32(w0, vdupq_n_u32(0x5A827999)); - w1 = vsha1su1q_u32(w1, w0); - w2 = vsha1su0q_u32(w2, w3, w0); - -#define FIO_SHA1_ROUND_(K, rn_fn, n, ni, n0, n1, n2, n3) \ - e##n = vsha1h_u32(vgetq_lane_u32(v0, 0)); \ - v0 = rn_fn(v0, e##ni, t##ni); \ - t##ni = vaddq_u32(w##n1, vdupq_n_u32(K)); \ - w##n2 = vsha1su1q_u32(w##n2, w##n1); \ - w##n3 = vsha1su0q_u32(w##n3, w##n0, w##n1); - FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1cq_u32, 0, 1, 0, 1, 2, 3) - FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1cq_u32, 1, 0, 1, 2, 3, 0) - FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 0, 1, 2, 3, 0, 1) - FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 1, 0, 3, 0, 1, 2) - FIO_SHA1_ROUND_(0x6ED9EBA1, vsha1pq_u32, 0, 1, 0, 1, 2, 3) - - FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1pq_u32, 1, 0, 1, 2, 3, 0) - FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1pq_u32, 0, 1, 2, 3, 0, 1) - FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 1, 0, 3, 0, 1, 2) - FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 0, 1, 0, 1, 2, 3) - FIO_SHA1_ROUND_(0x8F1BBCDC, vsha1mq_u32, 1, 0, 1, 2, 3, 0) - - FIO_SHA1_ROUND_(0xCA62C1D6, vsha1mq_u32, 0, 1, 2, 3, 0, 1) - FIO_SHA1_ROUND_(0xCA62C1D6, vsha1mq_u32, 1, 0, 3, 0, 1, 2) - FIO_SHA1_ROUND_(0xCA62C1D6, vsha1pq_u32, 0, 1, 0, 1, 2, 3) - FIO_SHA1_ROUND_(0xCA62C1D6, vsha1pq_u32, 1, 0, 1, 2, 3, 0) -#undef FIO_SHA1_ROUND_ - /* round: 68-71 */ - e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); - v0 = vsha1pq_u32(v0, e1, t1); - t1 = vaddq_u32(w3, vdupq_n_u32(0xCA62C1D6)); - w0 = vsha1su1q_u32(w0, w3); - - /* round: 72-75 */ - e1 = vsha1h_u32(vgetq_lane_u32(v0, 0)); - v0 = vsha1pq_u32(v0, e0, t0); - - /* round: 76-79 */ - e0 = vsha1h_u32(vgetq_lane_u32(v0, 0)); - v0 = vsha1pq_u32(v0, e1, t1); - - /* combine and store */ - e0 += e_old; - v0 = vaddq_u32(v_old, v0); - vst1q_u32(old, v0); - old[4] = e0; - -#else /* !FIO___HAS_ARM_INTRIN portable implementation */ - - uint32_t v[8] = {0}; /* copy old state to new + reserve registers (8 not 6) */ - for (size_t i = 0; i < 5; ++i) - v[i] = old[i]; - - for (size_t i = 0; i < 16; ++i) /* convert read buffer to Big Endian */ - w[i] = fio_ntol32(w[i]); - -#define FIO___SHA1_ROUND4(K, F, i) \ - FIO___SHA1_ROUND((K), (F), i); \ - FIO___SHA1_ROUND((K), (F), i + 1); \ - FIO___SHA1_ROUND((K), (F), i + 2); \ - FIO___SHA1_ROUND((K), (F), i + 3); -#define FIO___SHA1_ROUND16(K, F, i) \ - FIO___SHA1_ROUND4((K), (F), i); \ - FIO___SHA1_ROUND4((K), (F), i + 4); \ - FIO___SHA1_ROUND4((K), (F), i + 8); \ - FIO___SHA1_ROUND4((K), (F), i + 12); -#define FIO___SHA1_ROUND20(K, F, i) \ - FIO___SHA1_ROUND16(K, F, i); \ - FIO___SHA1_ROUND4((K), (F), i + 16); - -#define FIO___SHA1_ROTATE_OLD(K, F, i) \ - v[5] = fio_lrot32(v[0], 5) + v[4] + F + (uint32_t)K + w[(i)&15]; \ - v[4] = v[3]; \ - v[3] = v[2]; \ - v[2] = fio_lrot32(v[1], 30); \ - v[1] = v[0]; \ - v[0] = v[5]; - -#define FIO___SHA1_ROTATE(K, F, i) \ - v[5] = fio_lrot32(v[0], 5) + v[4] + F + (uint32_t)K + w[(i)&15]; \ - v[1] = fio_lrot32(v[1], 30); \ - fio_u32x8_reshuffle(v, 5, 0, 1, 2, 3, 5, 6, 7); - -#define FIO___SHA1_CALC_WORD(i) \ - fio_lrot32( \ - (w[(i + 13) & 15] ^ w[(i + 8) & 15] ^ w[(i + 2) & 15] ^ w[(i)&15]), \ - 1); - -#define FIO___SHA1_ROUND(K, F, i) FIO___SHA1_ROTATE(K, F, i); - /* perform first 16 rounds with simple words as copied from data */ - FIO___SHA1_ROUND16(0x5A827999, ((v[1] & v[2]) | ((~v[1]) & (v[3]))), 0); - -/* change round definition so now we compute the word's value per round */ -#undef FIO___SHA1_ROUND -#define FIO___SHA1_ROUND(K, F, i) \ - w[(i)&15] = FIO___SHA1_CALC_WORD(i); \ - FIO___SHA1_ROTATE(K, F, i); - - /* complete last 4 round from the first 20 round group */ - FIO___SHA1_ROUND4(0x5A827999, ((v[1] & v[2]) | ((~v[1]) & (v[3]))), 16); - - /* remaining 20 round groups */ - FIO___SHA1_ROUND20(0x6ED9EBA1, (v[1] ^ v[2] ^ v[3]), 20); - FIO___SHA1_ROUND20(0x8F1BBCDC, ((v[1] & (v[2] | v[3])) | (v[2] & v[3])), 40); - FIO___SHA1_ROUND20(0xCA62C1D6, (v[1] ^ v[2] ^ v[3]), 60); - /* sum and store */ - for (size_t i = 0; i < 5; ++i) - old[i] += v[i]; - -#undef FIO___SHA1_ROTATE -#undef FIO___SHA1_ROTATE_OLD -#undef FIO___SHA1_CALC_WORD -#undef FIO___SHA1_ROUND -#undef FIO___SHA1_ROUND4 -#undef FIO___SHA1_ROUND16 -#undef FIO___SHA1_ROUND20 -#endif /* FIO___HAS_ARM_INTRIN */ -} -/** - * A simple, non streaming, implementation of the SHA1 hashing algorithm. - * - * Do NOT use - SHA1 is broken... but for some reason some protocols still - * require it's use (i.e., WebSockets), so it's here for your convinience. - */ -SFUNC fio_sha1_s fio_sha1(const void *data, uint64_t len) { - fio_sha1_s s FIO_ALIGN(16) = {.v = { - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0, - }}; - uint32_t vec[16] FIO_ALIGN(16); - - const uint8_t *buf = (const uint8_t *)data; - - for (size_t i = 63; i < len; i += 64) { - fio_memcpy64(vec, buf); - fio___sha1_round512(s.v, vec); - buf += 64; - } - for (size_t i = 0; i < 16; ++i) { - vec[i] = 0; - } - if ((len & 63)) { - uint32_t tbuf[16] = {0}; - fio_memcpy63x(tbuf, buf, len); - fio_memcpy64(vec, tbuf); - } - ((uint8_t *)vec)[(len & 63)] = 0x80; - - if ((len & 63) > 55) { - fio___sha1_round512(s.v, vec); - for (size_t i = 0; i < 16; ++i) { - vec[i] = 0; - } - } - len <<= 3; - len = fio_lton64(len); - vec[14] = (uint32_t)(len & 0xFFFFFFFF); - vec[15] = (uint32_t)(len >> 32); - fio___sha1_round512(s.v, vec); - for (size_t i = 0; i < 5; ++i) { - s.v[i] = fio_ntol32(s.v[i]); - } - return s; -} - -/** HMAC-SHA1, resulting in a 20 byte authentication code. */ -SFUNC fio_sha1_s fio_sha1_hmac(const void *key, - uint64_t key_len, - const void *msg, - uint64_t msg_len) { - fio_sha1_s inner FIO_ALIGN(16) = {.v = - { - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0, - }}, - outer FIO_ALIGN(16) = {.v = { - 0x67452301, - 0xEFCDAB89, - 0x98BADCFE, - 0x10325476, - 0xC3D2E1F0, - }}; - fio_u512 v = fio_u512_init64(0), k = fio_u512_init64(0); - const uint8_t *buf = (const uint8_t *)msg; - - /* copy key */ - if (key_len > 64) - goto key_too_long; - if (key_len == 64) - fio_memcpy64(k.u8, key); - else - fio_memcpy63x(k.u8, key, key_len); - /* prepare inner key */ - for (size_t i = 0; i < 8; ++i) - k.u64[i] ^= (uint64_t)0x3636363636363636ULL; - - /* hash inner key block */ - fio___sha1_round512(inner.v, k.u32); - /* consume data */ - for (size_t i = 63; i < msg_len; i += 64) { - fio_memcpy64(v.u8, buf); - fio___sha1_round512(inner.v, v.u32); - buf += 64; - } - /* finalize temporary hash */ - if ((msg_len & 63)) { - v = fio_u512_init64(0); - fio_memcpy63x(v.u8, buf, msg_len); - } - v.u8[(msg_len & 63)] = 0x80; - if ((msg_len & 63) > 55) { - fio___sha1_round512(inner.v, v.u32); - v = fio_u512_init64(0); - } - msg_len += 64; /* add the 64 byte inner key to the length count */ - msg_len <<= 3; - msg_len = fio_lton64(msg_len); - v.u32[14] = (uint32_t)(msg_len & 0xFFFFFFFFUL); - v.u32[15] = (uint32_t)(msg_len >> 32); - fio___sha1_round512(inner.v, v.u32); - for (size_t i = 0; i < 5; ++i) - inner.v[i] = fio_ntol32(inner.v[i]); - - /* switch key to outer key */ - for (size_t i = 0; i < 8; ++i) - k.u64[i] ^= - ((uint64_t)0x3636363636363636ULL ^ (uint64_t)0x5C5C5C5C5C5C5C5CULL); - - /* hash outer key block */ - fio___sha1_round512(outer.v, k.u32); - /* hash inner (temporary) hash result and finalize */ - v = fio_u512_init64(0); - for (size_t i = 0; i < 5; ++i) - v.u32[i] = inner.v[i]; - v.u8[20] = 0x80; - msg_len = ((64U + 20U) << 3); - msg_len = fio_lton64(msg_len); - v.u32[14] = (uint32_t)(msg_len & 0xFFFFFFFF); - v.u32[15] = (uint32_t)(msg_len >> 32); - fio___sha1_round512(outer.v, v.u32); - for (size_t i = 0; i < 5; ++i) - outer.v[i] = fio_ntol32(outer.v[i]); - - return outer; - -key_too_long: - inner = fio_sha1(key, key_len); - return fio_sha1_hmac(inner.digest, 20, msg, msg_len); -} -/* ***************************************************************************** -Module Cleanup -***************************************************************************** */ - -#endif /* FIO_EXTERN_COMPLETE */ -#endif /* FIO_SHA1 */ -#undef FIO_SHA1 -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_SHA2 /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** - - - - - SHA 2 - SHA-256 / SHA-512 and variations - - - -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_SHA2) && !defined(H___FIO_SHA2___H) -#define H___FIO_SHA2___H -/* ***************************************************************************** -SHA 2 API -***************************************************************************** */ - -/** Streaming SHA-256 type. */ -typedef struct { - fio_u256 hash; - fio_u512 cache; - uint64_t total_len; -} fio_sha256_s; - -/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ -FIO_IFUNC fio_u256 fio_sha256(const void *data, uint64_t len); - -/** initializes a fio_u256 so the hash can consume streaming data. */ -FIO_IFUNC fio_sha256_s fio_sha256_init(void); -/** Feed data into the hash */ -SFUNC void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len); -/** finalizes a fio_u256 with the SHA 256 hash. */ -SFUNC fio_u256 fio_sha256_finalize(fio_sha256_s *h); - -/** Streaming SHA-512 type. */ -typedef struct { - fio_u512 hash; - fio_u1024 cache; - uint64_t total_len; -} fio_sha512_s; - -/** A simple, non streaming, implementation of the SHA-512 hashing algorithm. */ -FIO_IFUNC fio_u512 fio_sha512(const void *data, uint64_t len); - -/** initializes a fio_u512 so the hash can consume streaming data. */ -FIO_IFUNC fio_sha512_s fio_sha512_init(void); -/** Feed data into the hash */ -SFUNC void fio_sha512_consume(fio_sha512_s *h, const void *data, uint64_t len); -/** finalizes a fio_u512 with the SHA 512 hash. */ -SFUNC fio_u512 fio_sha512_finalize(fio_sha512_s *h); - -/* ***************************************************************************** -Implementation - static / inline functions. -***************************************************************************** */ - -/** initializes a fio_u256 so the hash can be consumed. */ -FIO_IFUNC fio_sha256_s fio_sha256_init(void) { - fio_sha256_s h = {.hash.u32 = {0x6A09E667ULL, - 0xBB67AE85ULL, - 0x3C6EF372ULL, - 0xA54FF53AULL, - 0x510E527FULL, - 0x9B05688CULL, - 0x1F83D9ABULL, - 0x5BE0CD19ULL}}; - return h; -} - -/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ -FIO_IFUNC fio_u256 fio_sha256(const void *data, uint64_t len) { - fio_sha256_s h = fio_sha256_init(); - fio_sha256_consume(&h, data, len); - return fio_sha256_finalize(&h); -} - -/** initializes a fio_u256 so the hash can be consumed. */ -FIO_IFUNC fio_sha512_s fio_sha512_init(void) { - fio_sha512_s h = {.hash.u64 = {0x6A09E667F3BCC908ULL, - 0xBB67AE8584CAA73BULL, - 0x3C6EF372FE94F82BULL, - 0xA54FF53A5F1D36F1ULL, - 0x510E527FADE682D1ULL, - 0x9B05688C2B3E6C1FULL, - 0x1F83D9ABFB41BD6BULL, - 0x5BE0CD19137E2179ULL}}; - return h; -} - -/** A simple, non streaming, implementation of the SHA-256 hashing algorithm. */ -FIO_IFUNC fio_u512 fio_sha512(const void *data, uint64_t len) { - fio_sha512_s h = fio_sha512_init(); - fio_sha512_consume(&h, data, len); - return fio_sha512_finalize(&h); -} - -/* ***************************************************************************** -Implementation - possibly externed functions. -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - -/* ***************************************************************************** -Implementation - SHA-256 -***************************************************************************** */ - -FIO_IFUNC void fio___sha256_round(fio_u256 *h, const uint8_t *block) { - const uint32_t sha256_consts[64] = { - 0x428A2F98ULL, 0x71374491ULL, 0xB5C0FBCFULL, 0xE9B5DBA5ULL, 0x3956C25BULL, - 0x59F111F1ULL, 0x923F82A4ULL, 0xAB1C5ED5ULL, 0xD807AA98ULL, 0x12835B01ULL, - 0x243185BEULL, 0x550C7DC3ULL, 0x72BE5D74ULL, 0x80DEB1FEULL, 0x9BDC06A7ULL, - 0xC19BF174ULL, 0xE49B69C1ULL, 0xEFBE4786ULL, 0x0FC19DC6ULL, 0x240CA1CCULL, - 0x2DE92C6FULL, 0x4A7484AAULL, 0x5CB0A9DCULL, 0x76F988DAULL, 0x983E5152ULL, - 0xA831C66DULL, 0xB00327C8ULL, 0xBF597FC7ULL, 0xC6E00BF3ULL, 0xD5A79147ULL, - 0x06CA6351ULL, 0x14292967ULL, 0x27B70A85ULL, 0x2E1B2138ULL, 0x4D2C6DFCULL, - 0x53380D13ULL, 0x650A7354ULL, 0x766A0ABBULL, 0x81C2C92EULL, 0x92722C85ULL, - 0xA2BFE8A1ULL, 0xA81A664BULL, 0xC24B8B70ULL, 0xC76C51A3ULL, 0xD192E819ULL, - 0xD6990624ULL, 0xF40E3585ULL, 0x106AA070ULL, 0x19A4C116ULL, 0x1E376C08ULL, - 0x2748774CULL, 0x34B0BCB5ULL, 0x391C0CB3ULL, 0x4ED8AA4AULL, 0x5B9CCA4FULL, - 0x682E6FF3ULL, 0x748F82EEULL, 0x78A5636FULL, 0x84C87814ULL, 0x8CC70208ULL, - 0x90BEFFFAULL, 0xA4506CEBULL, 0xBEF9A3F7ULL, 0xC67178F2ULL}; - - uint32_t v[8]; - for (size_t i = 0; i < 8; ++i) { - v[i] = h->u32[i]; - } - /* read data as an array of 16 big endian 32 bit integers. */ - uint32_t w[16] FIO_ALIGN(16); - fio_memcpy64(w, block); - for (size_t i = 0; i < 16; ++i) { - w[i] = fio_lton32(w[i]); /* no-op on big endien systems */ - } - -#define FIO___SHA256_ROUND_INNER_COMMON() \ - uint32_t t2 = \ - ((v[0] & v[1]) ^ (v[0] & v[2]) ^ (v[1] & v[2])) + \ - (fio_rrot32(v[0], 2) ^ fio_rrot32(v[0], 13) ^ fio_rrot32(v[0], 22)); \ - fio_u32x8_reshuffle(v, 7, 0, 1, 2, 3, 4, 5, 6); \ - v[4] += t1; \ - v[0] = t1 + t2; - - for (size_t i = 0; i < 16; ++i) { - const uint32_t t1 = - v[7] + sha256_consts[i] + w[i] + ((v[4] & v[5]) ^ ((~v[4]) & v[6])) + - (fio_rrot32(v[4], 6) ^ fio_rrot32(v[4], 11) ^ fio_rrot32(v[4], 25)); - FIO___SHA256_ROUND_INNER_COMMON(); - } - for (size_t i = 0; i < 48; ++i) { /* expand block */ - w[(i & 15)] = - (fio_rrot32(w[((i + 14) & 15)], 17) ^ - fio_rrot32(w[((i + 14) & 15)], 19) ^ (w[((i + 14) & 15)] >> 10)) + - w[((i + 9) & 15)] + w[(i & 15)] + - (fio_rrot32(w[((i + 1) & 15)], 7) ^ fio_rrot32(w[((i + 1) & 15)], 18) ^ - (w[((i + 1) & 15)] >> 3)); - const uint32_t t1 = - v[7] + sha256_consts[i + 16] + w[(i & 15)] + - ((v[4] & v[5]) ^ ((~v[4]) & v[6])) + - (fio_rrot32(v[4], 6) ^ fio_rrot32(v[4], 11) ^ fio_rrot32(v[4], 25)); - FIO___SHA256_ROUND_INNER_COMMON(); - } - for (size_t i = 0; i < 8; ++i) - h->u32[i] += v[i]; /* compress block with previous state */ - -#undef FIO___SHA256_ROUND_INNER_COMMON -} - -/** consume data and feed it to hash. */ -SFUNC void fio_sha256_consume(fio_sha256_s *h, const void *data, uint64_t len) { - const uint8_t *r = (const uint8_t *)data; - const size_t old_total = h->total_len; - const size_t new_total = len + h->total_len; - h->total_len = new_total; - /* manage cache */ - if (old_total & 63) { - const size_t offset = (old_total & 63); - if (len + offset < 64) { /* not enough - copy to cache */ - fio_memcpy63x((h->cache.u8 + offset), r, len); - return; - } - /* consume cache */ - const size_t byte2copy = 64UL - offset; - fio_memcpy63x(h->cache.u8 + offset, r, byte2copy); - fio___sha256_round(&h->hash, h->cache.u8); - FIO_MEMSET(h->cache.u8, 0, 64); - r += byte2copy; - len -= byte2copy; - } - const uint8_t *end = r + (len & (~(uint64_t)63ULL)); - while ((uintptr_t)r < (uintptr_t)end) { - fio___sha256_round(&h->hash, r); - r += 64; +/* destroy data, copy to `old` pointer if necessary. */ +#ifdef FIO_MAP2_VALUE + FIO_MAP2_KEY_DESTROY(o->map[pos].key); + o->map[pos].key = (FIO_MAP2_KEY_INTERNAL){0}; + if (!old) { + FIO_MAP2_VALUE_DESTROY(o->map[pos].value); + } else { + *old = o->map[pos].value; } - fio_memcpy63x(h->cache.u64, r, len); -} - -SFUNC fio_u256 fio_sha256_finalize(fio_sha256_s *h) { - if (h->total_len == ((uint64_t)0ULL - 1ULL)) - return h->hash; - const size_t total = h->total_len; - const size_t remainder = total & 63; - h->cache.u8[remainder] = 0x80U; /* set the 1 bit at the left most position */ - if ((remainder) > 47) { /* make sure there's room to attach `total_len` */ - fio___sha256_round(&h->hash, h->cache.u8); - FIO_MEMSET(h->cache.u8, 0, 64); + o->map[pos].value = (FIO_MAP2_VALUE_INTERNAL){0}; +#else + if (!old) { + FIO_MAP2_KEY_DESTROY(o->map[pos].key); + } else { + *old = o->map[pos].key; } - h->cache.u64[7] = fio_lton64((total << 3)); - fio___sha256_round(&h->hash, h->cache.u8); - for (size_t i = 0; i < 8; ++i) - h->hash.u32[i] = fio_ntol32(h->hash.u32[i]); /* back to big endien */ - h->total_len = ((uint64_t)0ULL - 1ULL); - return h->hash; + o->map[pos].key = (FIO_MAP2_KEY_INTERNAL){0}; +#endif +#if !FIO_MAP2_RECALC_HASH && defined(DEBUG) + o->map[pos].hash = 0; /* not necessary, but ... good for debugging? */ +#endif + return 0; } /* ***************************************************************************** -Implementation - SHA-512 +Map Iteration ***************************************************************************** */ -FIO_IFUNC void fio___sha512_round(fio_u512 *h, const uint8_t *block) { - const uint64_t sha512_consts[80] = { - 0x428A2F98D728AE22, 0x7137449123EF65CD, 0xB5C0FBCFEC4D3B2F, - 0xE9B5DBA58189DBBC, 0x3956C25BF348B538, 0x59F111F1B605D019, - 0x923F82A4AF194F9B, 0xAB1C5ED5DA6D8118, 0xD807AA98A3030242, - 0x12835B0145706FBE, 0x243185BE4EE4B28C, 0x550C7DC3D5FFB4E2, - 0x72BE5D74F27B896F, 0x80DEB1FE3B1696B1, 0x9BDC06A725C71235, - 0xC19BF174CF692694, 0xE49B69C19EF14AD2, 0xEFBE4786384F25E3, - 0x0FC19DC68B8CD5B5, 0x240CA1CC77AC9C65, 0x2DE92C6F592B0275, - 0x4A7484AA6EA6E483, 0x5CB0A9DCBD41FBD4, 0x76F988DA831153B5, - 0x983E5152EE66DFAB, 0xA831C66D2DB43210, 0xB00327C898FB213F, - 0xBF597FC7BEEF0EE4, 0xC6E00BF33DA88FC2, 0xD5A79147930AA725, - 0x06CA6351E003826F, 0x142929670A0E6E70, 0x27B70A8546D22FFC, - 0x2E1B21385C26C926, 0x4D2C6DFC5AC42AED, 0x53380D139D95B3DF, - 0x650A73548BAF63DE, 0x766A0ABB3C77B2A8, 0x81C2C92E47EDAEE6, - 0x92722C851482353B, 0xA2BFE8A14CF10364, 0xA81A664BBC423001, - 0xC24B8B70D0F89791, 0xC76C51A30654BE30, 0xD192E819D6EF5218, - 0xD69906245565A910, 0xF40E35855771202A, 0x106AA07032BBD1B8, - 0x19A4C116B8D2D0C8, 0x1E376C085141AB53, 0x2748774CDF8EEB99, - 0x34B0BCB5E19B48A8, 0x391C0CB3C5C95A63, 0x4ED8AA4AE3418ACB, - 0x5B9CCA4F7763E373, 0x682E6FF3D6B2B8A3, 0x748F82EE5DEFB2FC, - 0x78A5636F43172F60, 0x84C87814A1F0AB72, 0x8CC702081A6439EC, - 0x90BEFFFA23631E28, 0xA4506CEBDE82BDE9, 0xBEF9A3F7B2C67915, - 0xC67178F2E372532B, 0xCA273ECEEA26619C, 0xD186B8C721C0C207, - 0xEADA7DD6CDE0EB1E, 0xF57D4F7FEE6ED178, 0x06F067AA72176FBA, - 0x0A637DC5A2C898A6, 0x113F9804BEF90DAE, 0x1B710B35131C471B, - 0x28DB77F523047D84, 0x32CAAB7B40C72493, 0x3C9EBE0A15C9BEBC, - 0x431D67C49C100D4C, 0x4CC5D4BECB3E42B6, 0x597F299CFC657E2A, - 0x5FCB6FAB3AD6FAEC, 0x6C44198C4A475817}; - - uint64_t t1, t2; /* used often... */ - /* copy original state */ - uint64_t v[8] FIO_ALIGN(16); - for (size_t i = 0; i < 8; ++i) - v[i] = h->u64[i]; +/** Returns the next iterator position after `current_pos`, first if `NULL`. */ +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_next)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos) { + FIO_NAME(FIO_MAP2_NAME, iterator_s) r = {.private_ = {.pos = 0}}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return r; + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + size_t capa = FIO_MAP2_CAPA(o->bits); + size_t pos_counter = 0; + if (!current_pos || !current_pos->private_.map_validator) { + goto find_pos; + } + if (current_pos->private_.pos + 1 == o->count) + return r; + r.private_.pos = current_pos->private_.pos + 1; + if (current_pos->private_.map_validator != (uintptr_t)o) { + goto refind_pos; + } + r.private_.index = current_pos->private_.index; - /* read data as an array of 16 big endian 64 bit integers. */ - uint64_t w[16] FIO_ALIGN(16); - fio_memcpy128(w, block); - for (size_t i = 0; i < 16; ++i) - w[i] = fio_lton64(w[i]); /* no-op on big endien systems */ +#if !FIO_MAP2_RECALC_HASH +#define FIO_MAP2___EACH_COPY_HASH() r.hash = o->map[r.private_.index].hash +#else +#define FIO_MAP2___EACH_COPY_HASH() +#endif -#define FIO___SHA512_ROUND_UNROLL(s) \ - t1 = v[(7 - s) & 7] + sha512_consts[i + s] + w[(i + s) & 15] + \ - (fio_rrot64(v[(4 - s) & 7], 14) ^ fio_rrot64(v[(4 - s) & 7], 18) ^ \ - fio_rrot64(v[(4 - s) & 7], 41)) + \ - ((v[(4 - s) & 7] & v[(5 - s) & 7]) ^ \ - ((~v[(4 - s) & 7]) & v[(6 - s) & 7])); \ - t2 = \ - (fio_rrot64(v[(0 - s) & 7], 28) ^ fio_rrot64(v[(0 - s) & 7], 34) ^ \ - fio_rrot64(v[(0 - s) & 7], 39)) + \ - ((v[(0 - s) & 7] & v[(1 - s) & 7]) ^ (v[(0 - s) & 7] & v[(2 - s) & 7]) ^ \ - (v[(1 - s) & 7] & v[(2 - s) & 7])); \ - v[(3 - s) & 7] += t1; \ - v[(7 - s) & 7] = t1 + t2 +#ifdef FIO_MAP2_VALUE +#define FIO_MAP2___EACH_COPY_DATA() \ + FIO_MAP2___EACH_COPY_HASH(); \ + r.private_.map_validator = (uintptr_t)o; \ + r.node = o->map + r.private_.index; \ + r.key = FIO_MAP2_KEY_FROM_INTERNAL(o->map[r.private_.index].key); \ + r.value = FIO_MAP2_VALUE_FROM_INTERNAL(o->map[r.private_.index].value) +#else +#define FIO_MAP2___EACH_COPY_DATA() \ + FIO_MAP2___EACH_COPY_HASH(); \ + r.private_.map_validator = (uintptr_t)o; \ + r.node = o->map + r.private_.index; \ + r.key = FIO_MAP2_KEY_FROM_INTERNAL(o->map[r.private_.index].key) +#endif - /* perform 80 "shuffle" rounds */ - for (size_t i = 0; i < 16; i += 8) { - FIO___SHA512_ROUND_UNROLL(0); - FIO___SHA512_ROUND_UNROLL(1); - FIO___SHA512_ROUND_UNROLL(2); - FIO___SHA512_ROUND_UNROLL(3); - FIO___SHA512_ROUND_UNROLL(4); - FIO___SHA512_ROUND_UNROLL(5); - FIO___SHA512_ROUND_UNROLL(6); - FIO___SHA512_ROUND_UNROLL(7); +/* start seeking at the position inherited from current_pos */ +#if FIO_MAP2_ORDERED + (void)imap; /* unused in ordered maps */ + (void)capa; /* unused in ordered maps */ + r.private_.index = o->map[r.private_.index].node.next; + if (r.private_.index == o->head) + goto not_found; + FIO_MAP2___EACH_COPY_DATA(); + return r; +#else + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while ((++r.private_.index) & 7) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + while (r.private_.index < capa) { + uint64_t simd = *(uint64_t *)(imap + r.private_.index); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index += 8; + continue; + } + for (int i = 0; i < 8; (++i), (++r.private_.index)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; } -#undef FIO___SHA512_ROUND_UNROLL -#define FIO___SHA512_ROUND_UNROLL(s) \ - t1 = (i + s + 14) & 15; \ - t2 = (i + s + 1) & 15; \ - t1 = fio_rrot64(w[t1], 19) ^ fio_rrot64(w[t1], 61) ^ (w[t1] >> 6); \ - t2 = fio_rrot64(w[t2], 1) ^ fio_rrot64(w[t2], 8) ^ (w[t2] >> 7); \ - w[(i + s) & 15] = t1 + t2 + w[(i + s + 9) & 15] + w[(i + s) & 15]; \ - t1 = v[(7 - s) & 7] + sha512_consts[i + s] + w[(i + s) & 15] + \ - (fio_rrot64(v[(4 - s) & 7], 14) ^ fio_rrot64(v[(4 - s) & 7], 18) ^ \ - fio_rrot64(v[(4 - s) & 7], 41)) + \ - ((v[(4 - s) & 7] & v[(5 - s) & 7]) ^ \ - ((~v[(4 - s) & 7]) & v[(6 - s) & 7])); \ - t2 = \ - (fio_rrot64(v[(0 - s) & 7], 28) ^ fio_rrot64(v[(0 - s) & 7], 34) ^ \ - fio_rrot64(v[(0 - s) & 7], 39)) + \ - ((v[(0 - s) & 7] & v[(1 - s) & 7]) ^ (v[(0 - s) & 7] & v[(2 - s) & 7]) ^ \ - (v[(1 - s) & 7] & v[(2 - s) & 7])); \ - v[(3 - s) & 7] += t1; \ - v[(7 - s) & 7] = t1 + t2 - - for (size_t i = 16; i < 80; i += 8) { - FIO___SHA512_ROUND_UNROLL(0); - FIO___SHA512_ROUND_UNROLL(1); - FIO___SHA512_ROUND_UNROLL(2); - FIO___SHA512_ROUND_UNROLL(3); - FIO___SHA512_ROUND_UNROLL(4); - FIO___SHA512_ROUND_UNROLL(5); - FIO___SHA512_ROUND_UNROLL(6); - FIO___SHA512_ROUND_UNROLL(7); + /* review as array */ + while ((++r.private_.index) < capa) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; } - /* sum/store state */ - for (size_t i = 0; i < 8; ++i) - h->u64[i] += v[i]; -} + goto not_found; +#endif /* FIO_MAP2_ORDERED */ -/** Feed data into the hash */ -SFUNC void fio_sha512_consume(fio_sha512_s *restrict h, - const void *restrict data, - uint64_t len) { - const uint8_t *r = (const uint8_t *)data; - const size_t old_total = h->total_len; - const size_t new_total = len + h->total_len; - h->total_len = new_total; - /* manage cache */ - if (old_total & 127) { - const size_t offset = (old_total & 127); - if (len + offset < 128) { /* not enough - copy to cache */ - fio_memcpy127x((h->cache.u8 + offset), r, len); - return; +refind_pos: + if (current_pos->private_.index) + goto not_found; +find_pos: +/* first seek... re-start seeking */ +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST_EACH(o->map, node, o->head, i) { + if (pos_counter != r.private_.pos) { + ++pos_counter; + continue; } - /* consume cache */ - const size_t byte2copy = 128UL - offset; - fio_memcpy127x(h->cache.u8 + offset, r, byte2copy); - fio___sha512_round(&h->hash, h->cache.u8); - FIO_MEMSET(h->cache.u8, 0, 128); - r += byte2copy; - len -= byte2copy; + r.private_.index = (uint32_t)i; + FIO_MAP2___EACH_COPY_DATA(); + return r; } - const uint8_t *end = r + (len & (~(uint64_t)127ULL)); - while ((uintptr_t)r < (uintptr_t)end) { - fio___sha512_round(&h->hash, r); - r += 128; + goto not_found; +#else + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while (r.private_.index < capa) { + uint64_t simd = *(uint64_t *)(imap + r.private_.index); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index += 8; + continue; + } + for (int i = 0; i < 8; (++i), (++r.private_.index)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + if (pos_counter != r.private_.pos) { + ++pos_counter; + continue; + } + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; } - fio_memcpy127x(h->cache.u64, r, len); -} - -/** finalizes a fio_u512 with the SHA 512 hash. */ -SFUNC fio_u512 fio_sha512_finalize(fio_sha512_s *h) { - if (h->total_len == ((uint64_t)0ULL - 1ULL)) - return h->hash; - const size_t total = h->total_len; - const size_t remainder = total & 127; - h->cache.u8[remainder] = 0x80U; /* set the 1 bit at the left most position */ - if ((remainder) > 112) { /* make sure there's room to attach `total_len` */ - fio___sha512_round(&h->hash, h->cache.u8); - FIO_MEMSET(h->cache.u8, 0, 128); + /* review as array */ + while (r.private_.index < capa) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) { + ++r.private_.index; + continue; + } + FIO_MAP2___EACH_COPY_DATA(); + return r; } - h->cache.u64[15] = fio_lton64((total << 3)); - fio___sha512_round(&h->hash, h->cache.u8); - for (size_t i = 0; i < 8; ++i) - h->hash.u64[i] = fio_ntol64(h->hash.u64[i]); /* back to/from big endien */ - h->total_len = ((uint64_t)0ULL - 1ULL); - return h->hash; -} - -/* ***************************************************************************** -Cleanup -***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#endif /* FIO_SHA2 */ -#undef FIO_SHA2 -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_ED25519 /* Development inclusion - ignore line */ -#define FIO_SHA2 /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** - - - - - Elliptic Curve ED25519 (WIP) - - - - -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if 0 && defined(FIO_ED25519) && !defined(H___FIO_ED25519___H) -#define H___FIO_ED25519___H - -/* ***************************************************************************** -TODO: ED 25519 - -ED-25519 key generation, key exchange and signatures are crucial to complete the -minimal building blocks that would allow to secure inter-machine communication -in mostly secure environments. Of course the use of a tested cryptographic -library (where accessible) might be preferred, but some security is better than -none. -***************************************************************************** */ - -/* ***************************************************************************** -ED25519 API -***************************************************************************** */ - -/** ED25519 Key Pair */ -typedef struct { - fio_u512 private_key; /* Private key (with extra internal storage?) */ - fio_u256 public_key; /* Public key */ -} fio_ed25519_s; - -/* Generates a random ED25519 keypair. */ -SFUNC void fio_ed25519_keypair(fio_ed25519_s *keypair); - -/* Sign a message using ED25519 */ -SFUNC void fio_ed25519_sign(uint8_t *signature, - const fio_buf_info_s message, - const fio_ed25519_s *keypair); - -/* Verify an ED25519 signature */ -SFUNC int fio_ed25519_verify(const uint8_t *signature, - const fio_buf_info_s message, - const fio_u256 *public_key); - -/* ***************************************************************************** -Implementation - inlined static functions -***************************************************************************** */ - -/* ***************************************************************************** -Implementation - possibly externed functions. -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - -/* prevent ED25519 keys from having a small period (cyclic value). */ -FIO_IFUNC void fio___ed25519_clamp_on_key(uint8_t *k) { - k[0] &= 0xF8U; /* zero out 3 least significant bits (emulate mul by 8) */ - k[31] &= 0x7FU; /* unset most significant bit (constant time fix) */ - k[31] |= 0x40U; /* set the 255th bit (making sure the value is big) */ -} - -static fio_u256 FIO___ED25519_PRIME = fio_u256_init64(0x7FFFFFFFFFFFFFFF, - 0xFFFFFFFFFFFFFFFF, - 0xFFFFFFFFFFFFFFFF, - 0xFFFFFFFFFFFFFFED); -/* Obfuscate or recover ED25519 keys to prevent easy memory scraping */ -FIO_IFUNC void fio___ed25519_flip(fio_ed25519_s *k) { - /* Generate a deterministic mask */ - uint64_t msk = - k->public_key.u64[3] + (uint64_t)(uintptr_t)(void *)&fio_ed25519_keypair; - /* XOR mask the private key */ - fio_u512_cxor64(&k->private_key, &k->private_key, msk); - /* XOR mask the first 192 bits of the public key */ - k->public_key.u64[0] ^= msk; - k->public_key.u64[1] ^= msk; - k->public_key.u64[2] ^= msk; -} - -/* Elliptic Curve Point Addition for Ed25519 */ -FIO_IFUNC void fio___ed25519_point_add(fio_u1024 *R, const fio_u1024 *P) { - /* Extract coordinates for P1 and P2 (R and P) */ - fio_u256 X1 = R->u256[0], Y1 = R->u256[1], Z1 = R->u256[2], T1 = R->u256[3]; - fio_u256 X2 = P->u256[0], Y2 = P->u256[1], Z2 = P->u256[2], T2 = P->u256[3]; - - fio_u256 A, B, C, D, X3, Y3, Z3, T3; - - /* A = (Y1 - X1) * (Y2 - X2) */ - fio_u256 Y1_minus_X1 = fio_u256_sub(Y1, X1); - fio_u256 Y2_minus_X2 = fio_u256_sub(Y2, X2); - A = fio_u256_mul(Y1_minus_X1, Y2_minus_X2); - - /* B = (Y1 + X1) * (Y2 + X2) */ - fio_u256 Y1_plus_X1 = fio_u256_add(Y1, X1); - fio_u256 Y2_plus_X2 = fio_u256_add(Y2, X2); - B = fio_u256_mul(Y1_plus_X1, Y2_plus_X2); - - /* C = 2 * T1 * T2 * d */ - C = fio_u256_mul(fio_u256_mul(T1, T2), ED25519_D); - - /* D = 2 * Z1 * Z2 */ - D = fio_u256_mul(fio_u256_mul(Z1, Z2), fio_u256_two()); - - /* X3 = (B - A) * (D - C) */ - X3 = fio_u256_mul(fio_u256_sub(B, A), fio_u256_sub(D, C)); - - /* Y3 = (B + A) * (D + C) */ - Y3 = fio_u256_mul(fio_u256_add(B, A), fio_u256_add(D, C)); - - /* Z3 = D * C */ - Z3 = fio_u256_mul(D, C); - - /* T3 = (B - A) * (B + A) */ - T3 = fio_u256_mul(fio_u256_sub(B, A), fio_u256_add(B, A)); +#endif /* FIO_MAP2_ORDERED */ - /* Update R with the result */ - R->u256[0] = X3; /* X */ - R->u256[1] = Y3; /* Y */ - R->u256[2] = Z3; /* Z */ - R->u256[3] = T3; /* T */ +not_found: + return (r = (FIO_NAME(FIO_MAP2_NAME, iterator_s)){.private_ = {.pos = 0}}); + FIO_ASSERT_DEBUG(0, "should this happen? ever?"); } -/* Helper function: Scalar multiplication on the elliptic curve */ -FIO_IFUNC void fio___ed25519_mul(fio_u512 *result, - const fio_u512 *scalar, - const fio_u512 *point) { - /* Start with the point */ - fio_u512 R[2] = {{0}, point[0]}; /* Identity point */ - - /* Step 2: Perform the Montgomery ladder scalar multiplication */ - for (int i = 255; i >= 0; --i) { - uint64_t bit = (scalar->u64[i >> 6] >> (i & 63)) & 1U; - /* Elliptic curve point addition and doubling */ - fio___ed25519_point_add(R, R + 1); - fio___ed25519_point_double(R + bit); +/** Returns the next iterator position after `current_pos`, first if `NULL`. */ +SFUNC FIO_NAME(FIO_MAP2_NAME, iterator_s) + FIO_NAME(FIO_MAP2_NAME, + get_prev)(FIO_MAP2_PTR map, + FIO_NAME(FIO_MAP2_NAME, iterator_s) * current_pos) { + FIO_NAME(FIO_MAP2_NAME, iterator_s) r = {.private_ = {.pos = 0}}; + FIO_PTR_TAG_VALID_OR_RETURN(map, r); + FIO_NAME(FIO_MAP2_NAME, s) *o = FIO_PTR_TAG_GET_UNTAGGED(FIO_MAP2_T, map); + if (!o->count) + return r; +#if !FIO_MAP2_ORDERED + uint8_t *imap = FIO_NAME(FIO_MAP2_NAME, __imap)(o); + size_t capa = FIO_MAP2_CAPA(o->bits); +#endif + size_t pos_counter = o->count; + if (!current_pos || !current_pos->private_.map_validator) { + r.private_.map_validator = (uintptr_t)o; + r.private_.pos = o->count; + goto find_pos; } + if (!current_pos->private_.pos) + return r; + r.private_.pos = current_pos->private_.pos - 1; + r.private_.map_validator = (uintptr_t)o; + if (current_pos->private_.map_validator != (uintptr_t)o) { + goto refind_pos; + } + r.private_.index = current_pos->private_.index; - /* Step 3: The final result is stored in R0 */ - *result = R[0]; -} - -/* Helper function: Modular reduction for Ed25519 */ -FIO_IFUNC void fio___ed25519_mod_reduce(fio_u256 *s) { - /* TODO: Implement modular reduction for Ed25519 scalar */ -} - -/* ED25519 Base Point (G) */ -const fio_u512 FIO___ED25519_BASEPOINT = { - .u64 = - { - 0x216936D3CD6E53FEULL, /* x-coordinate (lower 64 bits) */ - 0xC0A4E231FDD6DC5CULL, /* x-coordinate (upper 64 bits) */ - 0x6666666666666666ULL, /* y-coordinate (lower 64 bits) */ - 0x6666666666666666ULL /* y-coordinate (upper 64 bits) */ - }, -}; +/* start seeking at the position inherited from current_pos */ +#if FIO_MAP2_ORDERED + if (r.private_.index == o->head) + goto not_found; + r.private_.index = o->map[r.private_.index].node.prev; + FIO_MAP2___EACH_COPY_DATA(); + return r; +#else + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while ((--r.private_.index) & 7) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + while (r.private_.index) { + uint64_t simd = *(uint64_t *)(imap + (r.private_.index - 8)); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index -= 8; + continue; + } + for (int i = 0; i < 8; ++i) { + --r.private_.index; + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; + } + /* review as array */ + while (r.private_.index--) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + goto not_found; +#endif /* FIO_MAP2_ORDERED */ -/* Generate ED25519 keypair */ -SFUNC fio_ed25519_s fio_ed25519_keypair(void) { - fio_ed25519_s keypair; - /* Generate the 512-bit (clamped) private key */ - keypair.private_key.u64[0] = fio_rand64(); - keypair.private_key.u64[1] = fio_rand64(); - keypair.private_key.u64[2] = fio_rand64(); - keypair.private_key.u64[3] = fio_rand64(); - keypair.private_key = fio_sha512(keypair.private_key.u8, 32); - fio___ed25519_clamp_on_key(keypair.private_key.u8); - /* TODO: Derive the public key */ - fio_u256_mul(fio_u512 * result, const fio_u256 *a, const fio_u256 *b) - fio___ed25519_mul(&keypair.public_key, - &keypair.private_key, - &FIO___ED25519_BASEPOINT); - /* Maybe... */ +refind_pos: + if (current_pos->private_.index) + goto not_found; +find_pos: +/* first seek... re-start seeking */ +#if FIO_MAP2_ORDERED + FIO_INDEXED_LIST_EACH_REVERSED(o->map, node, o->head, i) { + if (pos_counter != r.private_.pos) { + --pos_counter; + continue; + } + r.private_.index = (uint32_t)i; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + goto not_found; +#else + r.private_.index = (uint32_t)capa; + if (FIO_MAP2_IS_SPARSE(o)) { /* sparsely populated */ + while (r.private_.index) { + uint64_t simd = *(uint64_t *)(imap + r.private_.index); + if (!simd || simd == 0xFFFFFFFFFFFFFFFFULL) { + r.private_.index -= 8; + continue; + } + for (int i = 0; i < 8; (++i), (--r.private_.index)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + if (pos_counter != r.private_.pos) { + ++pos_counter; + continue; + } + FIO_MAP2___EACH_COPY_DATA(); + return r; + } + } + goto not_found; + } + /* review as array */ + while ((r.private_.index--)) { + if (!imap[r.private_.index] || imap[r.private_.index] == 0xFF) + continue; + FIO_MAP2___EACH_COPY_DATA(); + return r; + } +#endif /* FIO_MAP2_ORDERED */ - /* Mask data, so it's harder to scrape in case of a memory dump. */ - fio___ed25519_flip(&keypair); - return keypair; +not_found: + return (r = (FIO_NAME(FIO_MAP2_NAME, iterator_s)){.private_ = {.pos = 0}}); + FIO_ASSERT_DEBUG(0, "should this happen? ever?"); } +#undef FIO_MAP2___EACH_COPY_HASH +#undef FIO_MAP2___EACH_COPY_DATA -/* Sign a message using ED25519 */ -SFUNC void fio_ed25519_sign(uint8_t *signature, - const fio_buf_info_s message, - const fio_ed25519_s *keypair) { - fio_sha512_s sha; - fio_u512 r, h; - fio_u256 R; - - /* Step 1: Hash the private key and message */ - sha = fio_sha512_init(); - fio_sha512_consume(&sha, - keypair->private_key.u8 + 32, - 32); /* Hash private key second part */ - fio_sha512_consume(&sha, message.buf, message.len); /* Hash the message */ - r = fio_sha512_finalize(&sha); /* Finalize the hash */ - - /* Step 2: Clamp and scalar multiply */ - fio___ed25519_clamp_on_key(r.u8); - fio___ed25519_mul((fio_ed25519_s *)&R, &r, &FIO___ED25519_BASEPOINT); - - /* Step 3: Compute 's' */ - sha = fio_sha512_init(); - fio_sha512_consume(&sha, R.u8, 32); /* Hash R */ - fio_sha512_consume(&sha, message.buf, message.len); /* Hash message */ - h = fio_sha512_finalize(&sha); /* Compute H(R || message) */ - fio___ed25519_mod_reduce(h.u8); /* Modular reduction of the hash */ - - /* Step 4: Create the signature */ - memcpy(signature, R.u8, 32); /* Copy R to the signature */ - memcpy(signature + 32, h.u8, 32); /* Copy the reduced hash 's' */ +/** + * Iteration using a callback for each element in the map. + * + * The callback task function must accept an each_s pointer, see above. + * + * If the callback returns -1, the loop is broken. Any other value is ignored. + * + * Returns the relative "stop" position, i.e., the number of items processed + + * the starting point. + */ +SFUNC uint32_t FIO_NAME(FIO_MAP2_NAME, + each)(FIO_MAP2_PTR map, + int (*task)(FIO_NAME(FIO_MAP2_NAME, each_s) *), + void *udata, + ssize_t start_at) { + FIO_PTR_TAG_VALID_OR_RETURN(map, 1); + FIO_NAME(FIO_MAP2_NAME, each_s) + e = { + .parent = map, + .task = task, + .udata = udata, + }; + FIO_NAME(FIO_MAP2_NAME, s) *o = + FIO_PTR_TAG_GET_UNTAGGED(FIO_NAME(FIO_MAP2_NAME, s), map); + if (start_at < 0) { + start_at += o->count; + if (start_at < 0) + start_at = 0; + } else if (start_at > o->count) + return o->count; + FIO_NAME(FIO_MAP2_NAME, iterator_s) i = {.private_ = {.pos = 0}}; + for (;;) { + i = FIO_NAME(FIO_MAP2_NAME, get_next)(map, &i); + if (!FIO_NAME(FIO_MAP2_NAME, iterator_is_valid)(&i)) + return o->count; + e.index = i.private_.pos; + e.key = i.key; +#ifdef FIO_MAP2_VALUE + e.value = i.value; +#endif + if (e.task(&e)) + return (uint32_t)(e.index + 1); + } + return o->count; } -/* Verify an ED25519 signature */ -SFUNC int fio_ed25519_verify(const uint8_t *signature, - const fio_buf_info_s message, - const fio_u256 *public_key) { - fio_sha512_s sha; - fio_u512 r, h; - uint8_t calculated_R[32]; +/* ***************************************************************************** +Speed Testing +***************************************************************************** */ - /* Step 1: Recalculate R */ - sha = fio_sha512_init(); - fio_sha512_consume(&sha, public_key->u8, 32); /* Hash the public key */ - fio_sha512_consume(&sha, message.buf, message.len); /* Hash the message */ - r = fio_sha512_finalize(&sha); /* Finalize the hash */ +/* ***************************************************************************** +Map Testing +***************************************************************************** */ +#ifdef FIO_MAP2_TEST - fio___ed25519_mul((fio_ed25519_s *)calculated_R, &r, &public_key->u8); +#ifdef FIO_MAP2_HASH_FN +#define FIO___M_HASH(k) +#else +#define FIO___M_HASH(k) (k), +#endif +#ifdef FIO_MAP2_VALUE +#define FIO___M_VAL(v) , (v) +#define FIO___M_OLD , NULL +#else +#define FIO___M_VAL(v) +#define FIO___M_OLD +#endif - /* Step 2: Compare calculated R with signature R using FIO_MEMCMP */ - return FIO_MEMCMP(calculated_R, signature, 32) == 0; +FIO_SFUNC void FIO_NAME_TEST(stl, FIO_MAP2_NAME)(void) { + /* testing only only works with integer external types */ + fprintf(stderr, + "* Testing maps with key " FIO_MACRO2STR( + FIO_MAP2_KEY) " (=> " FIO_MACRO2STR(FIO_MAP2_VALUE) ").\n"); + { /* test set / get overwrite , FIO_MAP2_EACH and evict */ + FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + for (size_t i = 1; i < (1UL << (FIO_MAP2_ARRAY_LOG_LIMIT + 5)); ++i) { + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + "map `set` failed? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + i); + for (size_t j = ((i << 2) + 1); j < i; ++j) { /* effects LRU ordering */ + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, get_ptr)(&map, FIO___M_HASH(j) j) && + FIO_NAME(FIO_MAP2_NAME, node2val)( + FIO_NAME(FIO_MAP2_NAME, + get_ptr)(&map, FIO___M_HASH(j) j)) == j, + "map `get` failed? %zu/%zu (%p)", + j, + i, + FIO_NAME(FIO_MAP2_NAME, get_ptr)(&map, FIO___M_HASH(j) j)); + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(j) j FIO___M_VAL(j) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + "map `set` added an item that already exists? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + i); + } + } + /* test FIO_MAP2_EACH and ordering */ + uint32_t count = FIO_NAME(FIO_MAP2_NAME, count)(&map); + uint32_t loop_test = 0; + FIO_MAP2_EACH(FIO_MAP2_NAME, &map, i) { + /* test ordering */ +#ifdef FIO_MAP2_LRU + FIO_ASSERT(i.key == (count - loop_test), + "map FIO_MAP2_EACH LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(count - loop_test)); + ++loop_test; +#elif FIO_MAP2_ORDERED + ++loop_test; + FIO_ASSERT(i.key == loop_test, + "map FIO_MAP2_EACH LRU ordering broken? %zu != %zu", + (size_t)(i.key), + (size_t)(loop_test)); +#else + ++loop_test; +#endif + } + FIO_ASSERT(loop_test == count, + "FIO_MAP2_EACH failed to iterate all elements? (%zu != %zu", + (size_t)loop_test != (size_t)count); + loop_test = 0; + FIO_MAP2_EACH_REVERSED(FIO_MAP2_NAME, &map, i) { ++loop_test; } + FIO_ASSERT( + loop_test == count, + "FIO_MAP2_EACH_REVERSED failed to iterate all elements? (%zu != %zu", + (size_t)loop_test != (size_t)count); + /* test `evict` while we're here */ + FIO_NAME(FIO_MAP2_NAME, evict)(&map, (count >> 1)); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == (count - (count >> 1)), + "map `evict` count error %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)(count - (count >> 1))); + /* cleanup */ + FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + } +#ifndef FIO_MAP2_HASH_FN + { /* test full collision guard and zero hash*/ + FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + fprintf( + stderr, + "* Testing full collision guard for " FIO_MACRO2STR( + FIO_NAME(FIO_MAP2_NAME, s)) " - expect SECURITY log messages.\n"); + for (size_t i = 1; i < 4096; ++i) { + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(0) i FIO___M_VAL(i) FIO___M_OLD); + } + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map), + "zero hash fails insertion?"); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) <= FIO_MAP2_ATTACK_LIMIT, + "map attack guard failed? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + (size_t)FIO_MAP2_ATTACK_LIMIT); + FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + } +#endif + { /* test reserve, remove */ + FIO_NAME(FIO_MAP2_NAME, s) map = FIO_MAP2_INIT; + FIO_NAME(FIO_MAP2_NAME, reserve)(&map, 4096); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, capa)(&map) == 4096, + "map reserve error? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, capa)(&map), + 4096); + for (size_t i = 1; i < 4096; ++i) { + FIO_NAME(FIO_MAP2_NAME, set) + (&map, FIO___M_HASH(i) i FIO___M_VAL(i) FIO___M_OLD); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == i, + "insertion failed?"); + } + for (size_t i = 1; i < 4096; ++i) { + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, get)(&map, FIO___M_HASH(i) i), + "key missing?"); + FIO_NAME(FIO_MAP2_NAME, remove) + (&map, FIO___M_HASH(i) i, NULL); + FIO_ASSERT(!FIO_NAME(FIO_MAP2_NAME, get)(&map, FIO___M_HASH(i) i), + "map_remove error?"); + FIO_ASSERT(FIO_NAME(FIO_MAP2_NAME, count)(&map) == 4095 - i, + "map count error after removal? %zu != %zu", + (size_t)FIO_NAME(FIO_MAP2_NAME, count)(&map), + i); + } + FIO_NAME(FIO_MAP2_NAME, destroy)(&map); + } } +#undef FIO___M_HASH +#undef FIO___M_VAL +#undef FIO___M_OLD +#endif /* FIO_MAP2_TEST */ /* ***************************************************************************** -Cleanup +Map Cleanup ***************************************************************************** */ #endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_ED25519 -#endif /* FIO_ED25519 */ + +#undef FIO_MAP2_ARRAY_LOG_LIMIT +#undef FIO_MAP2_ATTACK_LIMIT +#undef FIO_MAP2_CAPA +#undef FIO_MAP2_CAPA_BITS_LIMIT +#undef FIO_MAP2_CUCKOO_STEPS +#undef FIO_MAP2_GET_T +#undef FIO_MAP2_HASH_FN +#undef FIO_MAP2_IS_SPARSE +#undef FIO_MAP2_KEY +#undef FIO_MAP2_KEY_CMP +#undef FIO_MAP2_KEY_COPY +#undef FIO_MAP2_KEY_DESTROY +#undef FIO_MAP2_KEY_DESTROY_SIMPLE +#undef FIO_MAP2_KEY_DISCARD +#undef FIO_MAP2_KEY_FROM_INTERNAL +#undef FIO_MAP2_KEY_INTERNAL +#undef FIO_MAP2_KEY_IS_GREATER_THAN +#undef FIO_MAP2_LRU +#undef FIO_MAP2_NAME +#undef FIO_MAP2_ORDERED +#undef FIO_MAP2_PTR +#undef FIO_MAP2_RECALC_HASH +#undef FIO_MAP2_SEEK_LIMIT +#undef FIO_MAP2_T +#undef FIO_MAP2_TEST +#undef FIO_MAP2_VALUE +#undef FIO_MAP2_VALUE_BSTR +#undef FIO_MAP2_VALUE_COPY +#undef FIO_MAP2_VALUE_DESTROY +#undef FIO_MAP2_VALUE_DESTROY_SIMPLE +#undef FIO_MAP2_VALUE_DISCARD +#undef FIO_MAP2_VALUE_FROM_INTERNAL +#undef FIO_MAP2_VALUE_INTERNAL +#undef FIO_OMAP_NAME +#undef FIO_UMAP_NAME + +#endif /* FIO_MAP2_NAME */ /* ************************************************************************* */ #if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ #define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_SERVER /* Development inclusion - ignore line */ +#define FIO_REF_NAME long_ref /* Development inclusion - ignore line */ +#define FIO_REF_TYPE long /* Development inclusion - ignore line */ #include "./include.h" /* Development inclusion - ignore line */ #endif /* Development inclusion - ignore line */ /* ***************************************************************************** @@ -31876,5805 +31759,5571 @@ Cleanup - A Simple Server - Evented, Reactor based, Single-Threaded - + Reference Counting / Wrapper + (must be placed after all type macros) Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ -#if defined(FIO_SERVER) && !defined(FIO___RECURSIVE_INCLUDE) && \ - !defined(H___FIO_SERVER___H) -#define H___FIO_SERVER___H -/* ***************************************************************************** -Server Settings - -At this point, define any MACROs and customizable settings available to the -developer. -***************************************************************************** */ +#ifdef FIO_REF_NAME -#ifndef FIO_SRV_BUFFER_PER_WRITE -/** Control the size of the on-stack buffer used for `write` events. */ -#define FIO_SRV_BUFFER_PER_WRITE 65536U +#ifndef FIO_REF_TYPE +#define FIO_REF_TYPE FIO_NAME(FIO_REF_NAME, s) #endif -#ifndef FIO_SRV_THROTTLE_LIMIT -/** IO will be throttled (no `on_data` events) if outgoing buffer is large. */ -#define FIO_SRV_THROTTLE_LIMIT 2097152U +#ifndef FIO_REF_INIT +#define FIO_REF_INIT(obj) \ + do { \ + if (!FIO_MEM_REALLOC_IS_SAFE_) \ + (obj) = (FIO_REF_TYPE){0}; \ + } while (0) #endif -#ifndef FIO_SRV_TIMEOUT_MAX -/** Controls the maximum and default timeout in milliseconds. */ -#define FIO_SRV_TIMEOUT_MAX 300000 +#ifndef FIO_REF_DESTROY +#define FIO_REF_DESTROY(obj) #endif -#ifndef FIO_SRV_SHUTDOWN_TIMEOUT -/* Sets the hard timeout (in milliseconds) for the server's shutdown loop. */ -#define FIO_SRV_SHUTDOWN_TIMEOUT 10000 +#ifndef FIO_REF_METADATA_INIT +#ifdef FIO_REF_METADATA +#define FIO_REF_METADATA_INIT(meta) \ + do { \ + if (!FIO_MEM_REALLOC_IS_SAFE_) \ + (meta) = (FIO_REF_METADATA){0}; \ + } while (0) +#else +#define FIO_REF_METADATA_INIT(meta) +#endif #endif -/* ***************************************************************************** -IO Types -***************************************************************************** */ - -/** The main protocol object type. See `struct fio_protocol_s`. */ -typedef struct fio_protocol_s fio_protocol_s; - -/** The IO functions used by the protocol object. */ -typedef struct fio_io_functions_s fio_io_functions_s; - -/** The main IO object type. Should be treated as an opaque pointer. */ -typedef struct fio_s fio_s; +#ifndef FIO_REF_METADATA_DESTROY +#define FIO_REF_METADATA_DESTROY(meta) +#endif -/** An opaque type used for the SSL/TLS helper functions. */ -typedef struct fio_tls_s fio_tls_s; +/** + * FIO_REF_CONSTRUCTOR_ONLY allows the reference counter constructor (TYPE_new) + * to be the only constructor function. + * + * When set, the reference counting functions will use `X_new` and `X_free`. + * Otherwise (assuming `X_new` and `X_free` are already defined), the reference + * counter will define `X_new2` and `X_free2` instead. + */ +#ifdef FIO_REF_CONSTRUCTOR_ONLY +#define FIO_REF_CONSTRUCTOR new +#define FIO_REF_DESTRUCTOR free +#define FIO_REF_DUPNAME dup +#else +#define FIO_REF_CONSTRUCTOR new2 +#define FIO_REF_DESTRUCTOR free2 +#define FIO_REF_DUPNAME dup2 +#endif -/** Message structure, as received by the `on_message` subscription callback. */ -typedef struct fio_msg_s fio_msg_s; +typedef struct { +#ifdef FIO_REF_FLEX_TYPE + volatile uint32_t ref; + uint32_t flx_size; +#else + volatile size_t ref; +#endif +#ifdef FIO_REF_METADATA + FIO_REF_METADATA metadata; +#endif +} FIO_NAME(FIO_REF_NAME, _wrapper_s); -/** The Server Async Queue type. */ -typedef struct fio_srv_async_s fio_srv_async_s; +#ifdef FIO_PTR_TAG_TYPE +#define FIO_REF_TYPE_PTR FIO_PTR_TAG_TYPE +#else +#define FIO_REF_TYPE_PTR FIO_REF_TYPE * +#endif /* ***************************************************************************** -Starting / Stopping the Server +Reference Counter (Wrapper) API ***************************************************************************** */ -/** Stopping the server. */ -SFUNC void fio_srv_stop(void); - -/** Adds `workers` amount of workers to the root server process. */ -SFUNC void fio_srv_add_workers(int workers); - -/** Starts the server, using optional `workers` processes. This will BLOCK! */ -SFUNC void fio_srv_start(int workers); - -/** Returns true if server running and 0 if server stopped or shutting down. */ -SFUNC int fio_srv_is_running(void); - -/** Returns true if the current process is the server's master process. */ -SFUNC int fio_srv_is_master(void); - -/** Returns true if the current process is a server's worker process. */ -SFUNC int fio_srv_is_worker(void); +/** Allocates a reference counted object. */ +#ifdef FIO_REF_FLEX_TYPE +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, + FIO_REF_CONSTRUCTOR)(size_t members); +#else +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, FIO_REF_CONSTRUCTOR)(void); +#endif /* FIO_REF_FLEX_TYPE */ -/** Returns the number or workers the server will actually run. */ -SFUNC uint16_t fio_srv_workers(int workers_requested); +/** Increases the reference count. */ +FIO_IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, + FIO_REF_DUPNAME)(FIO_REF_TYPE_PTR wrapped); -/** Returns current process id. */ -SFUNC int fio_srv_pid(void); +/** Frees a reference counted object (or decreases the reference count). */ +IFUNC void FIO_NAME(FIO_REF_NAME, FIO_REF_DESTRUCTOR)(FIO_REF_TYPE_PTR wrapped); -/** Returns the root / master process id. */ -SFUNC int fio_srv_root_pid(void); +#ifdef FIO_REF_METADATA +/** Returns a pointer to the object's metadata, if defined. */ +IFUNC FIO_REF_METADATA *FIO_NAME(FIO_REF_NAME, + metadata)(FIO_REF_TYPE_PTR wrapped); +#endif +#ifdef FIO_REF_FLEX_TYPE +/** The allocation length for the flex type */ +IFUNC uint32_t FIO_NAME(FIO_REF_NAME, + metadata_flex_len)(FIO_REF_TYPE_PTR wrapped); +#endif /* ***************************************************************************** -Listening to Incoming Connections +Inline Implementation ***************************************************************************** */ +/** Increases the reference count. */ +FIO_IFUNC FIO_REF_TYPE_PTR +FIO_NAME(FIO_REF_NAME, FIO_REF_DUPNAME)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return 0; + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + fio_atomic_add(&o->ref, 1); + return wrapped_; +} -/** Arguments for the fio_listen function */ -typedef struct fio_srv_listen_args { - /** - * The binding address in URL format. Defaults to: tcp://0.0.0.0:3000 - * - * Note: `.url` accept an optional query for building a TLS context. - * - * Possible query values include: - * - * - `tls` or `ssl` (no value): sets TLS as active, possibly self-signed. - * - `tls=` or `ssl=`: value is a prefix for "key.pem" and "cert.pem". - * - `key=` and `cert=`: file paths for ".pem" files. - * - * i.e.: - * - * fio_srv_listen(.url = "0.0.0.0:3000/?tls", ...); - * fio_srv_listen(.url = "0.0.0.0:3000/?tls=./", ...); - * // same as: - * fio_srv_listen(.url = "0.0.0.0:3000/" - * "?key=./key.pem" - * "&cert=./cert.pem", ...); - */ - const char *url; - /** The `fio_protocol_s` that will be assigned to incoming connections. */ - fio_protocol_s *protocol; - /** The default `udata` set for (new) incoming connections. */ - void *udata; - /** TLS object used for incoming connections (ownership moved to listener). */ - fio_tls_s *tls; - /** - * Called when the a listening socket starts to listen. - * - * May be called multiple times (i.e., if the server stops and starts again). - */ - void (*on_start)(fio_protocol_s *protocol, void *udata); - /** - * Called during listener cleanup. - * - * This will be called separately for every process before exiting. - */ - void (*on_stop)(fio_protocol_s *protocol, void *udata); - /** - * Selects a queue that will be used to schedule a pre-accept task. - * May be used to test user thread stress levels before accepting connections. - */ - fio_srv_async_s *queue_for_accept; - /** If the server is forked - listen on the root process instead of workers */ - uint8_t on_root; - /** Hides "started/stopped listening" messages from log (if set). */ - uint8_t hide_from_log; -} fio_srv_listen_args; - -/** - * Sets up a network service on a listening socket. - * - * Returns a self-destructible listener handle on success or NULL on error. - */ -SFUNC void *fio_srv_listen(fio_srv_listen_args args); -#define fio_srv_listen(...) fio_srv_listen((fio_srv_listen_args){__VA_ARGS__}) - -/** Notifies a listener to stop listening. */ -SFUNC void fio_srv_listen_stop(void *listener); - -/** Returns the URL on which the listener is listening. */ -SFUNC fio_buf_info_s fio_srv_listener_url(void *listener); +/** Debugging helper, do not use for data, as returned value is unstable. */ +FIO_IFUNC size_t FIO_NAME(FIO_REF_NAME, references)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return 0; + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + return o->ref; +} -/** Returns true if the listener protocol has an attached TLS context. */ -SFUNC int fio_srv_listener_is_tls(void *listener); +#ifdef FIO_REF_FLEX_TYPE +/** The allocation length for the flex type */ +IFUNC uint32_t FIO_NAME(FIO_REF_NAME, + metadata_flex_len)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return 0; + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + return o->flx_size; +} +#endif /* ***************************************************************************** -Connecting as a Client +Reference Counter (Wrapper) Implementation ***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/** Named arguments for fio_srv_connect */ -typedef struct { - /** The URL to connect to (may contain TLS hints in query / `tls` scheme). */ - const char *url; - /** Connection protocol (once connection established). */ - fio_protocol_s *protocol; - /** Called in case of a failed connection, use for cleanup. */ - void (*on_failed)(fio_protocol_s *protocol, void *udata); - /** Opaque user data (set only once connection was established). */ - void *udata; - /** TLS builder object for TLS connections. */ - fio_tls_s *tls; - /** Connection timeout in milliseconds (defaults to 30 seconds). */ - uint32_t timeout; -} fio_srv_connect_args_s; +FIO_LEAK_COUNTER_DEF(FIO_REF_NAME) + +/** Allocates a reference counted object. */ +#ifdef FIO_REF_FLEX_TYPE +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, + FIO_REF_CONSTRUCTOR)(size_t members) { + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + (FIO_NAME(FIO_REF_NAME, _wrapper_s) *)FIO_MEM_REALLOC_( + NULL, + 0, + sizeof(*o) + sizeof(FIO_REF_TYPE) + + (sizeof(FIO_REF_FLEX_TYPE) * members), + 0); +#else +IFUNC FIO_REF_TYPE_PTR FIO_NAME(FIO_REF_NAME, FIO_REF_CONSTRUCTOR)(void) { + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = (FIO_NAME(FIO_REF_NAME, _wrapper_s) *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*o) + sizeof(FIO_REF_TYPE), 0); +#endif /* FIO_REF_FLEX_TYPE */ + if (!o) + return (FIO_REF_TYPE_PTR)(o); + FIO_LEAK_COUNTER_ON_ALLOC(FIO_REF_NAME); + o->ref = 1; +#ifdef FIO_REF_FLEX_TYPE + o->flx_size = members; +#endif + FIO_REF_METADATA_INIT((o->metadata)); + FIO_REF_TYPE *ret = (FIO_REF_TYPE *)(o + 1); + FIO_REF_INIT((ret[0])); + return (FIO_REF_TYPE_PTR)(FIO_PTR_TAG(ret)); + (void)FIO_NAME(FIO_REF_NAME, references); +} -/** Connects to a specific URL, returning the `fio_s` IO object or `NULL`. */ -SFUNC fio_s *fio_srv_connect(fio_srv_connect_args_s args); +/** Frees a reference counted object (or decreases the reference count). */ +IFUNC void FIO_NAME(FIO_REF_NAME, + FIO_REF_DESTRUCTOR)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + if (!wrapped || !wrapped_) + return; + FIO_PTR_TAG_VALID_OR_RETURN_VOID(wrapped_); + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + if (!o) + return; + if (fio_atomic_sub_fetch(&o->ref, 1)) + return; + FIO_REF_DESTROY((wrapped[0])); + FIO_REF_METADATA_DESTROY((o->metadata)); + FIO_LEAK_COUNTER_ON_FREE(FIO_REF_NAME); + FIO_MEM_FREE_(o, sizeof(*o) + (o->flx_size * sizeof(FIO_REF_TYPE))); +} -#define fio_srv_connect(url_, ...) \ - fio_srv_connect((fio_srv_connect_args_s){.url = url_, __VA_ARGS__}) +#ifdef FIO_REF_METADATA +/** Returns a pointer to the object's metadata, if defined. */ +IFUNC FIO_REF_METADATA *FIO_NAME(FIO_REF_NAME, + metadata)(FIO_REF_TYPE_PTR wrapped_) { + FIO_REF_TYPE *wrapped = (FIO_REF_TYPE *)(FIO_PTR_UNTAG(wrapped_)); + FIO_NAME(FIO_REF_NAME, _wrapper_s) *o = + ((FIO_NAME(FIO_REF_NAME, _wrapper_s) *)wrapped) - 1; + return &o->metadata; +} +#endif /* ***************************************************************************** -IO Operations +Reference Counter (Wrapper) Cleanup ***************************************************************************** */ -/** - * Attaches the socket in `fd` to the facio.io engine (reactor). - * - * * `fd` should point to a valid socket. - * - * * `protocol` may be the existing protocol or NULL (for partial hijack). - * - * * `udata` is opaque user data and may be any value, including NULL. - * - * * `tls` is a context for Transport Layer (Security) and can be used to - * redirect read/write operations, as set by the protocol. - * - * Returns NULL on error. the `fio_s` pointer must NOT be used except within - * proper callbacks. - */ -SFUNC fio_s *fio_srv_attach_fd(int fd, - fio_protocol_s *protocol, - void *udata, - void *tls); - -/** Sets a new protocol object. `NULL` is a valid "only-write" protocol. */ -SFUNC fio_protocol_s *fio_protocol_set(fio_s *io, fio_protocol_s *protocol); - -/** - * Returns a pointer to the current protocol object. - * - * If `protocol` wasn't properly set, the pointer might be invalid. - */ -SFUNC fio_protocol_s *fio_protocol_get(fio_s *io); - -/** Associates a new `udata` pointer with the IO, returning the old `udata` */ -FIO_IFUNC void *fio_udata_set(fio_s *io, void *udata); - -/** Returns the `udata` pointer associated with the IO. */ -FIO_IFUNC void *fio_udata_get(fio_s *io); - -/** Associates a new `tls` pointer with the IO, returning the old `tls` */ -FIO_IFUNC void *fio_tls_set(fio_s *io, void *tls); - -/** Returns the `tls` pointer associated with the IO. */ -FIO_IFUNC void *fio_tls_get(fio_s *io); - -/** Returns the socket file descriptor (fd) associated with the IO. */ -SFUNC int fio_fd_get(fio_s *io); +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_REF_NAME +#undef FIO_REF_FLEX_TYPE +#undef FIO_REF_TYPE +#undef FIO_REF_INIT +#undef FIO_REF_DESTROY +#undef FIO_REF_METADATA +#undef FIO_REF_METADATA_INIT +#undef FIO_REF_METADATA_DESTROY +#undef FIO_REF_TYPE_PTR +#undef FIO_REF_CONSTRUCTOR_ONLY +#undef FIO_REF_CONSTRUCTOR +#undef FIO_REF_DUPNAME +#undef FIO_REF_DESTRUCTOR +#endif +/* ***************************************************************************** +Pointer Tagging Cleanup +***************************************************************************** */ +#ifndef FIO___DEV___ +#undef FIO_PTR_TAG +#undef FIO_PTR_UNTAG +#undef FIO_PTR_TAG_TYPE +#undef FIO_PTR_TAG_VALIDATE +#undef FIO_PTR_TAG_VALID_OR_RETURN +#undef FIO_PTR_TAG_VALID_OR_RETURN_VOID +#undef FIO_PTR_TAG_VALID_OR_GOTO +#endif +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_FIOBJ /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -/* Resets a socket's timeout counter. */ -SFUNC void fio_touch(fio_s *io); -/** - * Reads data to the buffer, if any data exists. Returns the number of bytes - * read. - * - * NOTE: zero (`0`) is a valid return value meaning no data was available. - */ -SFUNC size_t fio_read(fio_s *io, void *buf, size_t len); -typedef struct { - /** The buffer with the data to send (if no file descriptor) */ - void *buf; - /** The file descriptor to send (if no buffer) */ - intptr_t fd; - /** The length of the data to be sent. On files, 0 = the whole file. */ - size_t len; - /** The length of the data to be sent. On files, 0 = the whole file. */ - size_t offset; - /** - * If this is a buffer, the de-allocation function used to free it. - * - * If NULL, the buffer will NOT be de-allocated. - */ - void (*dealloc)(void *); - /** If non-zero, makes a copy of the buffer or keeps a file open. */ - uint8_t copy; -} fio_write_args_s; -/** - * Writes data to the outgoing buffer and schedules the buffer to be sent. - */ -SFUNC void fio_write2(fio_s *io, fio_write_args_s args); -#define fio_write2(io, ...) fio_write2(io, (fio_write_args_s){__VA_ARGS__}) -/** Helper macro for a common fio_write2 (copies the buffer). */ -#define fio_write(io, buf_, len_) \ - fio_write2(io, .buf = (buf_), .len = (len_), .copy = 1) -/** - * Sends data from a file as if it were a single atomic packet (sends up to - * length bytes or until EOF is reached). - * - * Once the file was sent, the `source_fd` will be closed using `close`. - * - * The file will be buffered to the socket chunk by chunk, so that memory - * consumption is capped. - * - * `offset` dictates the starting point for the data to be sent and length sets - * the maximum amount of data to be sent. - * - * Closes the file on error. - */ -#define fio_sendfile(io, source_fd, offset_, bytes) \ - fio_write2((io), \ - .fd = (source_fd), \ - .offset = (size_t)(offset_), \ - .len = (bytes)) -/** Marks the IO for closure as soon as scheduled data was sent. */ -SFUNC void fio_close(fio_s *io); -/** Marks the IO for immediate closure. */ -SFUNC void fio_close_now(fio_s *io); + FIOBJ - soft (dynamic) types -/** - * Increases a IO's reference count, so it won't be automatically destroyed - * when all tasks have completed. - * - * Use this function in order to use the IO outside of a scheduled task. - * - * This function is thread-safe. - */ -SFUNC fio_s *fio_dup(fio_s *io); -/** - * Decreases a IO's reference count, so it could be automatically destroyed - * when all other tasks have completed. - * - * Use this function once finished with a IO that was `dup`-ed. - * - * This function is thread-safe. - */ -SFUNC void fio_undup(fio_s *io); -/** Suspends future "on_data" events for the IO. */ -SFUNC void fio_srv_suspend(fio_s *io); +FIOBJ - dynamic types -/** Listens for future "on_data" events related to the IO. */ -SFUNC void fio_srv_unsuspend(fio_s *io); +These are dynamic types that use pointer tagging for fast type identification. -/** Returns 1 if the IO handle was suspended. */ -SFUNC int fio_srv_is_suspended(fio_s *io); +Pointer tagging on 64 bit systems allows for 3 bits at the lower bits. On most +32 bit systems this is also true due to allocator alignment. When in doubt, use +the provided custom allocator. -/** Returns 1 if the IO handle is marked as open. */ -SFUNC int fio_srv_is_open(fio_s *io); +To keep the 64bit memory address alignment on 32bit systems, a 32bit metadata +integer is added when a virtual function table is missing. This doesn't effect +memory consumption on 64 bit systems and uses 4 bytes on 32 bit systems. -/** Returns the approximate number of bytes in the outgoing buffer. */ -SFUNC size_t fio_srv_backlog(fio_s *io); +Note: this code is placed at the end of the STL file, since it leverages most of +the SLT features and could be affected by their inclusion. +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_FIOBJ) && !defined(H___FIO_FIOBJ___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_FIOBJ___H +#define FIO___RECURSIVE_INCLUDE 99 /* 99 keeps EXTERN rules */ /* ***************************************************************************** -Task Scheduling +FIOBJ compilation settings (type names and JSON nesting limits). + +Type Naming Macros for FIOBJ types. By default, results in: +- fiobj_true() (constant, cannot be changed except manually) +- fiobj_false() (constant, cannot be changed except manually) +- fiobj_null() +- fiobj_num_new() ... (etc') +- fiobj_float_new() ... (etc') +- fiobj_str_new() ... (etc') +- fiobj_array_new() ... (etc') +- fiobj_hash_new() ... (etc') ***************************************************************************** */ -/** Schedules a task for delayed execution. This function is thread-safe. */ -SFUNC void fio_srv_defer(void (*task)(void *, void *), - void *udata1, - void *udata2); +#ifndef FIOBJ___NAME_NULL +#define FIOBJ___NAME_NULL null +#endif +#ifndef FIOBJ___NAME_NUMBER +#define FIOBJ___NAME_NUMBER num +#endif +#ifndef FIOBJ___NAME_FLOAT +#define FIOBJ___NAME_FLOAT float +#endif +#ifndef FIOBJ___NAME_STRING +#define FIOBJ___NAME_STRING str +#endif +#ifndef FIOBJ___NAME_ARRAY +#define FIOBJ___NAME_ARRAY array +#endif +#ifndef FIOBJ___NAME_HASH +#define FIOBJ___NAME_HASH hash +#endif -/** Schedules a timer bound task, see `fio_timer_schedule`. */ -SFUNC void fio_srv_run_every(fio_timer_schedule_args_s args); +#ifndef FIOBJ_MAX_NESTING /** - * Schedules a timer bound task, see `fio_timer_schedule`. + * Sets the limit on nesting level transversal by recursive functions. * - * Possible "named arguments" (fio_timer_schedule_args_s members) include: + * This effects JSON output / input and the `fiobj_each2` function since they + * are recursive. * - * * The timer function. If it returns a non-zero value, the timer stops: - * int (*fn)(void *, void *) - * * Opaque user data: - * void *udata1 - * * Opaque user data: - * void *udata2 - * * Called when the timer is done (finished): - * void (*on_stop)(void *, void *) - * * Timer interval, in milliseconds: - * uint32_t every - * * The number of times the timer should be performed. -1 == infinity: - * int32_t repetitions + * HOWEVER: this value will NOT effect the recursive `fiobj_free` which could + * (potentially) expload the stack if given melformed input such as cyclic data + * structures. + * + * Values should be less than 32K. */ -#define fio_srv_run_every(...) \ - fio_srv_run_every((fio_timer_schedule_args_s){__VA_ARGS__}) - -/** Returns the last millisecond when the server reviewed pending IO events. */ -SFUNC int64_t fio_srv_last_tick(void); - -/** Returns a pointer for the server's queue. */ -SFUNC fio_queue_s *fio_srv_queue(void); - -/**************************************************************************/ /** -Protocol IO Functions -============ - -The Protocol struct uses IO callbacks to allow an easy way to override the -system's IO functions. - -This defines Transport Layer callbacks that facil.io will treat as non-blocking -system calls and allows any protocol to easily add a secure (SSL/TLS) flavor if -desired. -*/ -struct fio_io_functions_s { - /** Helper that converts a `fio_tls_s` into the implementation's context. */ - void *(*build_context)(fio_tls_s *tls, uint8_t is_client); - /** Helper to free the context built by build_context. */ - void (*free_context)(void *context); - /** called when a new IO is first attached to a valid protocol. */ - void (*start)(fio_s *io); - /** Called to perform a non-blocking `read`, same as the system call. */ - ssize_t (*read)(int fd, void *buf, size_t len, void *context); - /** Called to perform a non-blocking `write`, same as the system call. */ - ssize_t (*write)(int fd, const void *buf, size_t len, void *context); - /** Sends any unsent internal data. Returns 0 only if all data was sent. */ - int (*flush)(int fd, void *context); - /** Called when the IO object has closed . */ - void (*finish)(int fd, void *context); - /** Called after the IO object is closed, used to cleanup its `tls` object. */ - void (*cleanup)(void *context); -}; - -/**************************************************************************/ /** -The Protocol -============ - -The Protocol struct defines the callbacks used for a family of connections and -sets their behavior. The Protocol struct is part of facil.io's core design. - -Protocols are usually global objects and the same protocol can be assigned to -multiple IO handles. - -All the callbacks receive a IO handle, which is used instead of the system's -file descriptor and protects callbacks and IO operations from sending data to -incorrect clients (possible `fd` "recycling"). -*/ -struct fio_protocol_s { - /** - * Reserved / private data - used by facil.io internally. - * MUST be initialized to zero. - */ - struct { - /* A linked list of currently attached IOs (ordered) - do NOT alter. */ - FIO_LIST_HEAD ios; - /* A linked list of other protocols used by IO core - do NOT alter. */ - FIO_LIST_NODE protocols; - /* internal flags - do NOT alter after initial initialization to zero. */ - uintptr_t flags; - } reserved; - /** Called when an IO is attached to the protocol. */ - void (*on_attach)(fio_s *io); - /** Called when a data is available. */ - void (*on_data)(fio_s *io); - /** called once all pending `fio_write` calls are finished. */ - void (*on_ready)(fio_s *io); - /** Called after the connection was closed (called once per IO). */ - void (*on_close)(void *udata); - /** - * Called when the server is shutting down, immediately before closing the - * connection. - * - * After the `on_shutdown` callback returns, the socket is marked for closure. - * - * Once the socket was marked for closure, facil.io will allow a limited - * amount of time for data to be sent, after which the socket might be closed - * even if the client did not consume all buffered data. - */ - void (*on_shutdown)(fio_s *io); - /** Called when a connection's timeout was reached */ - void (*on_timeout)(fio_s *io); - /** Used as a default `on_message` when an IO object subscribes. */ - void (*on_pubsub)(struct fio_msg_s *msg); - /** Allows user specific protocol agnostic callbacks. */ - void (*on_user1)(fio_s *io, void *user_data); - /** Allows user specific protocol agnostic callbacks. */ - void (*on_user2)(fio_s *io, void *user_data); - /** Allows user specific protocol agnostic callbacks. */ - void (*on_user3)(fio_s *io, void *user_data); - /** Reserved for future protocol agnostic callbacks. */ - void (*on_reserved)(fio_s *io, void *user_data); - /** - * Defines Transport Layer callbacks that facil.io will treat as non-blocking - * system calls. - */ - fio_io_functions_s io_functions; - /** - * The timeout value in milliseconds for all connections using this protocol. - * - * Limited to FIO_SRV_TIMEOUT_MAX seconds. Zero (0) == FIO_SRV_TIMEOUT_MAX - */ - uint32_t timeout; -}; +#define FIOBJ_MAX_NESTING 512 +#endif -/** Performs a task for each IO in the stated protocol. */ -FIO_SFUNC size_t fio_protocol_each(fio_protocol_s *protocol, - void (*task)(fio_s *, void *udata2), - void *udata2); +/* make sure roundtrips work */ +#ifndef JSON_MAX_DEPTH +#define JSON_MAX_DEPTH FIOBJ_MAX_NESTING +#endif +#ifndef FIOBJ_JSON_APPEND +#define FIOBJ_JSON_APPEND 1 +#endif /* ***************************************************************************** -Connection Object Links / Environment +General Requirements / Macros ***************************************************************************** */ -/** Named arguments for the `fio_env_set` function. */ -typedef struct { - /** A numerical type filter. Defaults to 0. Negative values are reserved. */ - intptr_t type; - /** The name for the link. The name and type uniquely identify the object. */ - fio_buf_info_s name; - /** The object being linked to the connection. */ - void *udata; - /** A callback that will be called once the connection is closed. */ - void (*on_close)(void *data); - /** Set to true (1) if the name string's life lives as long as the `env` . */ - uint8_t const_name; -} fio_env_set_args_s; +#ifdef __cplusplus /* C++ doesn't allow declarations for static variables */ +#define FIOBJ_EXTERN_OBJ extern "C" FIO_WEAK +#define FIOBJ_EXTERN_OBJ_IMP extern "C" FIO_WEAK +#elif defined(FIO_EXTERN) +#define FIOBJ_EXTERN_OBJ extern +#define FIOBJ_EXTERN_OBJ_IMP FIO_WEAK +#else +#define FIOBJ_EXTERN_OBJ static __attribute__((unused)) +#define FIOBJ_EXTERN_OBJ_IMP static __attribute__((unused)) +#endif +/* ***************************************************************************** +Debugging / Leak Detection +***************************************************************************** */ +#if defined(TEST) || defined(DEBUG) || defined(FIO_LEAK_COUNTER) +size_t FIO_WEAK FIOBJ_MARK_MEMORY_ALLOC_COUNTER; +size_t FIO_WEAK FIOBJ_MARK_MEMORY_FREE_COUNTER; +#define FIOBJ_MARK_MEMORY_ALLOC() \ + fio_atomic_add(&FIOBJ_MARK_MEMORY_ALLOC_COUNTER, 1) +#define FIOBJ_MARK_MEMORY_FREE() \ + fio_atomic_add(&FIOBJ_MARK_MEMORY_FREE_COUNTER, 1) +#define FIOBJ_MARK_MEMORY_PRINT() \ + FIO___LOG_PRINT_LEVEL( \ + ((FIOBJ_MARK_MEMORY_ALLOC_COUNTER == FIOBJ_MARK_MEMORY_FREE_COUNTER) \ + ? 4 /* FIO_LOG_LEVEL_INFO */ \ + : 3 /* FIO_LOG_LEVEL_WARNING */), \ + ((FIOBJ_MARK_MEMORY_ALLOC_COUNTER == FIOBJ_MARK_MEMORY_FREE_COUNTER) \ + ? "INFO: total remaining FIOBJ allocations: %zu (%zu - %zu)" \ + : "WARNING: LEAKED! FIOBJ allocations: %zu (%zu - %zu)"), \ + FIOBJ_MARK_MEMORY_ALLOC_COUNTER - FIOBJ_MARK_MEMORY_FREE_COUNTER, \ + FIOBJ_MARK_MEMORY_ALLOC_COUNTER, \ + FIOBJ_MARK_MEMORY_FREE_COUNTER) +#define FIOBJ_MARK_MEMORY_ENABLED 1 -/** Named arguments for the `fio_env_unset` function. */ -typedef struct { - /** A numerical type filter. Should be the same as used with `fio_env_set` */ - intptr_t type; - /** The name of the object. Should be the same as used with `fio_env_set` */ - fio_buf_info_s name; -} fio_env_get_args_s; +#else -/** Returns the named `udata` associated with the IO object (or `NULL`). */ -SFUNC void *fio_env_get(fio_s *io, fio_env_get_args_s); +#define FIOBJ_MARK_MEMORY_ALLOC_COUNTER 0 /* when testing unmarked FIOBJ */ +#define FIOBJ_MARK_MEMORY_FREE_COUNTER 0 /* when testing unmarked FIOBJ */ +#define FIOBJ_MARK_MEMORY_ALLOC() +#define FIOBJ_MARK_MEMORY_FREE() +#define FIOBJ_MARK_MEMORY_PRINT() +#define FIOBJ_MARK_MEMORY_ENABLED 0 +#endif -/** Returns the named `udata` associated with the IO object (or `NULL`). */ -#define fio_env_get(io, ...) fio_env_get(io, (fio_env_get_args_s){__VA_ARGS__}) +/* ***************************************************************************** +The FIOBJ Type +***************************************************************************** */ -/** - * Links an object to a connection's lifetime / environment. - * - * The `on_close` callback will be called once the connection has died. - * - * If the `io` is NULL, the value will be set for the global environment. - */ -SFUNC void fio_env_set(fio_s *io, fio_env_set_args_s); +/** Use the FIOBJ type for dynamic types. */ +typedef struct FIOBJ_s { + struct FIOBJ_s *compiler_validation_type; +} * FIOBJ; -/** - * Links an object to a connection's lifetime, calling the `on_close` callback - * once the connection has died. - * - * If the `io` is NULL, the value will be set for the global environment, in - * which case the `on_close` callback will only be called once the process - * exits. - * - * This is a helper MACRO that allows the function to be called using named - * arguments. - */ -#define fio_env_set(io, ...) fio_env_set(io, (fio_env_set_args_s){__VA_ARGS__}) +/** FIOBJ type enum for common / primitive types. */ +typedef enum { + FIOBJ_T_NUMBER = 0x01, /* 0b001 3 bits taken for small numbers */ + FIOBJ_T_PRIMITIVE = 2, /* 0b010 a lonely second bit signifies a primitive */ + FIOBJ_T_STRING = 3, /* 0b011 */ + FIOBJ_T_ARRAY = 4, /* 0b100 */ + FIOBJ_T_HASH = 5, /* 0b101 */ + FIOBJ_T_FLOAT = 6, /* 0b110 */ + FIOBJ_T_OTHER = 7, /* 0b111 dynamic type - test content */ +} fiobj_class_en; -/** - * Un-links an object from the connection's lifetime, so it's `on_close` - * callback will NOT be called. - * - * Returns 0 on success and -1 if the object couldn't be found. - */ -SFUNC int fio_env_unset(fio_s *io, fio_env_get_args_s); +#define FIOBJ_T_NULL 2 /* 0b010 a lonely second bit signifies a primitive */ +#define FIOBJ_T_TRUE 18 /* 0b010 010 - primitive value */ +#define FIOBJ_T_FALSE 34 /* 0b100 010 - primitive value */ -/** - * Un-links an object from the connection's lifetime, so it's `on_close` - * callback will NOT be called. - * - * Returns 0 on success and -1 if the object couldn't be found. - * - * This is a helper MACRO that allows the function to be called using named - * arguments. - */ -#define fio_env_unset(io, ...) \ - fio_env_unset(io, (fio_env_get_args_s){__VA_ARGS__}) +/** Use the macros to avoid future API changes. */ +#define FIOBJ_TYPE(o) fiobj_type(o) +/** Use the macros to avoid future API changes. */ +#define FIOBJ_TYPE_IS(o, type) (fiobj_type(o) == type) +/** Identifies an invalid type identifier (returned from FIOBJ_TYPE(o) */ +#define FIOBJ_T_INVALID 0 +/** Identifies an invalid object */ +#define FIOBJ_INVALID 0 +/** Tests if the object is (probably) a valid FIOBJ */ +#define FIOBJ_IS_INVALID(o) (((uintptr_t)(o)&7UL) == 0) +#define FIOBJ_IS_NULL(o) (FIOBJ_IS_INVALID(o) || ((o) == FIOBJ_T_NULL)) +#define FIOBJ_TYPE_CLASS(o) ((fiobj_class_en)(((uintptr_t)(o)) & 7UL)) +#define FIOBJ_PTR_TAG(o, klass) ((uintptr_t)(((uintptr_t)(o)) | (klass))) +#define FIOBJ_PTR_UNTAG(o) ((uintptr_t)(((uintptr_t)(o)) & (~7ULL))) +/** Returns an objects type. This isn't limited to known types. */ +FIO_IFUNC size_t fiobj_type(FIOBJ o); -/** - * Removes an object from the connection's lifetime / environment, calling it's - * `on_close` callback as if the connection was closed. - */ -SFUNC int fio_env_remove(fio_s *io, fio_env_get_args_s); +/* ***************************************************************************** +FIOBJ Memory Management +***************************************************************************** */ -/** - * Removes an object from the connection's lifetime / environment, calling it's - * `on_close` callback as if the connection was closed. - * - * This is a helper MACRO that allows the function to be called using named - * arguments. - */ -#define fio_env_remove(io, ...) \ - fio_env_remove(io, (fio_env_get_args_s){__VA_ARGS__}) +/** Increases an object's reference count (or copies) and returns it. */ +FIO_IFUNC FIOBJ fiobj_dup(FIOBJ o); + +/** Decreases an object's reference count or frees it. */ +FIO_IFUNC void fiobj_free(FIOBJ o); /* ***************************************************************************** -TLS Context Helper Types +FIOBJ Data / Info ***************************************************************************** */ -/** Performs a `new` operation, returning a new `fio_tls_s` context. */ -SFUNC fio_tls_s *fio_tls_new(void); - -/** Takes a parsed URL and optional TLS target and returns a TLS if needed. */ -SFUNC fio_tls_s *fio_tls_from_url(fio_tls_s *target_or_null, fio_url_s url); +/** Compares two objects. */ +FIO_IFUNC unsigned char FIO_NAME_BL(fiobj, eq)(FIOBJ a, FIOBJ b); -/** Performs a `dup` operation, increasing the object's reference count. */ -SFUNC fio_tls_s *fio_tls_dup(fio_tls_s *); +/** Returns a temporary String representation for any FIOBJ object. */ +FIO_IFUNC fio_str_info_s FIO_NAME2(fiobj, cstr)(FIOBJ o); -/** Performs a `free` operation, reducing the reference count and freeing. */ -SFUNC void fio_tls_free(fio_tls_s *); +/** Returns an integer representation for any FIOBJ object. */ +FIO_IFUNC intptr_t FIO_NAME2(fiobj, i)(FIOBJ o); -/** - * Adds a certificate a new SSL/TLS context / settings object (SNI support). - * - * fio_tls_cert_add(tls, "www.example.com", - * "public_key.pem", - * "private_key.pem", NULL ); - * - * NOTE: Except for the `tls` and `server_name` arguments, all arguments might - * be `NULL`, which a context builder (`fio_io_functions_s`) should treat as a - * request for a self-signed certificate. It may be silently ignored. - */ -SFUNC fio_tls_s *fio_tls_cert_add(fio_tls_s *, - const char *server_name, - const char *public_cert_file, - const char *private_key_file, - const char *pk_password); +/** Returns a float (double) representation for any FIOBJ object. */ +FIO_IFUNC double FIO_NAME2(fiobj, f)(FIOBJ o); -/** - * Adds an ALPN protocol callback to the SSL/TLS context. - * - * The first protocol added will act as the default protocol to be selected. - * - * A `NULL` protocol name will be silently ignored. - * - * A `NULL` callback (`on_selected`) will be silently replaced with a no-op. - */ -SFUNC fio_tls_s *fio_tls_alpn_add(fio_tls_s *tls, - const char *protocol_name, - void (*on_selected)(fio_s *)); +/** Calculates an object's hash value for a specific hash map object. */ +FIO_IFUNC uint64_t FIO_NAME2(fiobj, hash)(FIOBJ object_key); -/** Calls the `on_selected` callback for the `fio_tls_s` object. */ -SFUNC int fio_tls_alpn_select(fio_tls_s *tls, - const char *protocol_name, - size_t name_length, - fio_s *); +/* ***************************************************************************** +FIOBJ Containers (iteration) +***************************************************************************** */ -/** - * Adds a certificate to the "trust" list, which automatically adds a peer - * verification requirement. - * - * If `public_cert_file` is `NULL`, implementation is expected to add the - * system's default trust registry. - * - * Note: when the `fio_tls_s` object is used for server connections, this should - * limit connections to clients that connect using a trusted certificate. - * - * fio_tls_trust_add(tls, "google-ca.pem" ); - */ -SFUNC fio_tls_s *fio_tls_trust_add(fio_tls_s *, const char *public_cert_file); +/** Iteration information structure passed to the callback. */ +typedef struct fiobj_each_s { + /** The being iterated. Once set, cannot be safely changed. */ + FIOBJ const parent; + /** The index to start at / the current object's index */ + uint64_t index; + /** The callback / task called for each index, may be updated mid-cycle. */ + int (*task)(struct fiobj_each_s *info); + /** The argument passed along to the task. */ + void *udata; + /** The value of the current object in the Array or Hash Map */ + FIOBJ value; + /* The key, if a Hash Map */ + FIOBJ key; +} fiobj_each_s; /** - * Returns the number of `fio_tls_cert_add` instructions. + * Performs a task for each element held by the FIOBJ object. * - * This could be used when deciding if to add a NULL instruction (self-signed). + * If `task` returns -1, the `each` loop will break (stop). * - * If `fio_tls_cert_add` was never called, zero (0) is returned. + * Returns the "stop" position - the number of elements processed + `start_at`. */ -SFUNC uintptr_t fio_tls_cert_count(fio_tls_s *tls); +FIO_SFUNC uint32_t fiobj_each1(FIOBJ o, + int (*task)(fiobj_each_s *info), + void *udata, + int32_t start_at); /** - * Returns the number of registered ALPN protocol names. - * - * This could be used when deciding if protocol selection should be delegated to - * the ALPN mechanism, or whether a protocol should be immediately assigned. + * Performs a task for the object itself and each element held by the FIOBJ + * object or any of it's elements (a deep task). * - * If no ALPN protocols are registered, zero (0) is returned. - */ -SFUNC uintptr_t fio_tls_alpn_count(fio_tls_s *tls); - -/** - * Returns the number of `fio_tls_trust_add` instructions. + * The order of performance is by order of appearance, as if all nesting levels + * were flattened. * - * This could be used when deciding if to disable peer verification or not. + * If `task` returns -1, the `each` loop will break (stop). * - * If `fio_tls_trust_add` was never called, zero (0) is returned. + * Returns the number of elements processed. */ -SFUNC uintptr_t fio_tls_trust_count(fio_tls_s *tls); +SFUNC uint32_t fiobj_each2(FIOBJ o, + int (*task)(fiobj_each_s *info), + void *udata); -/** Arguments (and info) for `fio_tls_each`. */ -typedef struct fio_tls_each_s { - fio_tls_s *tls; - void *udata; - void *udata2; - int (*each_cert)(struct fio_tls_each_s *, - const char *server_name, - const char *public_cert_file, - const char *private_key_file, - const char *pk_password); - int (*each_alpn)(struct fio_tls_each_s *, - const char *protocol_name, - void (*on_selected)(fio_s *)); - int (*each_trust)(struct fio_tls_each_s *, const char *public_cert_file); -} fio_tls_each_s; +/* ***************************************************************************** +FIOBJ Primitives (NULL, True, False) +***************************************************************************** */ -/** Calls callbacks for certificate, trust certificate and ALPN added. */ -SFUNC int fio_tls_each(fio_tls_each_s); +/** Returns the `true` primitive. */ +FIO_IFUNC FIOBJ fiobj_true(void) { return (FIOBJ)(FIOBJ_T_TRUE); } -/** `fio_tls_each` helper macro, see `fio_tls_each_s` for named arguments. */ -#define fio_tls_each(tls_, ...) \ - fio_tls_each(((fio_tls_each_s){.tls = tls_, __VA_ARGS__})) +/** Returns the `false` primitive. */ +FIO_IFUNC FIOBJ fiobj_false(void) { return (FIOBJ)(FIOBJ_T_FALSE); } -/** If `NULL` returns current default, otherwise sets it. */ -SFUNC fio_io_functions_s fio_tls_default_io_functions(fio_io_functions_s *); +/** Returns the `nil` / `null` primitive. */ +FIO_IFUNC FIOBJ FIO_NAME(fiobj, FIOBJ___NAME_NULL)(void) { + return (FIOBJ)(FIOBJ_T_NULL); +} /* ***************************************************************************** -Server Async - Worker Threads for non-IO tasks +FIOBJ Type - Extensibility (FIOBJ_T_OTHER) ***************************************************************************** */ -/** The Server Async Queue type. */ -struct fio_srv_async_s { - fio_queue_s *q; - uint32_t count; - fio_queue_s queue; - FIO_LIST_NODE node; -}; - -/** - * Initializes a server - async (multi-threaded) task queue. - * - * It is recommended that the `fio_srv_async_s` be allocated as a static - * variable, as its memory must remain valid throughout the lifetime of the - * server's app. - * - * The queue automatically spawns threads and shuts down as the server starts or - * stops. - */ -FIO_IFUNC fio_queue_s *fio_srv_async_queue(fio_srv_async_s *q) { return q->q; } - -/** - * Initializes an async server queue for multi-threaded (non IO) tasks. - * - * This function can only be called from the server's thread (or the thread that - * will eventually run the server). - */ -SFUNC void fio_srv_async_init(fio_srv_async_s *q, uint32_t threads); +/** FIOBJ types can be extended using virtual function tables. */ +typedef struct { + /** + * MUST return a unique number to identify object type. + * + * Numbers (type IDs) under 100 are reserved. Numbers under 40 are illegal. + */ + size_t type_id; + /** Test for equality between two objects with the same `type_id` */ + unsigned char (*is_eq)(FIOBJ restrict a, FIOBJ restrict b); + /** Converts an object to a String */ + fio_str_info_s (*to_s)(FIOBJ o); + /** Converts an object to an integer */ + intptr_t (*to_i)(FIOBJ o); + /** Converts an object to a double */ + double (*to_f)(FIOBJ o); + /** Returns the number of exposed elements held by the object, if any. */ + uint32_t (*count)(FIOBJ o); + /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ + uint32_t (*each1)(FIOBJ o, + int (*task)(fiobj_each_s *e), + void *udata, + int32_t start_at); + /** + * Decreases the reference count and/or frees the object, calling `free2` for + * any nested objects. + */ + void (*free2)(FIOBJ o); +} FIOBJ_class_vtable_s; -/** Initializes an async server queue for multo-threaded (non IO) tasks. */ -SFUNC void fio_srv_async_update(fio_srv_async_s *q, uint32_t threads); +FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___OBJECT_CLASS_VTBL; -#define fio_srv_async(q_, ...) fio_queue_push((q_)->q, __VA_ARGS__) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_NAME fiobj_object +#define FIO_REF_TYPE void * +#define FIO_REF_METADATA const FIOBJ_class_vtable_s * +#define FIO_REF_METADATA_INIT(m) \ + do { \ + m = &FIOBJ___OBJECT_CLASS_VTBL; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#define FIO_REF_METADATA_DESTROY(m) \ + do { \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE /* ***************************************************************************** -Simple Server Implementation - inlined static functions +FIOBJ Integers ***************************************************************************** */ -/** Defines a get / set function for the property. */ -#define FIO_SERVER_GETSET_FUNC(property, index) \ - FIO_IFUNC void *fio_##property##_set(fio_s *io, void *property) { \ - void *old = ((void **)io)[index]; \ - ((void **)io)[index] = property; \ - return old; \ - } \ - FIO_IFUNC void *fio_##property##_get(fio_s *io) { \ - return ((void **)io)[index]; \ - } -FIO_SERVER_GETSET_FUNC(udata, 0) -FIO_SERVER_GETSET_FUNC(tls, 1) - -/* ***************************************************************************** +/** Creates a new Number object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(intptr_t i); +/** Reads the number from a FIOBJ Number. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(FIOBJ i); +/** Reads the number from a FIOBJ Number, fitting it in a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(FIOBJ i); - Simple Server Implementation - possibly externed functions. +/** Returns a String representation of the number (in base 10). */ +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), + cstr)(FIOBJ i); +/** Frees a FIOBJ number. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), free)(FIOBJ i); -REMEMBER: memory allocations: FIO_MEM_REALLOC_ / FIO_MEM_FREE_ -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___NUMBER_CLASS_VTBL; -#define FIO___SRV_GET_TIME_MILLI() fio_time2milli(fio_time_real()) /* ***************************************************************************** -Protocol validation +FIOBJ Floats ***************************************************************************** */ -static void fio___srv_on_ev_mock_sus(fio_s *io) { fio_srv_suspend(io); } -static void fio___srv_on_ev_mock(fio_s *io) { (void)(io); } -static void fio___srv_on_ev_pubsub_mock(struct fio_msg_s *msg) { (void)(msg); } -static void fio___srv_on_user_mock(fio_s *io, void *i_) { (void)io, (void)i_; } -static void fio___srv_on_close_mock(void *ptr) { (void)ptr; } -static void fio___srv_on_ev_on_timeout(fio_s *io) { fio_close_now(io); } -static void fio___srv_on_timeout_never(fio_s *io) { fio_touch(io); } - -/* Called to perform a non-blocking `read`, same as the system call. */ -static ssize_t fio___io_func_default_read(int fd, - void *buf, - size_t len, - void *tls) { - return fio_sock_read(fd, buf, len); - (void)tls; -} -/** Called to perform a non-blocking `write`, same as the system call. */ -static ssize_t fio___io_func_default_write(int fd, - const void *buf, - size_t len, - void *tls) { - return fio_sock_write(fd, buf, len); - (void)tls; -} -/** Sends any unsent internal data. Returns 0 only if all data was sent. */ -static int fio___io_func_default_flush(int fd, void *tls) { - return 0; - (void)fd, (void)tls; -} -/** Sends any unsent internal data. Returns 0 only if all data was sent. */ -static void fio___io_func_default_finish(int fd, void *tls) { - (void)fd, (void)tls; -} -/** Builds a local TLS context out of the fio_tls_s object. */ -static void *fio___io_func_default_build_context(fio_tls_s *tls, - uint8_t is_client) { - if (!tls) - return NULL; - FIO_ASSERT(0, - "SSL/TLS `build_context` was called, but no SSL/TLS " - "implementation found."); - return NULL; - (void)tls, (void)is_client; -} -/** Builds a local TLS context out of the fio_tls_s object. */ -static void fio___io_func_default_free_context(void *context) { - if (!context) - return; - FIO_ASSERT(0, - "SSL/TLS `free_context` was called, but no SSL/TLS " - "implementation found."); - (void)context; -} - -static void fio___io_func_free_context_caller_task(void *fn_ptr, - void *context) { - union { - void (*free_context)(void *context); - void *fn_ptr; - } u = {.fn_ptr = fn_ptr}; - u.free_context(context); -} +/** Creates a new Float (double) object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(double i); -static void fio___io_func_free_context_caller(void (*free_context)(void *), - void *context) { - union { - void (*free_context)(void *context); - void *fn_ptr; - } u = {.free_context = free_context}; - fio_queue_push(fio_srv_queue(), - fio___io_func_free_context_caller_task, - u.fn_ptr, - context); -} -/** Builds a local TLS context out of the fio_tls_s object. */ -// static void fio___io_func_default_free_context(void *context) { -// if (!context) -// return; -// FIO_ASSERT(0, -// "SSL/TLS `free_context` was called, but no SSL/TLS " -// "implementation found."); -// (void)context; -// } +/** Reads the number from a FIOBJ Float rounding it to an integer. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(FIOBJ i); -// ; +/** Reads the value from a FIOBJ Float, as a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(FIOBJ i); -FIO_SFUNC void fio___srv_init_protocol(fio_protocol_s *pr, _Bool has_tls) { - pr->reserved.protocols = FIO_LIST_INIT(pr->reserved.protocols); - pr->reserved.ios = FIO_LIST_INIT(pr->reserved.ios); - fio_io_functions_s io_fn = { - .build_context = fio___io_func_default_build_context, - .free_context = fio___io_func_default_free_context, - .start = fio___srv_on_ev_mock, - .read = fio___io_func_default_read, - .write = fio___io_func_default_write, - .flush = fio___io_func_default_flush, - .finish = fio___io_func_default_finish, - .cleanup = fio___srv_on_close_mock, - }; - if (has_tls) - io_fn = fio_tls_default_io_functions(NULL); - if (!pr->on_attach) - pr->on_attach = fio___srv_on_ev_mock; - if (!pr->on_data) - pr->on_data = fio___srv_on_ev_mock_sus; - if (!pr->on_ready) - pr->on_ready = fio___srv_on_ev_mock; - if (!pr->on_close) - pr->on_close = fio___srv_on_close_mock; - if (!pr->on_shutdown) - pr->on_shutdown = fio___srv_on_ev_mock; - if (!pr->on_timeout) - pr->on_timeout = fio___srv_on_ev_on_timeout; - if (!pr->on_pubsub) - pr->on_pubsub = fio___srv_on_ev_pubsub_mock; - if (!pr->on_user1) - pr->on_user1 = fio___srv_on_user_mock; - if (!pr->on_user2) - pr->on_user2 = fio___srv_on_user_mock; - if (!pr->on_user3) - pr->on_user3 = fio___srv_on_user_mock; - if (!pr->on_reserved) - pr->on_reserved = fio___srv_on_user_mock; - if (!pr->io_functions.build_context) - pr->io_functions.build_context = io_fn.build_context; - if (!pr->io_functions.free_context) - pr->io_functions.free_context = io_fn.free_context; - if (!pr->io_functions.start) - pr->io_functions.start = io_fn.start; - if (!pr->io_functions.read) - pr->io_functions.read = io_fn.read; - if (!pr->io_functions.write) - pr->io_functions.write = io_fn.write; - if (!pr->io_functions.flush) - pr->io_functions.flush = io_fn.flush; - if (!pr->io_functions.finish) - pr->io_functions.finish = io_fn.finish; - if (!pr->io_functions.cleanup) - pr->io_functions.cleanup = io_fn.cleanup; -} +/** Returns a String representation of the float. */ +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), + cstr)(FIOBJ i); -/* the FIO___MOCK_PROTOCOL is used to manage hijacked / zombie connections. */ -static fio_protocol_s FIO___MOCK_PROTOCOL; +/** Frees a FIOBJ Float. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), free)(FIOBJ i); -FIO_IFUNC void fio___srv_init_protocol_test(fio_protocol_s *pr, _Bool has_tls) { - if (!fio_atomic_or(&pr->reserved.flags, 1)) - fio___srv_init_protocol(pr, has_tls); -} +FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___FLOAT_CLASS_VTBL; /* ***************************************************************************** -Server / IO environment support (`env`) +FIOBJ Strings ***************************************************************************** */ -/** An object that can be linked to any facil.io connection (fio_s). */ -typedef struct { - void (*on_close)(void *data); - void *udata; -} fio___srv_env_obj_s; - -/* unordered `env` dictionary style map */ -#define FIO_UMAP_NAME fio___srv_env -#define FIO_MAP_KEY_KSTR -#define FIO_MAP_VALUE fio___srv_env_obj_s -#define FIO_MAP_VALUE_DESTROY(o) \ +#define FIO_STR_NAME FIO_NAME(fiobj, FIOBJ___NAME_STRING) +#define FIO_STR_OPTIMIZE_EMBEDDED 1 +#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_STRING) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(s) \ do { \ - if ((o).on_close) \ - (o).on_close((o).udata); \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), destroy) \ + ((FIOBJ)FIOBJ_PTR_TAG(&s, FIOBJ_T_STRING)); \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_REF_INIT(s_) \ + do { \ + s_ = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s))FIO_STR_INIT; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ } while (0) -#define FIO_MAP_DESTROY_AFTER_COPY 0 -#define FIO___RECURSIVE_INCLUDE 1 +#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ +#define FIO_REF_METADATA uint32_t +#endif +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_STRING) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_STRING) +#define FIO_PTR_TAG_TYPE FIOBJ #include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE - -typedef struct { - fio_thread_mutex_t lock; - fio___srv_env_s env; -} fio___srv_env_safe_s; - -#define FIO___SRV_ENV_SAFE_INIT \ - { .lock = FIO_THREAD_MUTEX_INIT, .env = FIO_MAP_INIT } -FIO_IFUNC void *fio___srv_env_safe_get(fio___srv_env_safe_s *e, - char *key_, - size_t len, - intptr_t type_) { - void *r; - fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); - const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); - fio_thread_mutex_lock(&e->lock); - r = fio___srv_env_get(&e->env, hash, key).udata; - fio_thread_mutex_unlock(&e->lock); - return r; +/* Creates a new FIOBJ string object, copying the data to the new string. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_cstr)(const char *ptr, size_t len) { + FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(s, ptr, len); + return s; } -FIO_IFUNC void fio___srv_env_safe_set(fio___srv_env_safe_s *e, - char *key_, - size_t len, - intptr_t type_, - fio___srv_env_obj_s val, - uint8_t key_is_const) { - fio_str_info_s key = FIO_STR_INFO3(key_, len, !key_is_const); - const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); - fio_thread_mutex_lock(&e->lock); - fio___srv_env_set(&e->env, hash, key, val, NULL); - fio_thread_mutex_unlock(&e->lock); +/* Creates a new FIOBJ string object with (at least) the requested capacity. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_buf)(size_t capa) { + FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), reserve)(s, capa); + return s; } -FIO_IFUNC int fio___srv_env_safe_unset(fio___srv_env_safe_s *e, - char *key_, - size_t len, - intptr_t type_) { - int r; - fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); - const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); - fio___srv_env_obj_s old; - fio_thread_mutex_lock(&e->lock); - r = fio___srv_env_remove(&e->env, hash, key, &old); - fio_thread_mutex_unlock(&e->lock); - return r; +/* Creates a new FIOBJ string object, copying the origin (`fiobj2cstr`). */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + new_copy)(FIOBJ original) { + FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); + fio_str_info_s i = FIO_NAME2(fiobj, cstr)(original); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(s, i.buf, i.len); + return s; } -FIO_IFUNC int fio___srv_env_safe_remove(fio___srv_env_safe_s *e, - char *key_, - size_t len, - intptr_t type_) { - int r; - fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); - const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); - fio_thread_mutex_lock(&e->lock); - r = fio___srv_env_remove(&e->env, hash, key, NULL); - fio_thread_mutex_unlock(&e->lock); - return r; +/** Returns information about the string. Same as fiobj_str_info(). */ +FIO_IFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + cstr)(FIOBJ s) { + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(s); } -FIO_IFUNC void fio___srv_env_safe_destroy(fio___srv_env_safe_s *e) { - fio___srv_env_destroy(&e->env); /* no need to lock, performed in IO thread. */ - fio_thread_mutex_destroy(&e->lock); - *e = (fio___srv_env_safe_s)FIO___SRV_ENV_SAFE_INIT; -} +/** + * Creates a temporary FIOBJ String object on the stack. + * + * String data might be allocated dynamically. + */ +#define FIOBJ_STR_TEMP_VAR(str_name) \ + struct { \ + uint64_t i1; \ + uint64_t i2; \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ + } FIO_NAME(str_name, __auto_mem_tmp) = {0x7f7f7f7f7f7f7f7fULL, \ + 0x7f7f7f7f7f7f7f7fULL, \ + FIO_STR_INIT}; \ + FIOBJ str_name = \ + (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ + FIOBJ_T_STRING); + +/** + * Creates a temporary FIOBJ String object on the stack, initialized with a + * static string. + * + * String data might be allocated dynamically. + */ +#define FIOBJ_STR_TEMP_VAR_STATIC(str_name, buf_, len_) \ + struct { \ + uint64_t i1; \ + uint64_t i2; \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ + } FIO_NAME(str_name, \ + __auto_mem_tmp) = {0x7f7f7f7f7f7f7f7fULL, \ + 0x7f7f7f7f7f7f7f7fULL, \ + FIO_STR_INIT_STATIC2((buf_), (len_))}; \ + FIOBJ str_name = \ + (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ + FIOBJ_T_STRING); + +/** + * Creates a temporary FIOBJ String object on the stack, initialized with a + * static string. + * + * String data might be allocated dynamically. + */ +#define FIOBJ_STR_TEMP_VAR_EXISTING(str_name, buf_, len_, capa_) \ + struct { \ + uint64_t i1; \ + uint64_t i2; \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ + } FIO_NAME(str_name, __auto_mem_tmp) = { \ + 0x7f7f7f7f7f7f7f7fULL, \ + 0x7f7f7f7f7f7f7f7fULL, \ + FIO_STR_INIT_EXISTING((buf_), (len_), (capa_))}; \ + FIOBJ str_name = \ + (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ + FIOBJ_T_STRING); + +/** Resets a temporary FIOBJ String, freeing and any resources allocated. */ +#define FIOBJ_STR_TEMP_DESTROY(str_name) \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), destroy)(str_name); /* ***************************************************************************** -IO Validity Map - Type +FIOBJ Arrays ***************************************************************************** */ -#ifndef FIO_VALIDITY_MAP_USE -#define FIO_VALIDITY_MAP_USE 0 -#endif -#if FIO_VALIDITY_MAP_USE -#define FIO_UMAP_NAME fio_validity_map -#define FIO_MAP_KEY fio_s * -#define FIO_MAP_HASH_FN(o) fio_risky_ptr(o) -#define FIO_MAP_KEY_CMP(a, b) ((a) == (b)) -#ifndef FIO_VALIDATE_IO_MUTEX -/* mostly for debugging possible threading issues. */ -#define FIO_VALIDATE_IO_MUTEX 0 +#define FIO_ARRAY_NAME FIO_NAME(fiobj, FIOBJ___NAME_ARRAY) +#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_ARRAY) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(a) \ + do { \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), destroy) \ + ((FIOBJ)FIOBJ_PTR_TAG(&a, FIOBJ_T_ARRAY)); \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_REF_INIT(a) \ + do { \ + a = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), s))FIO_ARRAY_INIT; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ +#define FIO_REF_METADATA uint32_t #endif -#define FIO___RECURSIVE_INCLUDE 1 +#define FIO_ARRAY_TYPE FIOBJ +#define FIO_ARRAY_TYPE_CMP(a, b) FIO_NAME_BL(fiobj, eq)((a), (b)) +#define FIO_ARRAY_TYPE_DESTROY(o) fiobj_free(o) +#define FIO_ARRAY_TYPE_CONCAT_COPY(dest, obj) \ + do { \ + dest = fiobj_dup(obj); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_ARRAY) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_ARRAY) +#define FIO_PTR_TAG_TYPE FIOBJ #include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE -#else -typedef void *fio_validity_map_s; -#endif /* ***************************************************************************** -Global State +FIOBJ Hash Maps ***************************************************************************** */ -static void fio___srv_poll_on_data_schd(void *udata); -static void fio___srv_poll_on_ready_schd(void *udata); -static void fio___srv_poll_on_close_schd(void *udata); - -static struct { - FIO_LIST_HEAD protocols; -#if FIO_VALIDITY_MAP_USE - fio_validity_map_s valid; -#if FIO_VALIDATE_IO_MUTEX - fio_thread_mutex_t valid_lock; -#endif -#endif /* FIO_VALIDITY_MAP_USE */ - fio___srv_env_safe_s env; - fio_poll_s poll_data; - int64_t tick; - fio_thread_pid_t root_pid; - fio_thread_pid_t pid; - fio_s *wakeup; - int wakeup_fd; - int wakeup_wait; - uint16_t workers; - uint8_t is_worker; - volatile uint8_t stop; - FIO_LIST_HEAD async; -} fio___srvdata = { -#if FIO_VALIDATE_IO_MUTEX && FIO_VALIDITY_MAP_USE - .valid_lock = FIO_THREAD_MUTEX_INIT, -#endif -#if !FIO_OS_WIN - .env = FIO___SRV_ENV_SAFE_INIT, +#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_HASH) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(a) \ + do { \ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), destroy) \ + ((FIOBJ)FIOBJ_PTR_TAG(&(a), FIOBJ_T_HASH)); \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_REF_INIT(a) \ + do { \ + a = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), s))FIO_MAP_INIT; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ +#define FIO_REF_METADATA uint32_t #endif - .tick = 0, - .wakeup_fd = -1, - .stop = 1, -}; +#define FIO_MAP_NAME FIO_NAME(fiobj, FIOBJ___NAME_HASH) +#define FIO_MAP_ORDERED 1 +#define FIO_MAP_KEY FIOBJ +#define FIO_MAP_KEY_CMP(a, b) FIO_NAME_BL(fiobj, eq)((a), (b)) +#define FIO_MAP_KEY_COPY(dest, o) (dest = fiobj_dup(o)) +#define FIO_MAP_KEY_DESTROY(o) fiobj_free(o) +#define FIO_MAP_VALUE FIOBJ +#define FIO_MAP_HASH_FN(o) FIO_NAME2(fiobj, hash)(o) +#define FIO_MAP_VALUE_DESTROY(o) fiobj_free(o) +#define FIO_MAP_VALUE_DISCARD(o) fiobj_free(o) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_HASH) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_HASH) +#define FIO_PTR_TAG_TYPE FIOBJ +/* TODO! auto-hash object value */ +#include FIO_INCLUDE_FILE +/** + * Sets a value in a hash map, allocating the key String and automatically + * calculating the hash value. + */ +FIO_IFUNC +FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + set2)(FIOBJ hash, const char *key, size_t len, FIOBJ value); -/** Returns current process id. */ -SFUNC int fio_srv_pid(void) { return fio___srvdata.pid; } +/** + * Finds a value in the hash map, using a temporary String and automatically + * calculating the hash value. + */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + get2)(FIOBJ hash, const char *buf, size_t len); -/** Returns the root / master process id. */ -SFUNC int fio_srv_root_pid(void) { return fio___srvdata.root_pid; } +/** + * Removes a value in a hash map, using a temporary String and automatically + * calculating the hash value. + */ +FIO_IFUNC int FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + remove2)(FIOBJ hash, + const char *buf, + size_t len, + FIOBJ *old); /* ***************************************************************************** -Wakeup Protocol +FIOBJ JSON support ***************************************************************************** */ -FIO_SFUNC void fio___srv_wakeup_cb(fio_s *io) { - char buf[512]; - ssize_t r = fio_sock_read(fio_fd_get(io), buf, 512); - (void)r; - FIO_LOG_DDEBUG2("(%d) fio___srv_wakeup called", fio___srvdata.pid); - fio___srvdata.wakeup_wait = 0; -} -FIO_SFUNC void fio___srv_wakeup_on_close(void *ignr_) { - (void)ignr_; - fio_sock_close(fio___srvdata.wakeup_fd); - fio___srvdata.wakeup = NULL; - fio___srvdata.wakeup_fd = -1; - FIO_LOG_DEBUG2("(%d) fio___srv_wakeup destroyed", fio___srvdata.pid); -} - -FIO_SFUNC void fio___srv_wakeup(void) { - if (!fio___srvdata.wakeup || fio_queue_count(fio_srv_queue()) > 3 || - fio_atomic_or(&fio___srvdata.wakeup_wait, 1)) - return; - char buf[1] = {(char)~0}; - ssize_t ignr = fio_sock_write(fio___srvdata.wakeup_fd, buf, 1); - (void)ignr; -} - -static fio_protocol_s FIO___SRV_WAKEUP_PROTOCOL = { - .on_data = fio___srv_wakeup_cb, - .on_close = fio___srv_wakeup_on_close, - .on_timeout = fio___srv_on_timeout_never, -}; - -FIO_SFUNC void fio___srv_wakeup_init(void) { - if (fio___srvdata.wakeup) - return; - int fds[2]; - if (pipe(fds)) { - FIO_LOG_ERROR("(%d) couldn't open wakeup pipes, fio___srv_wakeup disabled.", - fio___srvdata.pid); - return; - } - fio_sock_set_non_block(fds[0]); - fio_sock_set_non_block(fds[1]); - fio___srvdata.wakeup_fd = fds[1]; - fio___srvdata.wakeup = fio_srv_attach_fd(fds[0], - &FIO___SRV_WAKEUP_PROTOCOL, - (void *)(uintptr_t)fds[1], - NULL); - FIO_LOG_DEBUG2("(%d) fio___srv_wakeup initialized", fio___srvdata.pid); -} - -/* ***************************************************************************** -Server Timers and Task Queues -***************************************************************************** */ +/** + * Returns a JSON valid FIOBJ String, representing the object. + * + * If `dest` is an existing String, the formatted JSON data will be appended to + * the existing string. + */ +FIO_IFUNC FIOBJ FIO_NAME2(fiobj, json)(FIOBJ dest, FIOBJ o, uint8_t beautify); -static fio_timer_queue_s fio___srv_timer[1] = {FIO_TIMER_QUEUE_INIT}; -static fio_queue_s fio___srv_tasks[1]; +/** + * Updates a Hash using JSON data. + * + * Parsing errors and non-dictionary object JSON data are silently ignored, + * attempting to update the Hash as much as possible before any errors + * encountered. + * + * Conflicting Hash data is overwritten (preferring the new over the old). + * + * Returns the number of bytes consumed. On Error, 0 is returned and no data is + * consumed. + */ +SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(FIOBJ hash, fio_str_info_s str); -/** Returns the last millisecond when the server reviewed pending IO events. */ -SFUNC int64_t fio_srv_last_tick(void) { return fio___srvdata.tick; } +/** Helper function, calls `fiobj_hash_update_json` with string information */ +FIO_IFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json2)(FIOBJ hash, char *ptr, size_t len); -/** Schedules a task for delayed execution. This function is thread-safe. */ -SFUNC void fio_srv_defer(void (*task)(void *, void *), - void *udata1, - void *udata2) { - fio_queue_push(fio___srv_tasks, task, udata1, udata2); - fio___srv_wakeup(); -} +/** + * Parses a C string for JSON data. If `consumed` is not NULL, the `size_t` + * variable will contain the number of bytes consumed before the parser stopped + * (due to either error or end of a valid JSON data segment). + * + * Returns a FIOBJ object matching the JSON valid C string `str`. + * + * If the parsing failed (no complete valid JSON data) `FIOBJ_INVALID` is + * returned. + */ +SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed); -void fio_srv_run_every___(void); /* IDE marker */ -/** Schedules a timer bound task, see `fio_timer_schedule` in the CSTL. */ -SFUNC void fio_srv_run_every FIO_NOOP(fio_timer_schedule_args_s args) { - args.start_at += ((uint64_t)0 - !args.start_at) & fio___srvdata.tick; - fio_timer_schedule FIO_NOOP(fio___srv_timer, args); -} +/** Helper macro, calls `fiobj_json_parse` with string information */ +#define fiobj_json_parse2(data_, len_, consumed) \ + fiobj_json_parse(FIO_STR_INFO2(data_, len_), consumed) -/** Returns a pointer for the server's queue. */ -SFUNC fio_queue_s *fio_srv_queue(void) { return fio___srv_tasks; } +/** + * Uses JavaScript style notation to find data in an object structure. + * + * For example, "[0].name" will return the "name" property of the first object + * in an array object. + * + * Returns a temporary reference to the object or FIOBJ_INVALID on an error. + * + * Use `fiobj_dup` to collect an actual reference to the returned object. + */ +SFUNC FIOBJ fiobj_json_find(FIOBJ object, fio_str_info_s notation); +/** + * Uses JavaScript style notation to find data in an object structure. + * + * For example, "[0].name" will return the "name" property of the first object + * in an array object. + * + * Returns a temporary reference to the object or FIOBJ_INVALID on an error. + * + * Use `fiobj_dup` to collect an actual reference to the returned object. + */ +#define fiobj_json_find2(object, str, length) \ + fiobj_json_find(object, FIO_STR_INFO2(str, length)) /* ***************************************************************************** -IO Validity Map - Implementation +FIOBJ Mustache support ***************************************************************************** */ -#if FIO_VALIDITY_MAP_USE -#if FIO_VALIDATE_IO_MUTEX -#define FIO_VALIDATE_LOCK() fio_thread_mutex_lock(&fio___srvdata.valid_lock) -#define FIO_VALIDATE_UNLOCK() fio_thread_mutex_unlock(&fio___srvdata.valid_lock) -#define FIO_VALIDATE_LOCK_DESTROY() \ - fio_thread_mutex_destroy(&fio___srvdata.valid_lock) -#else -#define FIO_VALIDATE_LOCK() -#define FIO_VALIDATE_UNLOCK() -#define FIO_VALIDATE_LOCK_DESTROY() -#endif - -FIO_IFUNC int fio_is_valid(fio_s *io) { - FIO_VALIDATE_LOCK(); - fio_s *r = fio_validity_map_get(&fio___srvdata.valid, fio_risky_ptr(io), io); - FIO_VALIDATE_UNLOCK(); - return r == io; -} - -FIO_IFUNC void fio_set_valid(fio_s *io) { - FIO_VALIDATE_LOCK(); - fio_validity_map_set(&fio___srvdata.valid, fio_risky_ptr(io), io, NULL); - FIO_VALIDATE_UNLOCK(); - FIO_ASSERT_DEBUG(fio_is_valid(io), - "(%d) IO validity set, but map reported as invalid!", - (int)fio___srvdata.pid); - FIO_LOG_DEBUG2("(%d) IO %p is now valid", (int)fio___srvdata.pid, (void *)io); -} - -FIO_IFUNC void fio_set_invalid(fio_s *io) { - fio_s *old = NULL; - FIO_LOG_DEBUG2("(%d) IO %p is no longer valid", - (int)fio___srvdata.pid, - (void *)io); - FIO_VALIDATE_LOCK(); - fio_validity_map_remove(&fio___srvdata.valid, fio_risky_ptr(io), io, &old); - FIO_VALIDATE_UNLOCK(); - FIO_ASSERT_DEBUG(!old || old == io, - "(%d) invalidity map corruption (%p != %p)!", - (int)fio___srvdata.pid, - io, - old); - FIO_ASSERT_DEBUG(!fio_is_valid(io), - "(%d) IO validity removed, but map reported as valid!", - (int)fio___srvdata.pid); -} - -FIO_IFUNC void fio_invalidate_all() { - FIO_VALIDATE_LOCK(); - fio_validity_map_destroy(&fio___srvdata.valid); - FIO_VALIDATE_UNLOCK(); - FIO_VALIDATE_LOCK_DESTROY(); -} - -/** Returns an approximate number of IO objects attached. */ -SFUNC size_t fio_io_count(void) { - return fio_validity_map_count(&fio___srvdata.valid); -} - -#undef FIO_VALIDATE_LOCK -#undef FIO_VALIDATE_UNLOCK -#undef FIO_VALIDATE_LOCK_DESTROY -#else /* FIO_VALIDITY_MAP_USE */ -#define fio_is_valid(io) 1 -#define fio_set_valid(io) -#define fio_set_invalid(io) -#define fio_invalidate_all() -#endif /* FIO_VALIDITY_MAP_USE */ -/* ***************************************************************************** -IO objects -***************************************************************************** */ - -struct fio_s { - void *udata; - void *tls; - fio_protocol_s *pr; - FIO_LIST_NODE node; - fio_stream_s stream; - fio___srv_env_safe_s env; -#ifdef DEBUG - size_t total_sent; -#endif - int64_t active; - uint16_t state; - uint16_t pflags; - int fd; - /* TODO? peer address buffer */ -}; +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Returns a FIOBJ String with the rendered template. May return `FIOBJ_INVALID` + * if nothing was written. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build(fio_mustache_s *m, FIOBJ ctx); -#define FIO___IO_STATE_OPEN ((uint16_t)1U) -#define FIO___IO_STATE_SUSPENDED ((uint16_t)2U) -#define FIO___IO_STATE_THROTTLED ((uint16_t)4U) -#define FIO___IO_STATE_CLOSING ((uint16_t)8U) -#define FIO___IO_STATE_CLOSE_LOCAL ((uint16_t)16U) -#define FIO___IO_STATE_CLOSE_REMOTE ((uint16_t)32U) -#define FIO___IO_STATE_CLOSE_ERROR ((uint16_t)64U) +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Writes output to `dest` string (may be `FIOBJ_INVALID` / `NULL`). + * + * Returns `dest` (or a new String). May return `FIOBJ_INVALID` if nothing was + * written and `dest` was empty. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build2(fio_mustache_s *m, FIOBJ dest, FIOBJ ctx); -#define FIO___IO_STATE_POLLIN_SET ((uint16_t)1U) -#define FIO___IO_STATE_POLLOUT_SET ((uint16_t)2U) +/* ***************************************************************************** -FIO_SFUNC void fio_s_init(fio_s *io) { - *io = (fio_s){ - .pr = &FIO___MOCK_PROTOCOL, - .node = FIO_LIST_INIT(io->node), - .stream = FIO_STREAM_INIT(io->stream), - .env = FIO___SRV_ENV_SAFE_INIT, - .active = fio___srvdata.tick, - .state = FIO___IO_STATE_OPEN, - .fd = -1, - }; - FIO_LIST_PUSH(&io->pr->reserved.ios, &io->node); - FIO_LIST_REMOVE(&FIO___MOCK_PROTOCOL.reserved.protocols); - FIO_LIST_PUSH(&fio___srvdata.protocols, - &FIO___MOCK_PROTOCOL.reserved.protocols); - fio_set_valid(io); -} -FIO_IFUNC void fio___s_monitor_in(fio_s *io) { - if (io->state & (FIO___IO_STATE_SUSPENDED | FIO___IO_STATE_THROTTLED | - FIO___IO_STATE_CLOSING)) - return; - if ((fio_atomic_or(&io->pflags, FIO___IO_STATE_POLLIN_SET) & - FIO___IO_STATE_POLLIN_SET)) { - return; - } - fio_poll_monitor(&fio___srvdata.poll_data, io->fd, (void *)io, POLLIN); -} -FIO_IFUNC void fio___s_monitor_out(fio_s *io) { - if ((fio_atomic_or(&io->pflags, FIO___IO_STATE_POLLOUT_SET) & - FIO___IO_STATE_POLLOUT_SET) == FIO___IO_STATE_POLLOUT_SET) - return; - fio_poll_monitor(&fio___srvdata.poll_data, io->fd, (void *)io, POLLOUT); -} -FIO_SFUNC void fio_s_destroy(fio_s *io) { - fio_set_invalid(io); - FIO_LIST_REMOVE(&io->node); - FIO_LOG_DDEBUG2("(%d) detaching and destroying %p (fd %d): %zu bytes total", - fio___srvdata.pid, - (void *)io, - io->fd, - io->total_sent); - /* store info, as it might be freed if the protocol is freed. */ - if (FIO_LIST_IS_EMPTY(&io->pr->reserved.ios)) - FIO_LIST_REMOVE_RESET(&io->pr->reserved.protocols); - /* call on_stop / free callbacks . */ - io->pr->io_functions.cleanup(io->tls); - io->pr->on_close(io->udata); /* may destroy protocol object! */ - fio___srv_env_safe_destroy(&io->env); - fio_sock_close(io->fd); - fio_stream_destroy(&io->stream); - fio_poll_forget(&fio___srvdata.poll_data, io->fd); -} -#define FIO_REF_NAME fio -#define FIO_REF_INIT(o) fio_s_init(&(o)) -#define FIO_REF_DESTROY(o) fio_s_destroy(&(o)) -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE -static void fio___protocol_set_task(void *io_, void *old_) { - fio_s *io = (fio_s *)io_; - fio_protocol_s *old = (fio_protocol_s *)old_; - FIO_LIST_REMOVE(&io->node); - if (FIO_LIST_IS_EMPTY(&old->reserved.ios)) - FIO_LIST_REMOVE_RESET(&old->reserved.protocols); - FIO_LIST_PUSH(&io->pr->reserved.ios, &io->node); - if (io->node.next == io->node.prev) /* list was empty before IO was added */ - FIO_LIST_PUSH(&fio___srvdata.protocols, &io->pr->reserved.protocols); - io->pr->on_attach(io); - io->pflags = (FIO___IO_STATE_POLLIN_SET | FIO___IO_STATE_POLLOUT_SET); - fio_poll_monitor(&fio___srvdata.poll_data, - io->fd, - (void *)io, - POLLIN | POLLOUT); - if (old == &FIO___MOCK_PROTOCOL) /* avoid calling `start` more than once */ - io->pr->io_functions.start(io); -} - -/** Sets a new protocol object, returning the old protocol. */ -SFUNC fio_protocol_s *fio_protocol_set(fio_s *io, fio_protocol_s *pr) { - if (!pr) - pr = &FIO___MOCK_PROTOCOL; - fio___srv_init_protocol_test(pr, !!io->tls); - fio_protocol_s *old = io->pr; - if (pr == old) - return NULL; - io->pr = pr; - // fio_queue_push(fio___srv_tasks, fio___protocol_set_task, io, old); - fio___protocol_set_task((void *)io, (void *)old); - return old; -} -/** Returns a pointer to the current protocol object. */ -SFUNC fio_protocol_s *fio_protocol_get(fio_s *io) { return io->pr; } -/* Attaches the socket in `fd` to the facio.io engine (reactor). */ -SFUNC fio_s *fio_srv_attach_fd(int fd, - fio_protocol_s *protocol, - void *udata, - void *tls) { - fio_s *io = NULL; - fio_protocol_s *old = NULL; - if (!protocol) - protocol = &FIO___MOCK_PROTOCOL; - fio___srv_init_protocol_test(protocol, !!tls); - if (fd == -1) - goto error; - io = fio_new2(); - FIO_ASSERT_ALLOC(io); - FIO_LOG_DDEBUG2("(%d) attaching fd %d to IO object %p", - fio___srvdata.pid, - fd, - (void *)io); - fio_sock_set_non_block(fd); - old = io->pr; - io->fd = fd; - io->pr = protocol; - io->udata = udata; - io->tls = tls; - fio_queue_push(fio___srv_tasks, fio___protocol_set_task, io, old); - return io; -error: - protocol->on_close(udata); - protocol->io_functions.cleanup(tls); - return NULL; -} -/** - * Increases a IO's reference count, so it won't be automatically destroyed - * when all tasks have completed. - */ -SFUNC fio_s *fio_dup(fio_s *io) { return fio_dup2(io); } +FIOBJ - Implementation - Inline / Macro like fucntions -static void fio_undup_task(void *io, void *ignr_) { - (void)ignr_; - fio_free2((fio_s *)io); -} -/** - * Decreases a IO's reference count, so it could be automatically destroyed - * when all other tasks have completed. - */ -SFUNC void fio_undup(fio_s *io) { - fio_queue_push(fio___srv_tasks, fio_undup_task, io); -} -/** Performs a task for each IO in the stated protocol. */ -FIO_SFUNC size_t fio_protocol_each(fio_protocol_s *protocol, - void (*task)(fio_s *, void *), - void *udata) { - size_t count = 0; - if (!protocol || !protocol->reserved.ios.next || !protocol->reserved.ios.prev) - return count; - FIO_LIST_EACH(fio_s, node, &protocol->reserved.ios, io) { - if (!(io->state & FIO___IO_STATE_OPEN)) - continue; - task(io, udata); - ++count; - } - return count; -} -/* ***************************************************************************** -Connection Object Links / Environment -***************************************************************************** */ -void fio_env_get___(void); /* IDE marker */ -/** Returns the named `udata` associated with the IO object (or `NULL). */ -SFUNC void *fio_env_get FIO_NOOP(fio_s *io, fio_env_get_args_s args) { - return fio___srv_env_safe_get((io ? &io->env : &fio___srvdata.env), - args.name.buf, - args.name.len, - args.type); -} -void fio_env_set___(void); /* IDE marker */ -/** - * Links an object to a connection's lifetime / environment. - */ -SFUNC void fio_env_set FIO_NOOP(fio_s *io, fio_env_set_args_s args) { - fio___srv_env_obj_s val = { - .on_close = args.on_close, - .udata = args.udata, - }; - fio___srv_env_safe_set((io ? &io->env : &fio___srvdata.env), - args.name.buf, - args.name.len, - args.type, - val, - args.const_name); -} -void fio_env_unset___(void); /* IDE marker */ -/** - * Un-links an object from the connection's lifetime, so it's `on_close` - * callback will NOT be called. - */ -SFUNC int fio_env_unset FIO_NOOP(fio_s *io, fio_env_get_args_s args) { - return fio___srv_env_safe_unset((io ? &io->env : &fio___srvdata.env), - args.name.buf, - args.name.len, - args.type); -} -/** - * Removes an object from the connection's lifetime / environment, calling it's - * `on_close` callback as if the connection was closed. - */ -SFUNC int fio_env_remove FIO_NOOP(fio_s *io, fio_env_get_args_s args) { - return fio___srv_env_safe_remove((io ? &io->env : &fio___srvdata.env), - args.name.buf, - args.name.len, - args.type); -} +***************************************************************************** */ /* ***************************************************************************** -Writing from the stream +The FIOBJ Type ***************************************************************************** */ -static void fio___srv_try_to_write_to_io(fio_s *io) { - char buf_mem[FIO_SRV_BUFFER_PER_WRITE]; - size_t total = 0; - if (!(io->state & FIO___IO_STATE_OPEN)) - return; - for (;;) { - size_t len = FIO_SRV_BUFFER_PER_WRITE; - char *buf = buf_mem; - fio_stream_read(&io->stream, &buf, &len); - if (!len) - break; - ssize_t r = io->pr->io_functions.write(io->fd, buf, len, io->tls); - if (r > 0) { - FIO_LOG_DDEBUG2("(%d) written %zu bytes to fd %d", - fio___srvdata.pid, - (size_t)r, - io->fd); - total += r; - fio_stream_advance(&io->stream, r); - continue; - } - if (r == -1) { - if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) - break; - if (errno == EINTR) - continue; - } - goto connection_error; - } - if (total) { - fio_touch(io); -#ifdef DEBUG - io->total_sent += total; -#endif +/** Returns an objects type. This isn't limited to known types. */ +FIO_IFUNC size_t fiobj_type(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_NULL: return FIOBJ_T_NULL; + case FIOBJ_T_TRUE: return FIOBJ_T_TRUE; + case FIOBJ_T_FALSE: return FIOBJ_T_FALSE; + }; + return FIOBJ_T_INVALID; + case FIOBJ_T_NUMBER: return FIOBJ_T_NUMBER; + case FIOBJ_T_FLOAT: return FIOBJ_T_FLOAT; + case FIOBJ_T_STRING: return FIOBJ_T_STRING; + case FIOBJ_T_ARRAY: return FIOBJ_T_ARRAY; + case FIOBJ_T_HASH: return FIOBJ_T_HASH; + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->type_id; } - return; - -connection_error: -#if DEBUG - if (fio_stream_any(&io->stream)) - FIO_LOG_DERROR( - "(%d) IO write failed (%d), disconnecting: %p (fd %d)\n\tError: %s", - fio___srvdata.pid, - errno, - (void *)io, - io->fd, - strerror(errno)); -#endif - fio_close_now(io); + if (!o) + return FIOBJ_T_NULL; + return FIOBJ_T_INVALID; } + /* ***************************************************************************** -Event handling +FIOBJ Memory Management ***************************************************************************** */ -static void fio___srv_poll_on_data(void *io_, void *ignr_) { - (void)ignr_; - fio_s *io = (fio_s *)io_; - fio_atomic_and(&io->pflags, ~FIO___IO_STATE_POLLIN_SET); - if (io->state == FIO___IO_STATE_OPEN) { - /* this also tests for the suspended / throttled / closing flags */ - io->pr->on_data(io); - fio___s_monitor_in(io); - } else if ((io->state & FIO___IO_STATE_OPEN)) { - fio___s_monitor_out(io); - } - fio_free2(io); - return; -} - -static void fio___srv_poll_on_ready(void *io_, void *ignr_) { - (void)ignr_; -#if DEBUG - errno = 0; -#endif - fio_s *io = (fio_s *)io_; - fio_atomic_and(&io->pflags, ~FIO___IO_STATE_POLLOUT_SET); - fio___srv_try_to_write_to_io(io); - if (!fio_stream_any(&io->stream) && - !io->pr->io_functions.flush(io->fd, io->tls)) { - if ((io->state & FIO___IO_STATE_CLOSING)) { - io->pr->io_functions.finish(io->fd, io->tls); - fio_close_now(io); - } else { - if ((io->state & FIO___IO_STATE_THROTTLED)) { - fio_atomic_and(&io->state, ~FIO___IO_STATE_THROTTLED); - fio___s_monitor_in(io); - } - FIO_LOG_DDEBUG2("(%d) calling on_ready for %p (fd %d) - %zu data left.", - fio___srvdata.pid, - (void *)io, - io->fd, - fio_stream_length(&io->stream)); - io->pr->on_ready(io); - } - } else { - if (fio_stream_length(&io->stream) >= FIO_SRV_THROTTLE_LIMIT) { - if (!(io->state & FIO___IO_STATE_THROTTLED)) - FIO_LOG_DDEBUG2("(%d), throttled IO %p (fd %d)", - fio___srvdata.pid, - (void *)io, - io->fd); - fio_atomic_or(&io->state, FIO___IO_STATE_THROTTLED); - } - fio___s_monitor_out(io); +/** Increases an object's reference count (or copies) and returns it. */ +FIO_IFUNC FIOBJ fiobj_dup(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: /* fall through */ return o; + case FIOBJ_T_STRING: /* fall through */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), dup)(o); + break; + case FIOBJ_T_ARRAY: /* fall through */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), dup)(o); + break; + case FIOBJ_T_HASH: /* fall through */ + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), dup)(o); + break; + case FIOBJ_T_OTHER: /* fall through */ fiobj_object_dup(o); } - fio_free2(io); -} - -static void fio___srv_poll_on_close(void *io_, void *ignr_) { - (void)ignr_; - fio_s *io = (fio_s *)io_; - fio_atomic_or(&io->state, FIO___IO_STATE_CLOSE_REMOTE); - FIO_LOG_DEBUG2("(%d) fd %d closed by remote peer", fio___srvdata.pid, io->fd); - fio_close_now(io); - fio_free2(io); -} - -static void fio___srv_poll_on_timeout(void *io_, void *ignr_) { - (void)ignr_; - fio_s *io = (fio_s *)io_; - io->pr->on_timeout(io); - fio_free2(io); + return o; } -/* ***************************************************************************** -Event scheduling -***************************************************************************** */ - -static void fio___srv_poll_on_data_schd(void *io) { - if (!fio_is_valid(io)) +/** Decreases an object's reference count or frees it. */ +FIO_IFUNC void fiobj_free(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: return; + case FIOBJ_T_STRING: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), free)(o); return; - fio_queue_push(fio___srv_tasks, - fio___srv_poll_on_data, - fio_dup2((fio_s *)io)); -} -static void fio___srv_poll_on_ready_schd(void *io) { - if (!fio_is_valid(io)) + case FIOBJ_T_ARRAY: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), free)(o); return; - fio_queue_push(fio___srv_tasks, - fio___srv_poll_on_ready, - fio_dup2((fio_s *)io)); -} -static void fio___srv_poll_on_close_schd(void *io) { - if (!fio_is_valid(io)) + case FIOBJ_T_HASH: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), free)(o); return; - fio_queue_push(fio___srv_tasks, - fio___srv_poll_on_close, - fio_dup2((fio_s *)io)); -} - -/* ***************************************************************************** -Timeout Review -***************************************************************************** */ - -/** Schedules the timeout event for any timed out IO object */ -static int fio___srv_review_timeouts(void) { - int c = 0; - static time_t last_to_review = 0; - /* test timeouts at whole second intervals */ - if (last_to_review + 1000 > fio___srvdata.tick) - return c; - last_to_review = fio___srvdata.tick; - const int64_t now_milli = fio___srvdata.tick; - - FIO_LIST_EACH(fio_protocol_s, - reserved.protocols, - &fio___srvdata.protocols, - pr) { - FIO_ASSERT_DEBUG(pr->reserved.flags, "protocol object flags unmarked?!"); - if (!pr->timeout || pr->timeout > FIO_SRV_TIMEOUT_MAX) - pr->timeout = FIO_SRV_TIMEOUT_MAX; - int64_t limit = now_milli - ((int64_t)pr->timeout); - FIO_LIST_EACH(fio_s, node, &pr->reserved.ios, io) { - FIO_ASSERT_DEBUG(io->pr == pr, "IO protocol ownership error"); - if (io->active >= limit) - break; - FIO_LOG_DDEBUG2("(%d) scheduling timeout for %p (fd %d)", - fio___srvdata.pid, - (void *)io, - io->fd); - fio_queue_push(fio___srv_tasks, fio___srv_poll_on_timeout, fio_dup2(io)); - ++c; - } + case FIOBJ_T_OTHER: (*fiobj_object_metadata(o))->free2(o); return; } - return c; } /* ***************************************************************************** -Reactor cycling +FIOBJ Data / Info ***************************************************************************** */ -static void fio___srv_signal_handle(int sig, void *flg) { - ((uint8_t *)flg)[0] = 1; - (void)sig; -} -FIO_SFUNC void fio___srv_tick(int timeout) { - static size_t performed_idle = 0; - if (fio_poll_review(&fio___srvdata.poll_data, timeout) > 0) { - performed_idle = 0; - } else if (timeout) { - if (!performed_idle && !fio___srvdata.stop) - fio_state_callback_force(FIO_CALL_ON_IDLE); - performed_idle = 1; - } - fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - fio_timer_push2queue(fio___srv_tasks, fio___srv_timer, fio___srvdata.tick); - for (size_t i = 0; i < 2048; ++i) - if (fio_queue_perform(fio___srv_tasks)) - break; - // fio_queue_perform_all(fio___srv_tasks); - fio___srv_review_timeouts(); - // fio_queue_perform_all(fio___srv_tasks); - fio_signal_review(); -} +/** Internal: compares two nestable objects. */ +SFUNC unsigned char fiobj___test_eq_nested(FIOBJ restrict a, + FIOBJ restrict b, + size_t nesting); -FIO_SFUNC void fio___srv_run_async_as_sync(void *ignr_1, void *ignr_2) { - (void)ignr_1, (void)ignr_2; - unsigned repeat = 0; - FIO_LIST_EACH(fio_srv_async_s, node, &fio___srvdata.async, pos) { - fio_queue_task_s t = fio_queue_pop(&pos->queue); - if (!t.fn) - continue; - t.fn(t.udata1, t.udata2); - repeat = 1; +/** Compares two objects. */ +FIO_IFUNC unsigned char FIO_NAME_BL(fiobj, eq)(FIOBJ a, FIOBJ b) { + if (a == b) + return 1; + if (FIOBJ_TYPE_CLASS(a) != FIOBJ_TYPE_CLASS(b)) + return 0; + switch (FIOBJ_TYPE_CLASS(a)) { + case FIOBJ_T_PRIMITIVE: + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: /* fall through */ return a == b; + case FIOBJ_T_STRING: + return FIO_NAME_BL(FIO_NAME(fiobj, FIOBJ___NAME_STRING), eq)(a, b); + case FIOBJ_T_ARRAY: return fiobj___test_eq_nested(a, b, 0); + case FIOBJ_T_HASH: return fiobj___test_eq_nested(a, b, 0); + case FIOBJ_T_OTHER: + if ((*fiobj_object_metadata(a))->count(a) || + (*fiobj_object_metadata(b))->count(b)) { + if ((*fiobj_object_metadata(a))->count(a) != + (*fiobj_object_metadata(b))->count(b)) + return 0; + return fiobj___test_eq_nested(a, b, 0); + } + return (*fiobj_object_metadata(a))->type_id == + (*fiobj_object_metadata(b))->type_id && + (*fiobj_object_metadata(a))->is_eq(a, b); } - if (repeat) - fio_queue_push(fio___srv_tasks, fio___srv_run_async_as_sync); -} - -FIO_SFUNC void fio___srv_shutdown_task(void *shutdown_start_, void *a2) { - intptr_t shutdown_start = (intptr_t)shutdown_start_; - if (shutdown_start + FIO_SRV_SHUTDOWN_TIMEOUT < fio___srvdata.tick || - FIO_LIST_IS_EMPTY(&fio___srvdata.protocols)) - return; - fio___srv_tick(fio_queue_count(fio___srv_tasks) ? 0 : 100); - fio_queue_push(fio___srv_tasks, fio___srv_run_async_as_sync); - fio_queue_push(fio___srv_tasks, fio___srv_shutdown_task, shutdown_start_, a2); + return 0; } -FIO_SFUNC void fio___srv_shutdown(void) { - /* collect tick for shutdown start, to monitor for possible timeout */ - int64_t shutdown_start = fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - size_t connected = 0; - /* first notify that shutdown is starting */ - fio_state_callback_force(FIO_CALL_ON_SHUTDOWN); - /* preform on_shutdown callback for each connection and close */ - FIO_LIST_EACH(fio_protocol_s, - reserved.protocols, - &fio___srvdata.protocols, - pr) { - FIO_LIST_EACH(fio_s, node, &pr->reserved.ios, io) { - pr->on_shutdown(io); /* TODO / FIX: move callback to task? */ - fio_close(io); /* TODO / FIX: skip close on return value? */ - ++connected; - } +/** Returns a temporary String representation for any FIOBJ object. */ +FIO_IFUNC fio_str_info_s FIO_NAME2(fiobj, cstr)(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_NULL: return FIO_STR_INFO2((char *)"null", 4); + case FIOBJ_T_TRUE: return FIO_STR_INFO2((char *)"true", 4); + case FIOBJ_T_FALSE: return FIO_STR_INFO2((char *)"false", 5); + }; + return (fio_str_info_s){.buf = (char *)""}; + case FIOBJ_T_NUMBER: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), cstr)(o); + case FIOBJ_T_FLOAT: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), cstr)(o); + case FIOBJ_T_STRING: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); + case FIOBJ_T_ARRAY: /* fall through */ + return FIO_STR_INFO2((char *)"[...]", 5); + case FIOBJ_T_HASH: { + return FIO_STR_INFO2((char *)"{...}", 5); } - FIO_LOG_DEBUG2("(%d) Server shutting down with %zu connected clients", - fio___srvdata.pid, - connected); - /* cycle while connections exist. */ - fio_queue_push(fio___srv_tasks, - fio___srv_shutdown_task, - (void *)(intptr_t)shutdown_start, - NULL); - fio_queue_perform_all(fio___srv_tasks); - /* in case of timeout, force close remaining connections. */ - connected = 0; - FIO_LIST_EACH(fio_protocol_s, - reserved.protocols, - &fio___srvdata.protocols, - pr) { - FIO_LIST_EACH(fio_s, node, &pr->reserved.ios, io) { - fio_close_now(io); - ++connected; - } + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_s(o); } - FIO_LOG_DEBUG2("(%d) Server shutdown timeout/done with %zu clients", - fio___srvdata.pid, - connected); - /* perform remaining tasks. */ - fio_queue_perform_all(fio___srv_tasks); -} - -FIO_SFUNC void fio___srv_work_task(void *ignr_1, void *ignr_2) { - if (fio___srvdata.stop) - return; - fio___srv_tick(fio_queue_count(fio___srv_tasks) ? 0 : 500); - fio_queue_push(fio___srv_tasks, fio___srv_work_task, ignr_1, ignr_2); + /* a non-explicit NULL is an empty string. */ + return (fio_str_info_s){.buf = (char *)""}; } -FIO_SFUNC void fio___srv_async_start(fio_srv_async_s *q); -FIO_SFUNC void fio___srv_async_stop(fio_srv_async_s *q); -FIO_SFUNC void fio___srv_work(int is_worker) { - fio___srvdata.is_worker = is_worker; - FIO_LIST_EACH(fio_srv_async_s, node, &fio___srvdata.async, q) { - fio___srv_async_start(q); +/** Returns an integer representation for any FIOBJ object. */ +FIO_IFUNC intptr_t FIO_NAME2(fiobj, i)(FIOBJ o) { + fio_str_info_s tmp; + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_NULL: return 0; + case FIOBJ_T_TRUE: return 1; + case FIOBJ_T_FALSE: return 0; + }; + return -1; + case FIOBJ_T_NUMBER: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(o); + case FIOBJ_T_FLOAT: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(o); + case FIOBJ_T_STRING: + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); + if (!tmp.len) + return 0; + return fio_atol(&tmp.buf); + case FIOBJ_T_ARRAY: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + case FIOBJ_T_HASH: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_i(o); } + if (!o) + return 0; + return -1; +} - fio_queue_perform_all(fio___srv_tasks); - if (is_worker) { - fio_state_callback_force(FIO_CALL_ON_START); - } - fio___srv_wakeup_init(); - fio_queue_push(fio___srv_tasks, fio___srv_work_task); - fio_queue_perform_all(fio___srv_tasks); - FIO_LIST_EACH(fio_srv_async_s, node, &fio___srvdata.async, q) { - fio___srv_async_stop(q); - } - fio___srv_shutdown(); - FIO_LIST_EACH(fio_srv_async_s, node, &fio___srvdata.async, q) { - fio___srv_async_stop(q); +/** Returns a float (double) representation for any FIOBJ object. */ +FIO_IFUNC double FIO_NAME2(fiobj, f)(FIOBJ o) { + fio_str_info_s tmp; + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + switch ((uintptr_t)(o)) { + case FIOBJ_T_FALSE: /* fall through */ + case FIOBJ_T_NULL: return 0.0; + case FIOBJ_T_TRUE: return 1.0; + }; + return -1.0; + case FIOBJ_T_NUMBER: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(o); + case FIOBJ_T_FLOAT: + return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(o); + case FIOBJ_T_STRING: + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); + if (!tmp.len) + return 0; + return (double)fio_atof(&tmp.buf); + case FIOBJ_T_ARRAY: + return (double)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + case FIOBJ_T_HASH: + return (double)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_f(o); } - fio_queue_perform_all(fio___srv_tasks); - fio_queue_perform_all(fio___srv_tasks); - fio_state_callback_force(FIO_CALL_ON_STOP); - fio_queue_perform_all(fio___srv_tasks); - fio___srvdata.workers = 0; + if (!o) + return 0.0; + return -1.0; } /* ***************************************************************************** -Worker Forking +FIOBJ Integers ***************************************************************************** */ -static void fio___srv_spawn_worker(void *ignr_1, void *ignr_2); -static void fio___srv_wait_for_worker(void *thr_) { - fio_thread_t t = (fio_thread_t)thr_; - fio_thread_join(&t); +#define FIO_REF_NAME fiobj___bignum +#define FIO_REF_TYPE intptr_t +#define FIO_REF_METADATA const FIOBJ_class_vtable_s * +#define FIO_REF_METADATA_INIT(m) \ + do { \ + m = &FIOBJ___NUMBER_CLASS_VTBL; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#define FIO_REF_METADATA_DESTROY(m) \ + do { \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE + +/* Places a 61 or 29 bit signed integer in the leftmost bits of a word. */ +#define FIO_NUMBER_ENCODE(i) (((uintptr_t)(i) << 3) | FIOBJ_T_NUMBER) +/* Reads a 61 or 29 bit signed integer from the leftmost bits of a word. */ +#define FIO_NUMBER_DECODE(i) \ + ((intptr_t)(((uintptr_t)(i) >> 3) | \ + ((uintptr_t)0 - \ + (((uintptr_t)(i) >> 3) & \ + ((uintptr_t)1 << ((sizeof(uintptr_t) * 8) - 4)))))) + +/** Creates a new Number object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), + new)(intptr_t i) { + FIOBJ o = (FIOBJ)FIO_NUMBER_ENCODE(i); + if (FIO_NUMBER_DECODE(o) == i) + return o; + o = fiobj___bignum_new2(); + + FIO_PTR_MATH_RMASK(intptr_t, o, 3)[0] = i; + return o; } -/** Worker sentinel */ -static void *fio___srv_worker_sentinel(void *pid_data) { -#ifdef WEXITSTATUS - fio_thread_pid_t pid = (fio_thread_pid_t)(uintptr_t)pid_data; - int status = 0; - (void)status; - fio_thread_t thr = fio_thread_current(); - fio_state_callback_add(FIO_CALL_ON_STOP, - fio___srv_wait_for_worker, - (void *)thr); - if (fio_thread_waitpid(pid, &status, 0) != pid && !fio___srvdata.stop) - FIO_LOG_ERROR("waitpid failed, worker re-spawning might fail."); - if (!WIFEXITED(status) || WEXITSTATUS(status)) { - FIO_LOG_WARNING("abnormal worker exit detected"); - fio_state_callback_force(FIO_CALL_ON_CHILD_CRUSH); - } - if (!fio___srvdata.stop) { - FIO_ASSERT_DEBUG( - 0, - "DEBUG mode prevents worker re-spawning, now crashing parent."); - fio_state_callback_remove(FIO_CALL_ON_STOP, - fio___srv_wait_for_worker, - (void *)thr); - fio_thread_detach(&thr); - fio_queue_push(fio___srv_tasks, fio___srv_spawn_worker, (void *)thr); - } -#else /* Non POSIX? no `fork`? no fio_thread_waitpid? */ - FIO_ASSERT( - 0, - "facil.io doesn't know how to spawn and wait on workers on this system."); -#endif - return NULL; +/** Reads the number from a FIOBJ number. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(FIOBJ i) { + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_NUMBER) + return FIO_NUMBER_DECODE(i); + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + return FIO_PTR_MATH_RMASK(intptr_t, i, 3)[0]; + return 0; } -static void fio___srv_spawn_worker(void *ignr_1, void *ignr_2) { - (void)ignr_1, (void)ignr_2; - fio_thread_t t; - fio_signal_review(); +/** Reads the number from a FIOBJ number, fitting it in a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(FIOBJ i) { + return (double)FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(i); +} - if (fio___srvdata.stop || fio___srvdata.root_pid != fio___srvdata.pid) - return; - if (fio_atomic_or_fetch(&fio___srvdata.stop, 2) != 2) - return; - FIO_LIST_EACH(fio_srv_async_s, node, &fio___srvdata.async, q) { - fio___srv_async_stop(q); - } - fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - fio_state_callback_force(FIO_CALL_BEFORE_FORK); - /* do not allow master tasks to run in worker */ - fio_queue_perform_all(fio___srv_tasks); - /* perform actual fork */ - fio_thread_pid_t pid = fio_thread_fork(); - FIO_ASSERT(pid != (fio_thread_pid_t)-1, "system call `fork` failed."); - if (!pid) - goto is_worker_process; - fio_state_callback_force(FIO_CALL_AFTER_FORK); - fio_state_callback_force(FIO_CALL_IN_MASTER); - if (fio_thread_create(&t, - fio___srv_worker_sentinel, - (void *)(uintptr_t)pid)) { - FIO_LOG_FATAL( - "sentinel thread creation failed, no worker will be spawned."); - fio_srv_stop(); - } - if (!fio_atomic_xor_fetch(&fio___srvdata.stop, 2)) - fio_queue_push(fio___srv_tasks, fio___srv_work_task); - return; +/** Frees a FIOBJ number. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), free)(FIOBJ i) { + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + fiobj___bignum_free2(i); +} -is_worker_process: - fio___srvdata.pid = fio_thread_getpid(); - fio___srvdata.is_worker = 1; - FIO_LOG_INFO("(%d) worker starting up.", (int)fio___srvdata.pid); - fio_state_callback_force(FIO_CALL_AFTER_FORK); - fio_state_callback_force(FIO_CALL_IN_CHILD); - if (!fio_atomic_xor_fetch(&fio___srvdata.stop, 2)) - fio___srv_work(1); - FIO_LOG_INFO("(%d) worker exiting.", (int)fio___srvdata.pid); - exit(0); +FIO_IFUNC unsigned char FIO_NAME_BL(fiobj___num, eq)(FIOBJ restrict a, + FIOBJ restrict b) { + /* it should be safe to assume that FIOBJ_TYPE_CLASS(i) != FIOBJ_T_NUMBER */ + return FIO_PTR_MATH_RMASK(intptr_t, a, 3)[0] == + FIO_PTR_MATH_RMASK(intptr_t, b, 3)[0]; + // return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(a) == + // FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(b); } +#undef FIO_NUMBER_ENCODE +#undef FIO_NUMBER_DECODE + /* ***************************************************************************** -Starting the Server +FIOBJ Floats ***************************************************************************** */ -/* Stopping the server. */ -SFUNC void fio_srv_stop(void) { fio_atomic_or(&fio___srvdata.stop, 1); } - -/* Returns true if server running and 0 if server stopped or shutting down. */ -SFUNC int fio_srv_is_running(void) { return !fio___srvdata.stop; } - -/* Returns true if the current process is the server's master process. */ -SFUNC int fio_srv_is_master(void) { - return fio___srvdata.root_pid == fio___srvdata.pid; -} - -/* Returns true if the current process is a server's worker process. */ -SFUNC int fio_srv_is_worker(void) { return fio___srvdata.is_worker; } +#define FIO_REF_NAME fiobj___bigfloat +#define FIO_REF_TYPE double +#define FIO_REF_METADATA const FIOBJ_class_vtable_s * +#define FIO_REF_METADATA_INIT(m) \ + do { \ + m = &FIOBJ___FLOAT_CLASS_VTBL; \ + FIOBJ_MARK_MEMORY_ALLOC(); \ + } while (0) +#define FIO_REF_METADATA_DESTROY(m) \ + do { \ + FIOBJ_MARK_MEMORY_FREE(); \ + } while (0) +#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) +#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) +#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) +#define FIO_PTR_TAG_TYPE FIOBJ +#include FIO_INCLUDE_FILE -/* Returns the number or workers the server will actually run. */ -SFUNC uint16_t fio_srv_workers(int workers) { - if (workers < 0) { - long cores = -1; -#ifdef _SC_NPROCESSORS_ONLN - cores = sysconf(_SC_NPROCESSORS_ONLN); -#endif /* _SC_NPROCESSORS_ONLN */ - if (cores == -1L) { - cores = 8; - FIO_LOG_WARNING("fio_srv_start called with negative value for worker " - "count, but auto-detect failed, assuming %d CPU cores", - cores); +/** Creates a new Float object. */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(double i) { + FIOBJ ui; + if (sizeof(double) <= sizeof(FIOBJ)) { + union { + double d; + uintptr_t i; + } punned; + punned.i = 0; /* dead code, but leave it, just in case */ + punned.d = i; + if ((punned.i & 7) == 0) { + return (FIOBJ)(punned.i | FIOBJ_T_FLOAT); } - workers = (int)(cores / (0 - workers)); - workers += !workers; } - return (uint16_t)workers; -} + ui = fiobj___bigfloat_new2(); -/** Adds `workers` amount of workers to the root server process. */ -SFUNC void fio_srv_add_workers(int workers) { - if (!workers || fio___srvdata.root_pid != fio___srvdata.pid) - return; - FIO_LOG_INFO("(%d) spawning %d workers.", fio___srvdata.root_pid, workers); - for (int i = 0; i < workers; ++i) - fio_queue_push(fio___srv_tasks, fio___srv_spawn_worker); + FIO_PTR_MATH_RMASK(double, ui, 3)[0] = i; + return ui; } -/* Starts the server, using optional `workers` processes. This will BLOCK! */ -SFUNC void fio_srv_start(int workers) { - fio___srvdata.stop = 0; - fio___srvdata.workers = fio_srv_workers(workers); - workers = (int)fio___srvdata.workers; - fio___srvdata.is_worker = !workers; - fio_sock_maximize_limits(0); +/** Reads the integer part from a FIOBJ Float. */ +FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(FIOBJ i) { + return (intptr_t)floor(FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(i)); +} - FIO_LIST_EACH(fio_srv_async_s, node, &fio___srvdata.async, q) { - fio___srv_async_start(q); +/** Reads the number from a FIOBJ number, fitting it in a double. */ +FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(FIOBJ i) { + if (sizeof(double) <= sizeof(FIOBJ) && FIOBJ_TYPE_CLASS(i) == FIOBJ_T_FLOAT) { + union { + double d; + uint64_t i; + } punned; + punned.d = 0; /* dead code, but leave it, just in case */ + punned.i = (uint64_t)(uintptr_t)i; + punned.i = ((uint64_t)(uintptr_t)i & (~(uintptr_t)7ULL)); + return punned.d; } + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + return FIO_PTR_MATH_RMASK(double, i, 3)[0]; + return 0.0; +} - fio_state_callback_force(FIO_CALL_PRE_START); - fio_queue_perform_all(fio___srv_tasks); - fio_signal_monitor(SIGINT, - fio___srv_signal_handle, - (void *)&fio___srvdata.stop); - fio_signal_monitor(SIGTERM, - fio___srv_signal_handle, - (void *)&fio___srvdata.stop); -#ifdef SIGPIPE - fio_signal_monitor(SIGPIPE, NULL, NULL); -#endif - fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - if (workers) { - FIO_LOG_INFO("(%d) spawning %d workers.", fio___srvdata.root_pid, workers); - for (int i = 0; i < workers; ++i) { - fio___srv_spawn_worker(NULL, NULL); - } - } else { - FIO_LOG_DEBUG2("(%d) starting facil.io IO reactor in single process mode.", - fio___srvdata.root_pid); - } - fio___srv_work(!workers); - fio_signal_forget(SIGINT); - fio_signal_forget(SIGTERM); -#ifdef SIGPIPE - fio_signal_forget(SIGPIPE); -#endif - fio_queue_perform_all(fio___srv_tasks); +/** Frees a FIOBJ number. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), free)(FIOBJ i) { + if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) + fiobj___bigfloat_free2(i); + return; } /* ***************************************************************************** -IO API +FIOBJ Basic Iteration ***************************************************************************** */ -/** Returns the socket file descriptor (fd) associated with the IO. */ -SFUNC int fio_fd_get(fio_s *io) { return io ? io->fd : -1; } - -FIO_SFUNC void fio_touch___task(void *io_, void *ignr_) { - (void)ignr_; - fio_s *io = (fio_s *)io_; - io->active = fio___srvdata.tick; - FIO_LIST_REMOVE(&io->node); - FIO_LIST_PUSH(&io->pr->reserved.ios, &io->node); - fio_free2(io); -} - -/* Resets a socket's timeout counter. */ -SFUNC void fio_touch(fio_s *io) { - fio_queue_push_urgent(fio___srv_tasks, fio_touch___task, fio_dup(io)); -} - /** - * Reads data to the buffer, if any data exists. Returns the number of bytes - * read. + * Performs a task for each element held by the FIOBJ object. * - * NOTE: zero (`0`) is a valid return value meaning no data was available. + * If `task` returns -1, the `each` loop will break (stop). + * + * Returns the "stop" position - the number of elements processed + `start_at`. */ -SFUNC size_t fio_read(fio_s *io, void *buf, size_t len) { - if (!io) - return 0; - ssize_t r = io->pr->io_functions.read(io->fd, buf, len, io->tls); - if (r > 0) { - fio_touch(io); - return r; +FIO_SFUNC uint32_t fiobj_each1(FIOBJ o, + int (*task)(fiobj_each_s *e), + void *udata, + int32_t start_at) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_STRING: /* fall through */ + case FIOBJ_T_FLOAT: return 0; + case FIOBJ_T_ARRAY: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), each)( + o, + (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), each_s *)))task, + udata, + start_at); + case FIOBJ_T_HASH: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each)( + o, + (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each_s *)))task, + udata, + start_at); + case FIOBJ_T_OTHER: + return (*fiobj_object_metadata(o))->each1(o, task, udata, start_at); } - if ((unsigned)(!len) | - ((unsigned)(r == -1) & ((unsigned)(errno == EAGAIN) | - (errno == EWOULDBLOCK) | (errno == EINTR)))) - return 0; - fio_close(io); return 0; } -FIO_SFUNC void fio_write2___dealloc_task(void *fn, void *data) { - union { - void *ptr; - void (*fn)(void *); - } u = {.ptr = fn}; - u.fn(data); -} - -FIO_SFUNC void fio_write2___task(void *io_, void *packet_) { - fio_s *io = (fio_s *)io_; - fio_stream_packet_s *packet = (fio_stream_packet_s *)packet_; - if (!(io->state & FIO___IO_STATE_OPEN)) - goto io_error; - fio_stream_add(&io->stream, packet); - fio___s_monitor_out(io); - fio_free2(io); /* undup the IO object since it isn't moved to on_ready */ - return; -io_error: - fio_stream_pack_free(packet); - fio_free2(io); /* undup the IO object since it isn't moved to on_ready */ -} +/* ***************************************************************************** +FIOBJ Hash Maps +***************************************************************************** */ -void fio_write2___(void); /* IDE marker*/ -/** - * Writes data to the outgoing buffer and schedules the buffer to be sent. - */ -SFUNC void fio_write2 FIO_NOOP(fio_s *io, fio_write_args_s args) { - fio_stream_packet_s *packet = NULL; - if (!io) - goto io_error_null; - if (args.buf) { - packet = fio_stream_pack_data(args.buf, - args.len, - args.offset, - args.copy, - args.dealloc); - } else if ((unsigned)(args.fd + 1) > 1) { - packet = fio_stream_pack_fd((int)args.fd, args.len, args.offset, args.copy); +/** Calculates an object's hash value for a specific hash map object. */ +FIO_IFUNC uint64_t FIO_NAME2(fiobj, hash)(FIOBJ o) { + uint64_t seed = (uint64_t)(uintptr_t)&FIO_NAME2(fiobj, hash); + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: + return fio_risky_hash(&o, sizeof(o), seed + (uintptr_t)o); + case FIOBJ_T_NUMBER: { + uintptr_t tmp = FIO_NAME2(fiobj, i)(o); + return fio_risky_hash(&tmp, sizeof(tmp), seed); } - if (!packet) - goto error; - if ((io->state & FIO___IO_STATE_CLOSING)) - goto write_called_after_close; - fio_srv_defer(fio_write2___task, fio_dup2(io), packet); - return; -error: /* note: `dealloc` is called by the `fio_stream` API error handler. */ - FIO_LOG_ERROR("couldn't create %zu bytes long user-packet for IO %p (%d)", - args.len, - (void *)io, - (io ? io->fd : -1)); - return; -write_called_after_close: - FIO_LOG_DEBUG2("`write` called after `close` was called for IO."); - { - union { - void *ptr; - void (*fn)(fio_stream_packet_s *); - } u = {.fn = fio_stream_pack_free}; - // u.fn(packet); - fio_queue_push(fio___srv_tasks, fio_write2___dealloc_task, u.ptr, packet); + case FIOBJ_T_FLOAT: { + double tmp = FIO_NAME2(fiobj, f)(o); + return fio_risky_hash(&tmp, sizeof(tmp), seed); } - return; -io_error_null: - FIO_LOG_ERROR("(%d) `fio_write2` called for invalid IO (NULL)", - fio___srvdata.pid); - if (args.dealloc) { - union { - void *ptr; - void (*fn)(void *); - } u = {.fn = args.dealloc}; - // u.fn(args.buf); - fio_queue_push(fio___srv_tasks, fio_write2___dealloc_task, u.ptr, args.buf); - if ((unsigned)(args.fd + 1) > 1) - close((int)args.fd); + case FIOBJ_T_STRING: /* fall through */ + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), hash)(o, seed); + case FIOBJ_T_ARRAY: { + uint64_t h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + h += fio_risky_hash(&h, sizeof(h), seed + FIOBJ_T_ARRAY); + { + FIOBJ *a = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), ptr)(o); + const size_t count = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + if (a) { + for (size_t i = 0; i < count; ++i) { + h += FIO_NAME2(fiobj, hash)(a[i]); + } + } + } + return h; + } + case FIOBJ_T_HASH: { + uint64_t h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + h += fio_risky_hash(&h, sizeof(h), seed + FIOBJ_T_HASH); + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), o, i) { + h += i.hash; + h += FIO_NAME2(fiobj, hash)(i.value); + } + return h; + } + case FIOBJ_T_OTHER: { + /* TODO: can we avoid "stringifying" the object? */ + fio_str_info_s tmp = (*fiobj_object_metadata(o))->to_s(o); + return fio_risky_hash(tmp.buf, tmp.len, seed); } + } + return 0; } -/** Marks the IO for closure as soon as scheduled data was sent. */ -SFUNC void fio_close(fio_s *io) { - if (io && (io->state & FIO___IO_STATE_OPEN) && - !(fio_atomic_or(&io->state, - (FIO___IO_STATE_CLOSING | FIO___IO_STATE_CLOSE_LOCAL)) & - FIO___IO_STATE_CLOSING)) { - FIO_LOG_DDEBUG2("scheduling IO %p (fd %d) for closure", (void *)io, io->fd); - fio_queue_push(fio___srv_tasks, - fio___srv_poll_on_ready, - fio_dup2((fio_s *)io)); - } +/** + * Sets a String value in a hash map, allocating the String and automatically + * calculating the hash value. + */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + set2)(FIOBJ hash, + const char *key, + size_t len, + FIOBJ value) { + FIOBJ tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(tmp, (char *)key, len); + FIOBJ v = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set)(hash, tmp, value, NULL); + fiobj_free(tmp); + return v; } -/** Marks the IO for immediate closure. */ -SFUNC void fio_close_now(fio_s *io) { - if (!io) - return; - fio_atomic_or(&io->state, FIO___IO_STATE_CLOSING); - if ((fio_atomic_and(&io->state, ~FIO___IO_STATE_OPEN) & FIO___IO_STATE_OPEN)) - fio_free2(io); +/** + * Finds a String value in a hash map, using a temporary String and + * automatically calculating the hash value. + */ +FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + get2)(FIOBJ hash, const char *buf, size_t len) { + if (FIOBJ_TYPE_CLASS(hash) != FIOBJ_T_HASH) + return FIOBJ_INVALID; + FIOBJ_STR_TEMP_VAR_STATIC(tmp, buf, len); + FIOBJ v = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(hash, tmp); + return v; } -/** Suspends future "on_data" events for the IO. */ -SFUNC void fio_srv_suspend(fio_s *io) { - if (!io) - return; - io->state |= FIO___IO_STATE_SUSPENDED; +/** + * Removes a String value in a hash map, using a temporary String and + * automatically calculating the hash value. + */ +FIO_IFUNC int FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + remove2)(FIOBJ hash, + const char *buf, + size_t len, + FIOBJ *old) { + FIOBJ_STR_TEMP_VAR_STATIC(tmp, buf, len); + int r = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(hash, tmp, old); + FIOBJ_STR_TEMP_DESTROY(tmp); + return r; } -/** Listens for future "on_data" events related to the IO. */ -SFUNC void fio_srv_unsuspend(fio_s *io) { - if (io && (fio_atomic_and(&io->state, ~FIO___IO_STATE_SUSPENDED) & - FIO___IO_STATE_SUSPENDED)) { - fio___s_monitor_in(io); +/** Updates a hash using information from another Hash. */ +FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), update)(FIOBJ dest, + FIOBJ src) { + if (FIOBJ_TYPE_CLASS(dest) != FIOBJ_T_HASH || + FIOBJ_TYPE_CLASS(src) != FIOBJ_T_HASH) + return; + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), src, i) { + if (i.key == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(i.key) == FIOBJ_T_NULL) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(dest, i.key, NULL); + continue; + } + register FIOBJ tmp; + switch (FIOBJ_TYPE_CLASS(i.value)) { + case FIOBJ_T_ARRAY: + /* TODO? decide if we should merge elements or overwrite...? */ + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(dest, i.key); + if (FIOBJ_TYPE_CLASS(tmp) == FIOBJ_T_ARRAY) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), concat) + (tmp, i.value); + continue; + } + break; + case FIOBJ_T_HASH: + tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(dest, i.key); + if (FIOBJ_TYPE_CLASS(tmp) == FIOBJ_T_HASH) + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), update) + (dest, i.value); + else break; + continue; + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_STRING: /* fall through */ + case FIOBJ_T_FLOAT: /* fall through */ + case FIOBJ_T_OTHER: break; + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) + (dest, i.key, fiobj_dup(i.value), NULL); } } -/** Returns 1 if the IO handle was suspended. */ -SFUNC int fio_srv_is_suspended(fio_s *io) { - return (io && io->state & FIO___IO_STATE_SUSPENDED); -} - -/** Returns 1 if the IO handle is marked as open. */ -SFUNC int fio_srv_is_open(fio_s *io) { - return io && (io->state & FIO___IO_STATE_OPEN) && - !(io->state & FIO___IO_STATE_CLOSING); -} - -/** Returns the approximate number of bytes in the outgoing buffer. */ -SFUNC size_t fio_srv_backlog(fio_s *io) { - return io ? fio_stream_length(&io->stream) : 0; -} - /* ***************************************************************************** -Listening to Incoming Connections +FIOBJ JSON support (inline functions) ***************************************************************************** */ typedef struct { - fio_protocol_s *protocol; - void *udata; - void *tls_ctx; - fio_srv_async_s *queue_for_accept; - fio_queue_s *queue; - fio_s *io; - void (*on_start)(fio_protocol_s *protocol, void *udata); - void (*on_stop)(fio_protocol_s *protocol, void *udata); - int owner; - int fd; - size_t ref_count; - size_t url_len; - uint8_t hide_from_log; - char url[]; -} fio___srv_listen_s; + FIOBJ json; + size_t level; + uint8_t beautify; +} fiobj___json_format_internal__s; -FIO_LEAK_COUNTER_DEF(fio_srv_listen) +/* internal helper function for recursive JSON formatting. */ +SFUNC void fiobj___json_format_internal__(fiobj___json_format_internal__s *, + FIOBJ); -static fio___srv_listen_s *fio___srv_listen_dup(fio___srv_listen_s *l) { - fio_atomic_add(&l->ref_count, 1); - return l; +/** Helper function, calls `fiobj_hash_update_json` with string information */ +FIO_IFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json2)(FIOBJ hash, char *ptr, size_t len) { + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(hash, FIO_STR_INFO2(ptr, len)); } -static void fio___srv_listen_free(void *l_) { - fio___srv_listen_s *l = (fio___srv_listen_s *)l_; - fio_close(l->io); - if (fio_atomic_sub(&l->ref_count, 1)) - return; - - fio_state_callback_remove(FIO_CALL_AT_EXIT, fio___srv_listen_free, (void *)l); - fio_state_callback_remove(FIO_CALL_ON_START, - fio___srv_listen_free, - (void *)l); - fio_state_callback_remove(FIO_CALL_PRE_START, - fio___srv_listen_free, - (void *)l); - fio___io_func_free_context_caller(l->protocol->io_functions.free_context, - l->tls_ctx); - fio_sock_close(l->fd); +/** + * Returns a JSON valid FIOBJ String, representing the object. + * + * If `dest` is an existing String, the formatted JSON data will be appended to + * the existing string. + */ +FIO_IFUNC FIOBJ FIO_NAME2(fiobj, json)(FIOBJ dest, FIOBJ o, uint8_t beautify) { + fiobj___json_format_internal__s args = + (fiobj___json_format_internal__s){.json = dest, .beautify = beautify}; + if (FIOBJ_TYPE_CLASS(dest) != FIOBJ_T_STRING) + args.json = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + fiobj___json_format_internal__(&args, o); + return args.json; +} -#ifdef AF_UNIX - /* delete the unix socket file, if any. */ - fio_url_s u = fio_url_parse(l->url, FIO_STRLEN(l->url)); - if (fio___srvdata.pid == l->owner && !u.host.buf && !u.port.buf && - u.path.buf) { - unlink(u.path.buf); - } -#endif +#undef FIO___RECURSIVE_INCLUDE /* from now on, type helpers are internal */ - if (l->on_stop) - l->on_stop(l->protocol, l->udata); +/* ***************************************************************************** +FIOBJ Mustache support - inline implementation +***************************************************************************** */ - if (l->hide_from_log) - FIO_LOG_DEBUG2("(%d) stopped listening @ %.*s", - getpid(), - (int)l->url_len, - l->url); - else - FIO_LOG_INFO("(%d) stopped listening @ %.*s", - getpid(), - (int)l->url_len, - l->url); - fio_queue_perform_all(fio___srv_tasks); - FIO_LEAK_COUNTER_ON_FREE(fio_srv_listen); - FIO_MEM_FREE_(l, sizeof(*l) + l->url_len + 1); -} +/* callback should write `txt` to output and return updated `udata.` */ +FIO_SFUNC void *fiobj___mustache_write_text(void *udata, fio_buf_info_s txt); +/* same as `write_text`, but should also HTML escape (sanitize) data. */ +FIO_SFUNC void *fiobj___mustache_write_text_escaped(void *udata, + fio_buf_info_s raw); +/* callback should return a new context pointer with the value of `name`. */ +FIO_SFUNC void *fiobj___mustache_get_var(void *ctx, fio_buf_info_s name); +/* if context is an Array, should return its length. */ +FIO_SFUNC size_t fiobj___mustache_array_length(void *ctx); +/* if context is an Array, should return a context pointer @ index. */ +FIO_SFUNC void *fiobj___mustache_get_var_index(void *ctx, size_t index); +/* should return the String value of context `var` as a `fio_buf_info_s`. */ +FIO_SFUNC fio_buf_info_s fiobj___mustache_var2str(void *var); +/* should return non-zero if the context pointer refers to a valid value. */ +FIO_SFUNC int fiobj___mustache_var_is_truthful(void *ctx); -SFUNC void fio_srv_listen_stop(void *listener) { - if (listener) - fio___srv_listen_free(listener); +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Returns a FIOBJ String with the rendered template. May return `FIOBJ_INVALID` + * if nothing was written. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build(fio_mustache_s *m, FIOBJ ctx) { + return (FIOBJ)fio_mustache_build( + m, + .write_text = fiobj___mustache_write_text, + .write_text_escaped = fiobj___mustache_write_text_escaped, + .get_var = fiobj___mustache_get_var, + .array_length = fiobj___mustache_array_length, + .get_var_index = fiobj___mustache_get_var_index, + .var2str = fiobj___mustache_var2str, + .var_is_truthful = fiobj___mustache_var_is_truthful, + .ctx = ctx, + .udata = NULL); } -/** Returns the URL on which the listener is listening. */ -SFUNC fio_buf_info_s fio_srv_listener_url(void *listener) { - fio___srv_listen_s *l = (fio___srv_listen_s *)listener; - return FIO_BUF_INFO2(l->url, l->url_len); +/** + * Builds a Mustache template using a FIOBJ context (usually a Hash). + * + * Writes output to `dest` string (may be `FIOBJ_INVALID` / `NULL`). + * + * Returns `dest` (or a new String). May return `FIOBJ_INVALID` if nothing was + * written and `dest` was empty. + */ +FIO_IFUNC FIOBJ fiobj_mustache_build2(fio_mustache_s *m, + FIOBJ dest, + FIOBJ ctx) { + dest = (FIOBJ)fio_mustache_build( + m, + .write_text = fiobj___mustache_write_text, + .write_text_escaped = fiobj___mustache_write_text_escaped, + .get_var = fiobj___mustache_get_var, + .array_length = fiobj___mustache_array_length, + .get_var_index = fiobj___mustache_get_var_index, + .var2str = fiobj___mustache_var2str, + .var_is_truthful = fiobj___mustache_var_is_truthful, + .ctx = ctx, + .udata = dest); + return dest; } -/** Returns true if the listener protocol has an attached TLS context. */ -SFUNC int fio_srv_listener_is_tls(void *listener) { - fio___srv_listen_s *l = (fio___srv_listen_s *)listener; - return !!l->tls_ctx; +/* callback should write `txt` to output and return updated `udata.` */ +FIO_SFUNC void *fiobj___mustache_write_text(void *udata, fio_buf_info_s txt) { + FIOBJ d = (FIOBJ)udata; + if (!d) + d = fiobj_str_new_buf(txt.len + 32); + fiobj_str_write(d, txt.buf, txt.len); + return (void *)d; } - -static void fio___srv_listen_on_data_task(void *io_, void *ignr_) { - (void)ignr_; - fio_s *io = (fio_s *)io_; - int fd; - fio___srv_listen_s *l = (fio___srv_listen_s *)(io->udata); - fio_srv_unsuspend(io); - while ((fd = fio_sock_accept(fio_fd_get(io), NULL, NULL)) != -1) { - fio_srv_attach_fd(fd, l->protocol, l->udata, l->tls_ctx); - } - fio_free2(io); +/* same as `write_text`, but should also HTML escape (sanitize) data. */ +FIO_SFUNC void *fiobj___mustache_write_text_escaped(void *ud, + fio_buf_info_s raw) { + FIOBJ d = (FIOBJ)ud; + if (!d) + d = fiobj_str_new_buf(raw.len + 32); + fiobj_str_write_html_escape(d, raw.buf, raw.len); + return (void *)d; } -static void fio___srv_listen_on_data_task_reschd(void *io_, void *ignr_) { - fio_srv_defer(fio___srv_listen_on_data_task, io_, ignr_); +/* callback should return a new context pointer with the value of `name`. */ +FIO_SFUNC void *fiobj___mustache_get_var(void *ctx, fio_buf_info_s name) { + if (!ctx) + return NULL; + if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_HASH)) + return NULL; + return fiobj_hash_get2((FIOBJ)ctx, name.buf, name.len); } -static void fio___srv_listen_on_attach(fio_s *io) { - fio___srv_listen_s *l = (fio___srv_listen_s *)(io->udata); - l->queue = (l->queue_for_accept && l->queue_for_accept->q != fio___srv_tasks) - ? l->queue_for_accept->q - : NULL; +/* if context is an Array, should return its length. */ +FIO_SFUNC size_t fiobj___mustache_array_length(void *ctx) { + if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_ARRAY)) + return 0; + return fiobj_array_count((FIOBJ)ctx); } -static void fio___srv_listen_on_shutdown(fio_s *io) { - fio___srv_listen_s *l = (fio___srv_listen_s *)(io->udata); - l->queue = fio_srv_queue(); +/* if context is an Array, should return a context pointer @ index. */ +FIO_SFUNC void *fiobj___mustache_get_var_index(void *ctx, size_t index) { + if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_ARRAY) || index > 0xFFFFFFFFUL) + return NULL; + return fiobj_array_get((FIOBJ)ctx, (uint32_t)index); } -static void fio___srv_listen_on_data(fio_s *io) { - fio___srv_listen_s *l = (fio___srv_listen_s *)(io->udata); - if (l->queue) { - fio_srv_suspend(io); - fio_queue_push(l->queue, - fio___srv_listen_on_data_task_reschd, - fio_dup2(io)); - return; - } - fio___srv_listen_on_data_task(fio_dup(io), NULL); +/* should return the String value of context `var` as a `fio_buf_info_s`. */ +FIO_SFUNC fio_buf_info_s fiobj___mustache_var2str(void *var) { + fio_buf_info_s r = {0}; + if (!var || var == fiobj_null()) + return r; + fio_str_info_s tmp = fiobj2cstr((FIOBJ)var); + r = FIO_STR2BUF_INFO(tmp); + return r; } -static void fio___srv_listen_on_close(void *l) { - ((fio___srv_listen_s *)l)->io = NULL; - fio___srv_listen_free(l); +/* should return non-zero if the context pointer refers to a valid value. */ +FIO_SFUNC int fiobj___mustache_var_is_truthful(void *v) { + return v && (FIOBJ)v != fiobj_null() && (FIOBJ)v != fiobj_false() && + (!FIOBJ_TYPE_IS((FIOBJ)v, FIOBJ_T_ARRAY) || + fiobj_array_count((FIOBJ)v)); } -static fio_protocol_s FIO___LISTEN_PROTOCOL = { - .on_attach = fio___srv_listen_on_attach, - .on_data = fio___srv_listen_on_data, - .on_close = fio___srv_listen_on_close, - .on_timeout = fio___srv_on_timeout_never, - .on_shutdown = fio___srv_listen_on_shutdown, +/* ***************************************************************************** + + +FIOBJ - Externed Implementation + + +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +FIOBJ Basic Object vtable +***************************************************************************** */ + +FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___OBJECT_CLASS_VTBL = { + .type_id = 99, /* type IDs below 100 are reserved. */ }; -FIO_SFUNC void fio___srv_listen_attach_task_deferred(void *l_, void *ignr_) { - fio___srv_listen_s *l = (fio___srv_listen_s *)l_; - l = fio___srv_listen_dup(l); - int fd = fio_sock_dup(l->fd); - FIO_ASSERT(fd != -1, "listening socket failed to `dup`"); - FIO_LOG_DEBUG2("(%d) Called dup(%d) to attach %d as a listening socket.", - (int)fio___srvdata.pid, - l->fd, - fd); - l->io = fio_srv_attach_fd(fd, &FIO___LISTEN_PROTOCOL, l, NULL); - if (l->on_start) - l->on_start(l->protocol, l->udata); - if (l->hide_from_log) - FIO_LOG_DEBUG2("(%d) started listening @ %s", fio___srvdata.pid, l->url); - else - FIO_LOG_INFO("(%d) started listening @ %s", fio___srvdata.pid, l->url); - (void)ignr_; -} +/* ***************************************************************************** +FIOBJ Complex Iteration +***************************************************************************** */ +typedef struct { + FIOBJ obj; + size_t pos; +} fiobj___stack_element_s; -FIO_SFUNC void fio___srv_listen_attach_task(void *l_) { - /* make sure to run in server thread */ - fio_srv_defer(fio___srv_listen_attach_task_deferred, l_, NULL); +#define FIO_ARRAY_NAME fiobj___active_stack +#define FIO_ARRAY_TYPE fiobj___stack_element_s +#define FIO_ARRAY_COPY(dest, src) \ + do { \ + (dest).obj = fiobj_dup((src).obj); \ + (dest).pos = (src).pos; \ + } while (0) +#define FIO_ARRAY_TYPE_CMP(a, b) (a).obj == (b).obj +#define FIO_ARRAY_DESTROY(o) fiobj_free(o) +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +#define FIO_ARRAY_TYPE_CMP(a, b) (a).obj == (b).obj +#define FIO_ARRAY_NAME fiobj___stack +#define FIO_ARRAY_TYPE fiobj___stack_element_s +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +typedef struct { + int (*task)(fiobj_each_s *info); + void *arg; + FIOBJ next; + size_t count; + fiobj___stack_s stack; + uint32_t end; + uint8_t stop; +} fiobj_____each2_data_s; + +FIO_SFUNC uint32_t fiobj____each2_element_count(FIOBJ o) { + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_STRING: /* fall through */ + case FIOBJ_T_FLOAT: return 0; + case FIOBJ_T_ARRAY: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + case FIOBJ_T_HASH: + return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + case FIOBJ_T_OTHER: /* fall through */ + return (*fiobj_object_metadata(o))->count(o); + } + return 0; +} +FIO_SFUNC int fiobj____each2_wrapper_task(fiobj_each_s *e) { + fiobj_____each2_data_s *d = (fiobj_____each2_data_s *)e->udata; + e->task = d->task; + e->udata = d->arg; + d->stop = (d->task(e) == -1); + d->task = e->task; + d->arg = e->udata; + e->task = fiobj____each2_wrapper_task; + e->udata = d; + ++d->count; + if (d->stop) + return -1; + uint32_t c = fiobj____each2_element_count(e->value); + if (c) { + d->next = e->value; + d->end = c; + return -1; + } + return 0; } -int fio_srv_listen___(void); /* IDE marker */ /** - * Sets up a network service on a listening socket. + * Performs a task for the object itself and each element held by the FIOBJ + * object or any of it's elements (a deep task). * - * Returns 0 on success or -1 on error. + * The order of performance is by order of appearance, as if all nesting levels + * were flattened. * - * See the `fio_listen` Macro for details. + * If `task` returns -1, the `each` loop will break (stop). + * + * Returns the number of elements processed. */ -SFUNC void *fio_srv_listen FIO_NOOP(struct fio_srv_listen_args args) { - fio___srv_listen_s *l = NULL; - void *built_tls = NULL; - int should_free_tls = !args.tls; - FIO_STR_INFO_TMP_VAR(url_alt, 2048); - if (!args.protocol) { - FIO_LOG_ERROR("fio_srv_listen requires a protocol to be assigned."); - return l; - } - if (args.on_root && !fio_srv_is_master()) { - FIO_LOG_ERROR("fio_srv_listen called with `on_root` by a non-root worker."); - return l; - } - if (!args.url) { - args.url = getenv("ADDRESS"); - if (!args.url) - args.url = "0.0.0.0"; - } - url_alt.len = strlen(args.url); - if (url_alt.len > 2024) { - FIO_LOG_ERROR("binding address / url too long."); - args.url = NULL; - } - fio_url_s url = fio_url_parse(args.url, url_alt.len); - if (url.scheme.buf && - (url.scheme.len > 2 && url.scheme.len < 5 && - (url.scheme.buf[0] | (char)0x20) == 't' && - (url.scheme.buf[1] | (char)0x20) == 'c') && - (url.scheme.buf[2] | (char)0x20) == 'p') - url.scheme = FIO_BUF_INFO0; - if (!url.port.buf && !url.scheme.buf) { - static size_t port_counter = 3000; - size_t port = fio_atomic_add(&port_counter, 1); - if (port == 3000 && getenv("PORT")) { - char *port_env = getenv("PORT"); - port = fio_atol10(&port_env); - if (!port | (port > 65535ULL)) - port = 3000; - } - url_alt.len = 0; - fio_string_write2(&url_alt, - NULL, - FIO_STRING_WRITE_STR2(url.scheme.buf, url.scheme.len), - (url.scheme.len ? FIO_STRING_WRITE_STR2("://", 3) - : FIO_STRING_WRITE_STR2(NULL, 0)), - FIO_STRING_WRITE_STR2(url.host.buf, url.host.len), - FIO_STRING_WRITE_STR2(":", 1), - FIO_STRING_WRITE_NUM(port)); - args.url = url_alt.buf; - url = fio_url_parse(args.url, url_alt.len); - } - - args.tls = fio_tls_from_url(args.tls, url); - fio___srv_init_protocol_test(args.protocol, !!args.tls); - built_tls = args.protocol->io_functions.build_context(args.tls, 0); - fio_buf_info_s url_buf = FIO_BUF_INFO2((char *)args.url, url_alt.len); - /* remove query details from URL */ - if (url.query.len) - url_buf.len = url.query.buf - (url_buf.buf + 1); - else if (url.target.len) - url_buf.len = url.target.buf - (url_buf.buf + 1); - l = (fio___srv_listen_s *) - FIO_MEM_REALLOC_(NULL, 0, sizeof(*l) + url_buf.len + 1, 0); - FIO_ASSERT_ALLOC(l); - FIO_LEAK_COUNTER_ON_ALLOC(fio_srv_listen); - *l = (fio___srv_listen_s){ - .protocol = args.protocol, - .udata = args.udata, - .tls_ctx = built_tls, - .queue_for_accept = args.queue_for_accept, - .on_start = args.on_start, - .on_stop = args.on_stop, - .owner = fio___srvdata.pid, - .url_len = url_buf.len, - .hide_from_log = args.hide_from_log, +SFUNC uint32_t fiobj_each2(FIOBJ o, int (*task)(fiobj_each_s *), void *udata) { + /* TODO - move to recursion with nesting limiter? */ + fiobj_____each2_data_s d = { + .task = task, + .arg = udata, + .next = FIOBJ_INVALID, + .stack = FIO_ARRAY_INIT, }; - FIO_MEMCPY(l->url, url_buf.buf, url_buf.len); - l->url[l->url_len] = 0; - if (should_free_tls) - fio_tls_free(args.tls); + struct FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each_s) e_tmp = { - l->fd = fio_sock_open2(l->url, FIO_SOCK_SERVER | FIO_SOCK_TCP); - if (l->fd == -1) { - fio___srv_listen_free(l); - return (l = NULL); - } - if (fio_srv_is_running()) { - fio_srv_defer(fio___srv_listen_attach_task_deferred, l, NULL); - } else { - fio_state_callback_add( - (args.on_root ? FIO_CALL_PRE_START : FIO_CALL_ON_START), - fio___srv_listen_attach_task, - (void *)l); - } - fio_state_callback_add(FIO_CALL_AT_EXIT, fio___srv_listen_free, l); - return l; + .parent = FIOBJ_INVALID, + .task = (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + each_s) *))fiobj____each2_wrapper_task, + .udata = &d, + .value = o, + }; + fiobj___stack_element_s i = {.obj = o, .pos = 0}; + uint32_t end = fiobj____each2_element_count(o); + fiobj____each2_wrapper_task((fiobj_each_s *)&e_tmp); + while (!d.stop && i.obj && i.pos < end) { + i.pos = + fiobj_each1(i.obj, fiobj____each2_wrapper_task, &d, (uint32_t)i.pos); + if (d.next != FIOBJ_INVALID) { + if (fiobj___stack_count(&d.stack) + 1 > FIOBJ_MAX_NESTING) { + FIO_LOG_ERROR("FIOBJ nesting level too deep (%u)." + "`fiobj_each2` stopping loop early.", + (unsigned int)fiobj___stack_count(&d.stack)); + d.stop = 1; + continue; + } + fiobj___stack_push(&d.stack, i); + i.pos = 0; + i.obj = d.next; + d.next = FIOBJ_INVALID; + end = d.end; + } else { + /* re-collect end position to accommodate for changes */ + end = fiobj____each2_element_count(i.obj); + } + while (i.pos >= end && fiobj___stack_count(&d.stack)) { + fiobj___stack_pop(&d.stack, &i); + end = fiobj____each2_element_count(i.obj); + } + }; + fiobj___stack_destroy(&d.stack); + return (uint32_t)d.count; } /* ***************************************************************************** -Establishing New Connections +FIOBJ Hash / Array / Other (enumerable) Equality test. ***************************************************************************** */ -typedef struct { - fio_protocol_s protocol; - fio_protocol_s *upr; - void (*on_failed)(fio_protocol_s *protocol, void *udata); - void *udata; - void *tls_ctx; - size_t url_len; - char url[]; -} fio___connecting_s; - -FIO_SFUNC void fio___connecting_cleanup(fio___connecting_s *c) { - fio___io_func_free_context_caller(c->protocol.io_functions.free_context, - c->tls_ctx); - FIO_MEM_FREE_(c, sizeof(*c) + c->url_len + 1); -} - -FIO_SFUNC void fio___connecting_on_close(void *udata) { - fio___connecting_s *c = (fio___connecting_s *)udata; - if (c->on_failed) - c->on_failed(c->upr, c->udata); - fio___connecting_cleanup(c); -} -FIO_SFUNC void fio___connecting_on_ready(fio_s *io) { - if (!fio_srv_is_open(io)) - return; - fio___connecting_s *c = (fio___connecting_s *)fio_udata_get(io); - FIO_LOG_DEBUG2("(%d) established client connection to %s", - (int)fio___srvdata.pid, - c->url); - fio_udata_set(io, c->udata); - fio_protocol_set(io, c->upr); - fio___connecting_cleanup(c); -} +/** Internal: compares two nestable objects. */ +SFUNC unsigned char fiobj___test_eq_nested(FIOBJ restrict a, + FIOBJ restrict b, + size_t nesting) { + if (a == b) + return 1; + if (FIOBJ_TYPE_CLASS(a) != FIOBJ_TYPE_CLASS(b)) + return 0; + if (fiobj____each2_element_count(a) != fiobj____each2_element_count(b)) + return 0; + if (nesting >= FIOBJ_MAX_NESTING) + return 0; -void fio_srv_connect___(void); /* IDE Marker */ -SFUNC fio_s *fio_srv_connect FIO_NOOP(fio_srv_connect_args_s args) { - int should_free_tls = !args.tls; - if (!args.protocol) - return NULL; - if (!args.url) { - if (args.on_failed) - args.on_failed(args.protocol, args.udata); - return NULL; - } - if (!args.timeout) - args.timeout = 30000; + ++nesting; - size_t url_len = strlen(args.url); - fio_url_s url = fio_url_parse(args.url, url_len); - args.tls = fio_tls_from_url(args.tls, url); - fio___srv_init_protocol(args.protocol, !!args.tls); - if (url.query.len) - url_len = url.query.buf - (args.url + 1); - else if (url.target.len) - url_len = url.target.buf - (args.url + 1); - fio___connecting_s *c = (fio___connecting_s *) - FIO_MEM_REALLOC_(NULL, 0, sizeof(*c) + url_len + 1, 0); - FIO_ASSERT_ALLOC(c); - *c = (fio___connecting_s){ - .protocol = - { - .on_ready = fio___connecting_on_ready, - .on_close = fio___connecting_on_close, - .io_functions = args.protocol->io_functions, - .timeout = args.timeout, - }, - .upr = args.protocol, - .on_failed = args.on_failed, - .udata = args.udata, - .tls_ctx = args.protocol->io_functions.build_context(args.tls, 1), - }; - FIO_MEMCPY(c->url, args.url, url_len); - c->url[url_len] = 0; - fio_s *io = fio_srv_attach_fd( - fio_sock_open2(c->url, FIO_SOCK_CLIENT | FIO_SOCK_NONBLOCK), - &c->protocol, - c, - c->tls_ctx); - if (should_free_tls) - fio_tls_free(args.tls); - return io; -} + switch (FIOBJ_TYPE_CLASS(a)) { + case FIOBJ_T_PRIMITIVE: /* fall through */ + case FIOBJ_T_NUMBER: /* fall through */ + case FIOBJ_T_FLOAT: return a == b; + case FIOBJ_T_STRING: + return FIO_NAME_BL(FIO_NAME(fiobj, FIOBJ___NAME_STRING), eq)(a, b); -/* ***************************************************************************** -Managing data after a fork -***************************************************************************** */ -FIO_SFUNC void fio___srv_after_fork(void *ignr_) { - (void)ignr_; - fio___srvdata.pid = fio_thread_getpid(); - fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - fio_queue_perform_all(fio___srv_tasks); - FIO_LIST_EACH(fio_protocol_s, - reserved.protocols, - &fio___srvdata.protocols, - pr) { - FIO_LIST_EACH(fio_s, node, &pr->reserved.ios, io) { fio_close_now(io); } - } - fio_queue_perform_all(fio___srv_tasks); - fio_invalidate_all(); - fio_queue_perform_all(fio___srv_tasks); - fio_queue_destroy(fio___srv_tasks); -} + case FIOBJ_T_ARRAY: + if (!fiobj____each2_element_count(a)) + return 1; + /* test each array member with matching index */ + { + const size_t count = fiobj____each2_element_count(a); + for (size_t i = 0; i < count; ++i) { + if (!fiobj___test_eq_nested( + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(a, + (int32_t)i), + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(b, + (int32_t)i), + nesting)) + return 0; + } + } + return 1; -FIO_SFUNC void fio___srv_cleanup_at_exit(void *ignr_) { - fio___srv_after_fork(ignr_); - fio_poll_destroy(&fio___srvdata.poll_data); - fio___srv_env_safe_destroy(&fio___srvdata.env); -#if FIO_VALIDITY_MAP_USE - fio_validity_map_destroy(&fio___srvdata.valid); -#if FIO_VALIDATE_IO_MUTEX - fio_thread_mutex_destroy(&fio___srvdata.valid_lock); -#endif -#endif /* FIO_VALIDATE_IO_MUTEX / FIO_VALIDITY_MAP_USE */ - fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - fio_queue_perform_all(fio___srv_tasks); - fio_timer_destroy(fio___srv_timer); - fio_queue_perform_all(fio___srv_tasks); + case FIOBJ_T_HASH: + if (!fiobj____each2_element_count(a)) + return 1; + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), a, pos) { + FIOBJ val = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(b, pos.key); + if (!fiobj___test_eq_nested(val, pos.value, nesting)) + return 0; + } + return 1; + case FIOBJ_T_OTHER: + if (!fiobj____each2_element_count(a) && + (*fiobj_object_metadata(a))->is_eq(a, b)) + return 1; + /* TODO: iterate through objects and test equality within nesting */ + return (*fiobj_object_metadata(a))->is_eq(a, b); + return 1; + } + return 0; } /* ***************************************************************************** -Initializing Server State +FIOBJ general helpers ***************************************************************************** */ -FIO_CONSTRUCTOR(fio___srv) { - fio_queue_init(fio___srv_tasks); - fio___srvdata.protocols = FIO_LIST_INIT(fio___srvdata.protocols); - fio___srvdata.tick = FIO___SRV_GET_TIME_MILLI(); - fio___srvdata.root_pid = fio___srvdata.pid = fio_thread_getpid(); - fio___srvdata.async = FIO_LIST_INIT(fio___srvdata.async); - fio_poll_init(&fio___srvdata.poll_data, - .on_data = fio___srv_poll_on_data_schd, - .on_ready = fio___srv_poll_on_ready_schd, - .on_close = fio___srv_poll_on_close_schd); - fio___srv_init_protocol_test(&FIO___MOCK_PROTOCOL, 0); - fio___srv_init_protocol_test(&FIO___LISTEN_PROTOCOL, 0); - fio_state_callback_add(FIO_CALL_IN_CHILD, fio___srv_after_fork, NULL); - fio_state_callback_add(FIO_CALL_AT_EXIT, fio___srv_cleanup_at_exit, NULL); + +FIO_SFUNC uint32_t fiobj___count_noop(FIOBJ o) { + return 0; + (void)o; } /* ***************************************************************************** -TLS Context Type and Helpers +FIOBJ Integers (bigger numbers) ***************************************************************************** */ -typedef struct { - fio_keystr_s nm; - void (*fn)(fio_s *); -} fio___tls_alpn_s; - -typedef struct { - fio_keystr_s nm; - fio_keystr_s public_cert_file; - fio_keystr_s private_key_file; - fio_keystr_s pk_password; -} fio___tls_cert_s; - -typedef struct { - fio_keystr_s nm; -} fio___tls_trust_s; - -#undef FIO_TYPEDEF_IMAP_REALLOC -#undef FIO_TYPEDEF_IMAP_FREE -#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE -#define FIO_TYPEDEF_IMAP_REALLOC(p, size_old, size, copy) realloc(p, size) -#define FIO_TYPEDEF_IMAP_FREE(ptr, len) free(ptr) -#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE 0 - -#define FIO___ALPN_HASH(o) ((uint16_t)fio_keystr_hash(o->nm)) -#define FIO___ALPN_CMP(a, b) fio_keystr_is_eq(a->nm, b->nm) -#define FIO___ALPN_VALID(o) fio_keystr_buf(&o->nm).len - -FIO_TYPEDEF_IMAP_ARRAY(fio___tls_alpn_map, - fio___tls_alpn_s, - uint16_t, - FIO___ALPN_HASH, - FIO___ALPN_CMP, - FIO___ALPN_VALID) -FIO_TYPEDEF_IMAP_ARRAY(fio___tls_trust_map, - fio___tls_trust_s, - uint16_t, - FIO___ALPN_HASH, - FIO___ALPN_CMP, - FIO___ALPN_VALID) -FIO_TYPEDEF_IMAP_ARRAY(fio___tls_cert_map, - fio___tls_cert_s, - uint16_t, - FIO___ALPN_HASH, - FIO___ALPN_CMP, - FIO_IMAP_ALWAYS_VALID) - -#undef FIO___ALPN_HASH -#undef FIO___ALPN_CMP -#undef FIO___ALPN_VALID -#undef FIO_TYPEDEF_IMAP_REALLOC -#undef FIO_TYPEDEF_IMAP_FREE -#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE -#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC -#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE -#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), + cstr)(FIOBJ i) { + static char buf[32 * 128]; + static uint8_t pos = 0; + size_t at = fio_atomic_add(&pos, 1); + fio_str_info_s s = {.buf = buf + ((at & 127) << 5), .capa = 31}; + fio_string_write_i(&s, + NULL, + FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(i)); + return s; +} -struct fio_tls_s { - fio___tls_cert_map_s cert; - fio___tls_alpn_map_s alpn; - fio___tls_trust_map_s trust; - uint8_t trust_sys; /** Set to 1 if system certificate registry is trusted */ +FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___NUMBER_CLASS_VTBL = { + /** + * MUST return a unique number to identify object type. + * + * Numbers (IDs) under 100 are reserved. + */ + .type_id = FIOBJ_T_NUMBER, + /** Test for equality between two objects with the same `type_id` */ + .is_eq = FIO_NAME_BL(fiobj___num, eq), + /** Converts an object to a String */ + .to_s = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), cstr), + /** Converts and object to an integer */ + .to_i = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i), + /** Converts and object to a float */ + .to_f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f), + /** Returns the number of exposed elements held by the object, if any. */ + .count = fiobj___count_noop, + /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ + .each1 = NULL, + /** Deallocates the element (but NOT any of it's exposed elements). */ + .free2 = fiobj___bignum_free2, }; -#define FIO___RECURSIVE_INCLUDE 1 -#define FIO_REF_NAME fio_tls -#define FIO_REF_DESTROY(tls) \ - do { \ - FIO_IMAP_EACH(fio___tls_alpn_map, &tls.alpn, i) { \ - fio_keystr_destroy(&tls.alpn.ary[i].nm, FIO_STRING_FREE_KEY); \ - } \ - FIO_IMAP_EACH(fio___tls_trust_map, &tls.trust, i) { \ - fio_keystr_destroy(&tls.trust.ary[i].nm, FIO_STRING_FREE_KEY); \ - } \ - FIO_IMAP_EACH(fio___tls_cert_map, &tls.cert, i) { \ - fio_keystr_destroy(&tls.cert.ary[i].nm, FIO_STRING_FREE_KEY); \ - fio_keystr_destroy(&tls.cert.ary[i].public_cert_file, \ - FIO_STRING_FREE_KEY); \ - fio_keystr_destroy(&tls.cert.ary[i].private_key_file, \ - FIO_STRING_FREE_KEY); \ - fio_keystr_destroy(&tls.cert.ary[i].pk_password, FIO_STRING_FREE_KEY); \ - } \ - fio___tls_alpn_map_destroy(&tls.alpn); \ - fio___tls_trust_map_destroy(&tls.trust); \ - fio___tls_cert_map_destroy(&tls.cert); \ - } while (0) -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +/* ***************************************************************************** +FIOBJ Floats (bigger / smaller doubles) +***************************************************************************** */ -/** Performs a `new` operation, returning a new `fio_tls_s` context. */ -SFUNC fio_tls_s *fio_tls_new(void) { - fio_tls_s *r = fio_tls_new2(); - FIO_ASSERT_ALLOC(r); - *r = (fio_tls_s){.trust_sys = 0}; +FIO_SFUNC unsigned char FIO_NAME_BL(fiobj___float, eq)(FIOBJ restrict a, + FIOBJ restrict b) { + unsigned char r = 0; + union { + uint64_t u; + double f; + } da, db; + da.f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(a); + db.f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(b); + /* regular equality? */ + r |= da.f == db.f; + /* test for small rounding errors (4 bit difference) on normalize floats */ + r |= !((da.u ^ db.u) & UINT64_C(0xFFFFFFFFFFFFFFF0)) && + (da.u & UINT64_C(0x7FF0000000000000)); + /* test for small ULP: */ + r |= (((da.u > db.u) ? da.u - db.u : db.u - da.u) < 2); + /* test for +-0 */ + r |= !((da.u | db.u) & UINT64_C(0x7FFFFFFFFFFFFFFF)); return r; } -/** Performs a `dup` operation, increasing the object's reference count. */ -SFUNC fio_tls_s *fio_tls_dup(fio_tls_s *tls) { return fio_tls_dup2(tls); } - -/** Performs a `free` operation, reducing the reference count and freeing. */ -SFUNC void fio_tls_free(fio_tls_s *tls) { - if (!tls) - return; - fio_tls_free2(tls); +SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), + cstr)(FIOBJ i) { + static char buf[32 * 128]; + static uint8_t pos = 0; + size_t at = fio_atomic_add(&pos, 1); + char *tmp = buf + ((at & 127) << 5); + size_t len = + fio_ftoa(tmp, FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(i), 10); + tmp[len] = 0; + return FIO_STR_INFO2(tmp, len); } -/** Takes a parsed URL and optional TLS target and returns a TLS if needed. */ -SFUNC fio_tls_s *fio_tls_from_url(fio_tls_s *tls, fio_url_s url) { - /* test for TLS info in URL */ - fio_url_tls_info_s tls_info = fio_url_is_tls(url); - if (!tls_info.tls) - return tls; - - if (!tls && tls_info.tls) - tls = fio_tls_new(); +FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___FLOAT_CLASS_VTBL = { + /** + * MUST return a unique number to identify object type. + * + * Numbers (IDs) under 100 are reserved. + */ + .type_id = FIOBJ_T_FLOAT, + /** Test for equality between two objects with the same `type_id` */ + .is_eq = FIO_NAME_BL(fiobj___float, eq), + /** Converts an object to a String */ + .to_s = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), cstr), + /** Converts and object to an integer */ + .to_i = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i), + /** Converts and object to a float */ + .to_f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f), + /** Returns the number of exposed elements held by the object, if any. */ + .count = fiobj___count_noop, + /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ + .each1 = NULL, + /** Deallocates the element (but NOT any of it's exposed elements). */ + .free2 = fiobj___bigfloat_free2, +}; - if (tls_info.key.buf && tls_info.cert.buf) { - const char *tmp = NULL; - FIO_STR_INFO_TMP_VAR(host_tmp, 512); - FIO_STR_INFO_TMP_VAR(key_tmp, 128); - FIO_STR_INFO_TMP_VAR(cert_tmp, 128); - FIO_STR_INFO_TMP_VAR(pass_tmp, 128); - if (url.host.len < 512 && url.host.buf) - fio_string_write(&host_tmp, NULL, url.host.buf, url.host.len); - else - host_tmp.buf = NULL; +/* ***************************************************************************** +FIOBJ JSON support - output +***************************************************************************** */ - if (tls_info.key.len < 124 && tls_info.cert.len < 124 && - tls_info.pass.len < 124) { - fio_string_write(&key_tmp, NULL, tls_info.key.buf, tls_info.key.len); - fio_string_write(&cert_tmp, NULL, tls_info.cert.buf, tls_info.cert.len); - if (tls_info.pass.len) - fio_string_write(&pass_tmp, NULL, tls_info.pass.buf, tls_info.pass.len); - else - pass_tmp.buf = NULL; +FIO_IFUNC void fiobj___json_format_internal_beauty_pad(FIOBJ json, + size_t level) { + size_t pos = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(json); + fio_str_info_s tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), + resize)(json, (level << 1) + pos + 2); + tmp.buf[pos++] = '\r'; + tmp.buf[pos++] = '\n'; + for (size_t i = 0; i < level; ++i) { + tmp.buf[pos++] = ' '; + tmp.buf[pos++] = ' '; + } +} - if (tls_info.key.buf == - tls_info.cert.buf) { /* assume value is prefix / folder */ - if ((tmp = getenv(cert_tmp.buf))) { - fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); - if (buf_tmp.len < 124) { - key_tmp.len = cert_tmp.len = buf_tmp.len; - FIO_MEMCPY(key_tmp.buf, buf_tmp.buf, buf_tmp.len); - FIO_MEMCPY(cert_tmp.buf, buf_tmp.buf, buf_tmp.len); - } +SFUNC void fiobj___json_format_internal__(fiobj___json_format_internal__s *args, + FIOBJ o) { + switch (FIOBJ_TYPE(o)) { + case FIOBJ_T_TRUE: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "true", 4); + return; + case FIOBJ_T_FALSE: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "false", 5); + return; + case FIOBJ_T_NULL: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "null", 4); + return; + case FIOBJ_T_NUMBER: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i) + (args->json, FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(o)); + return; + case FIOBJ_T_FLOAT: { + char tmp_buf[256]; + size_t len = fio_ftoa(tmp_buf, + FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(o), + 10); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, tmp_buf, len); + return; + } + case FIOBJ_T_STRING: /* fall through */ + default: { + fio_str_info_s info = FIO_NAME2(fiobj, cstr)(o); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "\"", 1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) + (args->json, info.buf, info.len); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "\"", 1); + return; + } + case FIOBJ_T_ARRAY: + if (!FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o)) + goto empty_array; + if (args->level == FIOBJ_MAX_NESTING) + goto err_array_nesting; + { + ++args->level; + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "[", 1); + const uint32_t len = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + fiobj___json_format_internal__( + args, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, 0)); + if (args->beautify) { + for (size_t i = 1; i < len; ++i) { + FIOBJ child = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, ",", 1); + fiobj___json_format_internal_beauty_pad(args->json, args->level); + fiobj___json_format_internal__(args, child); } - fio_string_write(&key_tmp, NULL, "key.pem", 7); - fio_string_write(&cert_tmp, NULL, "cert.pem", 8); } else { - if ((tmp = getenv(key_tmp.buf))) { - fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); - if (buf_tmp.len < 124) { - key_tmp.len = buf_tmp.len; - FIO_MEMCPY(key_tmp.buf, buf_tmp.buf, buf_tmp.len); - } - } - - if ((tmp = getenv(cert_tmp.buf))) { - fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); - if (buf_tmp.len < 124) { - cert_tmp.len = buf_tmp.len; - FIO_MEMCPY(cert_tmp.buf, buf_tmp.buf, buf_tmp.len); - } - } - - if (tls_info.key.len < 5 || - (fio_buf2u32u(tls_info.key.buf + (tls_info.key.len - 4)) | - 0x20202020UL) != fio_buf2u32u(".pem")) { - fio_string_write(&key_tmp, NULL, ".pem", 4); + for (size_t i = 1; i < len; ++i) { + FIOBJ child = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, i); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, ",", 1); + fiobj___json_format_internal__(args, child); } - if (tls_info.cert.len < 5 || - (fio_buf2u32u(tls_info.cert.buf + (tls_info.cert.len - 4)) | - 0x20202020UL) != fio_buf2u32u(".pem")) { - fio_string_write(&cert_tmp, NULL, ".pem", 4); + } + --args->level; + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "]", 1); + } + return; + case FIOBJ_T_HASH: + if (!FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o)) + goto empty_hash; + if (args->level == FIOBJ_MAX_NESTING) + goto err_hash_nesting; + { + size_t i = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "{", 1); + ++args->level; + FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), o, couplet) { + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); } + fio_str_info_s info = FIO_NAME2(fiobj, cstr)(couplet.key); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "\"", 1); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) + (args->json, info.buf, info.len); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "\":", 2); + fiobj___json_format_internal__(args, couplet.value); + if (--i) + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, ",", 1); } - fio_tls_cert_add(tls, - host_tmp.buf, - cert_tmp.buf, - key_tmp.buf, - pass_tmp.buf); - } else { - FIO_LOG_ERROR("TLS files in `fio_srv_listen` URL too long, " - "construct TLS object separately"); + --args->level; + if (args->beautify) { + fiobj___json_format_internal_beauty_pad(args->json, args->level); + } + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "}", 1); } + return; } - return tls; +empty_hash: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "{}", 2); + return; +empty_array: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "[]", 2); + return; +err_array_nesting: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "[ ]", 3); + goto log_nesting_error; +err_hash_nesting: + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) + (args->json, "{ }", 3); +log_nesting_error: + FIO_LOG_ERROR("JSON formatting truncated - nesting level too deep."); } -/** Adds a certificate a new SSL/TLS context / settings object (SNI support). */ -SFUNC fio_tls_s *fio_tls_cert_add(fio_tls_s *t, - const char *server_name, - const char *public_cert_file, - const char *private_key_file, - const char *pk_password) { - if (!t) - return t; - fio___tls_cert_s o = { - .nm = fio_keystr_init(FIO_STR_INFO1((char *)server_name), - FIO_STRING_ALLOC_KEY), - .public_cert_file = - fio_keystr_init(FIO_STR_INFO1((char *)public_cert_file), - FIO_STRING_ALLOC_KEY), - .private_key_file = - fio_keystr_init(FIO_STR_INFO1((char *)private_key_file), - FIO_STRING_ALLOC_KEY), - .pk_password = fio_keystr_init(FIO_STR_INFO1((char *)pk_password), - FIO_STRING_ALLOC_KEY), - }; - fio___tls_cert_s *old = fio___tls_cert_map_get(&t->cert, o); - if (old) - goto replace_old; - fio___tls_cert_map_set(&t->cert, o, 1); - return t; -replace_old: - fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); - fio_keystr_destroy(&old->public_cert_file, FIO_STRING_FREE_KEY); - fio_keystr_destroy(&old->private_key_file, FIO_STRING_FREE_KEY); - fio_keystr_destroy(&old->pk_password, FIO_STRING_FREE_KEY); - *old = o; - return t; -} +/* ***************************************************************************** +FIOBJ JSON parsing +***************************************************************************** */ +#if 1 -/** - * Adds an ALPN protocol callback to the SSL/TLS context. - * - * The first protocol added will act as the default protocol to be selected. - * - * Except for the `tls` and `protocol_name` arguments, all arguments can be - * NULL. - */ -SFUNC fio_tls_s *fio_tls_alpn_add(fio_tls_s *t, - const char *protocol_name, - void (*on_selected)(fio_s *)) { - if (!t || !protocol_name) - return t; - if (!on_selected) - on_selected = fio___srv_on_ev_mock; - size_t pr_name_len = strlen(protocol_name); - if (pr_name_len > 255) { - FIO_LOG_ERROR("fio_tls_alpn_add called with name longer than 255 chars!"); - return t; - } - fio___tls_alpn_s o = { - .nm = fio_keystr_init(FIO_STR_INFO2((char *)protocol_name, pr_name_len), - FIO_STRING_ALLOC_KEY), - .fn = on_selected, - }; - fio___tls_alpn_s *old = fio___tls_alpn_map_get(&t->alpn, o); - if (old) - goto replace_old; - fio___tls_alpn_map_set(&t->alpn, o, 1); - return t; -replace_old: - fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); - *old = o; - return t; +FIO_SFUNC void *fiobj___json_on_null(void) { + return FIO_NAME(fiobj, FIOBJ___NAME_NULL)(); } - -/** Calls the `on_selected` callback for the `fio_tls_s` object. */ -SFUNC int fio_tls_alpn_select(fio_tls_s *t, - const char *protocol_name, - size_t name_length, - fio_s *io) { - if (!t || !protocol_name) - return -1; - fio___tls_alpn_s seeking = { - .nm = fio_keystr_tmp(protocol_name, (uint32_t)name_length)}; - fio___tls_alpn_s *alpn = fio___tls_alpn_map_get(&t->alpn, seeking); - if (!alpn) { - FIO_LOG_DDEBUG2("TLS ALPN %.*s not found in %zu long list", - (int)name_length, - protocol_name, - t->alpn.count); - return -1; - } - alpn->fn(io); +FIO_SFUNC void *fiobj___json_on_true(void) { return fiobj_true(); } +FIO_SFUNC void *fiobj___json_on_false(void) { return fiobj_false(); } +FIO_SFUNC void *fiobj___json_on_number(int64_t i) { + return FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_NUMBER, new))(i); +} +FIO_SFUNC void *fiobj___json_on_float(double f) { + return FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_FLOAT, new))(f); +} +FIO_SFUNC void *fiobj___json_on_string(const void *start, size_t len) { + FIOBJ str = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, new))(); + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, write_unescape)) + (str, (const char *)start, len); + return str; +} +FIO_SFUNC void *fiobj___json_on_string_simple(const void *start, size_t len) { + FIOBJ str = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, new))(); + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, write)) + (str, (const char *)start, len); + return str; +} +FIO_SFUNC void *fiobj___json_on_map(void *ctx, void *at) { + FIOBJ m = FIOBJ_INVALID; + if (ctx && at && FIOBJ_TYPE_CLASS(ctx) == FIOBJ_T_HASH) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, get))((FIOBJ)ctx, + (FIOBJ)at); + if (!m || m == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(m) != FIOBJ_T_ARRAY) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, new))(); + return m; +} +FIO_SFUNC void *fiobj___json_on_array(void *ctx, void *at) { + FIOBJ m = FIOBJ_INVALID; + if (ctx && at && FIOBJ_TYPE_CLASS(ctx) == FIOBJ_T_HASH) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, get))((FIOBJ)ctx, + (FIOBJ)at); + if (!m || m == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(m) != FIOBJ_T_ARRAY) + m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_ARRAY, new))(); + return m; +} +FIO_SFUNC int fiobj___json_map_push(void *ctx, void *key, void *value) { + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, set)) + ((FIOBJ)ctx, (FIOBJ)key, (FIOBJ)value, NULL); + fiobj_free((FIOBJ)key); + return 0; +} +FIO_SFUNC int fiobj___json_array_push(void *ctx, void *value) { + FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_ARRAY, push))((FIOBJ)ctx, (FIOBJ)value); return 0; } +FIO_SFUNC void fiobj___json_free_unused_object(void *ctx) { + fiobj_free((FIOBJ)ctx); +} +FIO_SFUNC void *fiobj___json_on_error(void *ctx) { + fiobj_free((FIOBJ)ctx); + return FIOBJ_INVALID; +} +static fio_json_parser_callbacks_s FIOBJ_JSON_PARSER_CALLBACKS = { + .on_null = fiobj___json_on_null, + .on_true = fiobj___json_on_true, + .on_false = fiobj___json_on_false, + .on_number = fiobj___json_on_number, + .on_float = fiobj___json_on_float, + .on_string = fiobj___json_on_string, + .on_string_simple = fiobj___json_on_string_simple, + .on_map = fiobj___json_on_map, + .on_array = fiobj___json_on_array, + .map_push = fiobj___json_map_push, + .array_push = fiobj___json_array_push, + .free_unused_object = fiobj___json_free_unused_object, + .on_error = fiobj___json_on_error, +}; -/** - * Adds a certificate to the "trust" list, which automatically adds a peer - * verification requirement. - * - * If `public_cert_file` is `NULL`, adds the system's default trust registry. - * - * Note: when the `fio_tls_s` object is used for server connections, this will - * limit connections to clients that connect using a trusted certificate. - * - * fio_tls_trust_add(tls, "google-ca.pem" ); - */ -SFUNC fio_tls_s *fio_tls_trust_add(fio_tls_s *t, const char *public_cert_file) { - if (!t) - return t; - if (!public_cert_file) { - t->trust_sys = 1; - return t; +/** Returns a JSON valid FIOBJ String, representing the object. */ +SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed_p) { + fio_json_result_s result = + fio_json_parse(&FIOBJ_JSON_PARSER_CALLBACKS, str.buf, str.len); + if (consumed_p) + *consumed_p = result.stop_pos; + if (result.err) { +#ifdef DEBUG + FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, (FIOBJ)result.ctx, 0); + FIO_LOG_DEBUG("JSON data being deleted:\n%s", + FIO_NAME2(fiobj, cstr)(s).buf); + fiobj_free(s); +#endif + fiobj_free((FIOBJ)result.ctx); + result.ctx = FIOBJ_INVALID; } - fio___tls_trust_s o = { - .nm = fio_keystr_init(FIO_STR_INFO1((char *)public_cert_file), - FIO_STRING_ALLOC_KEY), - }; - fio___tls_trust_s *old = fio___tls_trust_map_get(&t->trust, o); - if (old) - goto replace_old; - fio___tls_trust_map_set(&t->trust, o, 1); - return t; -replace_old: - fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); - *old = o; - return t; + return (FIOBJ)result.ctx; } /** - * Returns the number of `fio_tls_cert_add` instructions. - * - * This could be used when deciding if to add a NULL instruction (self-signed). + * Updates a Hash using JSON data. * - * If `fio_tls_cert_add` was never called, zero (0) is returned. - */ -SFUNC uintptr_t fio_tls_cert_count(fio_tls_s *tls) { - return tls ? tls->cert.count : 0; -} - -/** - * Returns the number of registered ALPN protocol names. + * Parsing errors and non-dictionary object JSON data are silently ignored, + * attempting to update the Hash as much as possible before any errors + * encountered. * - * This could be used when deciding if protocol selection should be delegated to - * the ALPN mechanism, or whether a protocol should be immediately assigned. + * Conflicting Hash data is overwritten (preferring the new over the old). * - * If no ALPN protocols are registered, zero (0) is returned. + * Returns the number of bytes consumed. On Error, 0 is returned and no data is + * consumed. */ -SFUNC uintptr_t fio_tls_alpn_count(fio_tls_s *tls) { - return tls ? tls->alpn.count : 0; +SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(FIOBJ hash, fio_str_info_s str) { + /* TODO! FIXME! this will leak memory on NULL hash and break on Arrays */ + fio_json_result_s result = fio_json_parse_update(&FIOBJ_JSON_PARSER_CALLBACKS, + hash, + str.buf, + str.len); + // if (consumed_p) + // *consumed_p = result.stop_pos; + if (result.err) { +#ifdef DEBUG + FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, (FIOBJ)result.ctx, 0); + FIO_LOG_DEBUG("JSON data being deleted:\n%s", + FIO_NAME2(fiobj, cstr)(s).buf); + fiobj_free(s); +#endif + fiobj_free((FIOBJ)result.ctx); + result.ctx = FIOBJ_INVALID; + } + return result.stop_pos; + FIO_LOG_ERROR("fiobj_hash_update_json note yet implemented"); + return 0; + (void)str; } -/** - * Returns the number of `fio_tls_trust_add` instructions. - * - * This could be used when deciding if to disable peer verification or not. - * - * If `fio_tls_trust_add` was never called, zero (0) is returned. - */ -SFUNC uintptr_t fio_tls_trust_count(fio_tls_s *tls) { - return tls ? tls->trust.count : 0; -} +#else +#define FIO_JSON +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -/** Calls callbacks for certificate, trust certificate and ALPN added. */ -void fio_tls_each___(void); /* IDE Marker*/ -SFUNC int fio_tls_each FIO_NOOP(fio_tls_each_s a) { - if (!a.tls) - return -1; - if (a.each_cert) { - FIO_IMAP_EACH(fio___tls_cert_map, &a.tls->cert, i) { - if (a.each_cert(&a, - fio_keystr_buf(&a.tls->cert.ary[i].nm).buf, - fio_keystr_buf(&a.tls->cert.ary[i].public_cert_file).buf, - fio_keystr_buf(&a.tls->cert.ary[i].private_key_file).buf, - fio_keystr_buf(&a.tls->cert.ary[i].pk_password).buf)) - return -1; - } - } - if (a.each_alpn) { - FIO_IMAP_EACH(fio___tls_alpn_map, &a.tls->alpn, i) { - if (a.each_alpn(&a, - fio_keystr_buf(&a.tls->alpn.ary[i].nm).buf, - a.tls->alpn.ary[i].fn)) - return -1; - } - } - if (a.each_trust) { - if (a.tls->trust_sys && a.each_trust(&a, NULL)) - return -1; - FIO_IMAP_EACH(fio___tls_trust_map, &a.tls->trust, i) { - if (a.each_trust(&a, fio_keystr_buf(&a.tls->trust.ary[i].nm).buf)) - return -1; +/* FIOBJ JSON parser */ +typedef struct { + fio_json_parser_s p; + size_t so; /* stack offset */ + FIOBJ key; + FIOBJ top; + FIOBJ target; + FIOBJ stack[JSON_MAX_DEPTH + 1]; +} fiobj_json_parser_s; + +static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) { + if (p->top) { + if (FIOBJ_TYPE_CLASS(p->top) == FIOBJ_T_HASH) { + if (p->key) { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) + (p->top, p->key, o, NULL); + fiobj_free(p->key); + p->key = FIOBJ_INVALID; + } else { + p->key = o; + } + } else { + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(p->top, o); } + } else { + p->top = o; } - return 0; } -/** If `NULL` returns current default, otherwise sets it. */ -SFUNC fio_io_functions_s fio_tls_default_io_functions(fio_io_functions_s *f) { - static fio_io_functions_s default_io_functions = { - .build_context = fio___io_func_default_build_context, - .start = fio___srv_on_ev_mock, - .read = fio___io_func_default_read, - .write = fio___io_func_default_write, - .flush = fio___io_func_default_flush, - .finish = fio___io_func_default_finish, - .cleanup = fio___srv_on_close_mock, - }; - if (!f) - return default_io_functions; - if (!f->build_context) - f->build_context = fio___io_func_default_build_context; - if (!f->start) - f->start = fio___srv_on_ev_mock; - if (!f->read) - f->read = fio___io_func_default_read; - if (!f->write) - f->write = fio___io_func_default_write; - if (!f->flush) - f->flush = fio___io_func_default_flush; - if (!f->finish) - f->finish = fio___io_func_default_finish; - if (!f->cleanup) - f->cleanup = fio___srv_on_close_mock; - default_io_functions = *f; - return default_io_functions; +/** a NULL object was detected */ +static inline void fio_json_on_null(fio_json_parser_s *p) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, + FIO_NAME(fiobj, FIOBJ___NAME_NULL)()); } - -/* ***************************************************************************** -Server Async - Worker Threads for non-IO tasks -***************************************************************************** */ - -FIO_SFUNC void fio___srv_async_start(fio_srv_async_s *q) { - if (!q->count) - goto no_worker_threads; - q->q = &q->queue; - if (q->count > 4095) - goto failed; - fio_queue_workers_stop(&q->queue); - if (fio_queue_workers_add(&q->queue, (size_t)q->count)) - goto failed; - return; - -failed: - FIO_LOG_ERROR("Server Async Queue couldn't spawn threads!"); -no_worker_threads: - q->q = fio_srv_queue(); - fio_queue_perform_all(&q->queue); +/** a TRUE object was detected */ +static inline void fio_json_on_true(fio_json_parser_s *p) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true()); } -FIO_SFUNC void fio___srv_async_stop(fio_srv_async_s *q) { - q->q = fio___srv_tasks; - fio_queue_workers_stop(&q->queue); - fio_queue_perform_all(&q->queue); - fio_queue_destroy(&q->queue); +/** a FALSE object was detected */ +static inline void fio_json_on_false(fio_json_parser_s *p) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false()); } - -/** Initializes an async server queue for multo-threaded (non IO) tasks. */ -SFUNC void fio_srv_async_init(fio_srv_async_s *q, uint32_t threads) { - *q = (fio_srv_async_s){ - .q = fio_srv_queue(), - .count = threads, - .queue = FIO_QUEUE_STATIC_INIT(q->queue), - .node = FIO_LIST_INIT(q->node), - }; - FIO_LIST_PUSH(&fio___srvdata.async, &q->node); +/** a Numeral was detected (long long). */ +static inline void fio_json_on_number(fio_json_parser_s *p, long long i) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(i)); } - -/** Updates an async server queue for multi-threaded (non IO) tasks. */ -SFUNC void fio_srv_async_update(fio_srv_async_s *q, uint32_t threads) { - q->count = threads; - if (fio_srv_is_running()) - fio___srv_async_start(q); +/** a Float was detected (double). */ +static inline void fio_json_on_float(fio_json_parser_s *p, double f) { + fiobj_json_add2parser((fiobj_json_parser_s *)p, + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(f)); } - -/* ***************************************************************************** -Done with Server code -***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#endif /* FIO_SERVER */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_SERVER /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** - - - - - OpenSSL Implementation for IO Functions - - - - -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(H___FIO_SERVER___H) && \ - (HAVE_OPENSSL || __has_include("openssl/ssl.h")) && \ - !defined(H___FIO_OPENSSL___H) && !defined(FIO___RECURSIVE_INCLUDE) -#define H___FIO_OPENSSL___H 1 -/* ***************************************************************************** -OpenSSL IO Function Getter -***************************************************************************** */ - -/* Returns the OpenSSL IO functions. */ -SFUNC fio_io_functions_s fio_openssl_io_functions(void); - -/* ***************************************************************************** -OpenSSL Helpers Implementation -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) - -#include -#include - -FIO_ASSERT_STATIC(OPENSSL_VERSION_MAJOR > 2, "OpenSSL version mismatch"); - -/* ***************************************************************************** -Self-Signed Certificates - TODO: change to ECDSA -***************************************************************************** */ -static EVP_PKEY *fio___openssl_pkey = NULL; -static void fio___openssl_clear_root_key(void *key) { - EVP_PKEY_free(((EVP_PKEY *)key)); - fio___openssl_pkey = NULL; +/** a String was detected (int / float). update `pos` to point at ending */ +static inline void fio_json_on_string(fio_json_parser_s *p, + const void *start, + size_t len) { + FIOBJ str = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_unescape) + (str, start, len); + fiobj_json_add2parser((fiobj_json_parser_s *)p, str); } - -static void fio___openssl_make_root_key(void) { - static fio_lock_i lock = FIO_LOCK_INIT; - fio_lock(&lock); - if (!fio___openssl_pkey) { - /* create private key, free it at exit */ - FIO_LOG_DEBUG2("calculating a new TLS private key... might take a while."); - fio___openssl_pkey = EVP_RSA_gen(2048); - FIO_ASSERT(fio___openssl_pkey, "OpenSSL failed to create private key."); - fio_state_callback_add(FIO_CALL_AT_EXIT, - fio___openssl_clear_root_key, - fio___openssl_pkey); +/** a dictionary object was detected */ +static inline int fio_json_on_start_object(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + if (pr->target) { + /* push NULL, don't free the objects */ + pr->stack[pr->so++] = FIOBJ_INVALID; + pr->top = pr->target; + pr->target = FIOBJ_INVALID; + } else { + FIOBJ hash; +#if FIOBJ_JSON_APPEND + hash = FIOBJ_INVALID; + if (pr->key && FIOBJ_TYPE_CLASS(pr->top) == FIOBJ_T_HASH) { + hash = + FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(pr->top, pr->key); + } + if (FIOBJ_TYPE_CLASS(hash) != FIOBJ_T_HASH) { + hash = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); + fiobj_json_add2parser(pr, hash); + } else { + fiobj_free(pr->key); + pr->key = FIOBJ_INVALID; + } +#else + hash = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); + fiobj_json_add2parser(pr, hash); +#endif + pr->stack[pr->so++] = pr->top; + pr->top = hash; } - fio_unlock(&lock); + return 0; } - -static X509 *fio_tls_create_self_signed(const char *server_name) { - X509 *cert = X509_new(); - static uint32_t counter = 0; - FIO_ASSERT(cert, - "OpenSSL failed to allocate memory for self-signed certificate."); - fio___openssl_make_root_key(); - - /* serial number */ - fio_atomic_add(&counter, 1); - ASN1_INTEGER_set(X509_get_serialNumber(cert), counter); - - /* validity (180 days) */ - X509_gmtime_adj(X509_get_notBefore(cert), 0); - X509_gmtime_adj(X509_get_notAfter(cert), 15552000L); - - /* set (public) key */ - X509_set_pubkey(cert, fio___openssl_pkey); - - /* set identity details */ - X509_NAME *s = X509_get_subject_name(cert); - size_t srv_name_len = FIO_STRLEN(server_name); - FIO_ASSERT(srv_name_len < (size_t)((int)0 - 1), - "fio_tls_create_self_signed server_name too long"); - X509_NAME_add_entry_by_txt(s, - "O", - MBSTRING_ASC, - (unsigned char *)server_name, - (int)srv_name_len, - -1, - 0); - X509_NAME_add_entry_by_txt(s, - "CN", - MBSTRING_ASC, - (unsigned char *)server_name, - (int)srv_name_len, - -1, - 0); - X509_NAME_add_entry_by_txt(s, - "CA", - MBSTRING_ASC, - (unsigned char *)server_name, - (int)srv_name_len, - -1, - 0); - X509_set_issuer_name(cert, s); - - /* sign certificate */ - FIO_ASSERT(X509_sign(cert, fio___openssl_pkey, EVP_sha512()), - "OpenSSL failed to signed self-signed certificate"); - return cert; +/** a dictionary object closure detected */ +static inline void fio_json_on_end_object(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + if (pr->key) { + FIO_LOG_WARNING("(JSON parsing) malformed JSON, " + "ignoring dangling Hash key."); + fiobj_free(pr->key); + pr->key = FIOBJ_INVALID; + } + pr->top = FIOBJ_INVALID; + if (pr->so) + pr->top = pr->stack[--pr->so]; } +/** an array object was detected */ +static int fio_json_on_start_array(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + FIOBJ ary = FIOBJ_INVALID; + if (pr->target != FIOBJ_INVALID) { + if (FIOBJ_TYPE_CLASS(pr->target) != FIOBJ_T_ARRAY) + return -1; + ary = pr->target; + pr->target = FIOBJ_INVALID; + } +#if FIOBJ_JSON_APPEND + if (pr->key && FIOBJ_TYPE_CLASS(pr->top) == FIOBJ_T_HASH) { + ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(pr->top, pr->key); + } + if (FIOBJ_TYPE_CLASS(ary) != FIOBJ_T_ARRAY) { + ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + fiobj_json_add2parser(pr, ary); + } else { + fiobj_free(pr->key); + pr->key = FIOBJ_INVALID; + } +#else + FIOBJ ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); + fiobj_json_add2parser(pr, ary); +#endif -/* ***************************************************************************** -OpenSSL Context type wrappers -***************************************************************************** */ - -/* Context for all future connections */ -typedef struct { - SSL_CTX *ctx; - fio_tls_s *tls; -} fio___openssl_context_s; - -FIO_LEAK_COUNTER_DEF(fio___openssl_context_s) - -/* ***************************************************************************** -OpenSSL Callbacks -***************************************************************************** */ + pr->stack[pr->so++] = pr->top; + pr->top = ary; + return 0; +} +/** an array closure was detected */ +static inline void fio_json_on_end_array(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + pr->top = FIOBJ_INVALID; + if (pr->so) + pr->top = pr->stack[--pr->so]; +} +/** the JSON parsing is complete */ +static void fio_json_on_json(fio_json_parser_s *p) { + (void)p; /* nothing special... right? */ +} +/** the JSON parsing is complete */ +static inline void fio_json_on_error(fio_json_parser_s *p) { + fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; + fiobj_free(pr->stack[0]); + fiobj_free(pr->key); + *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID}; + FIO_LOG_DEBUG("JSON on_error callback called."); +} -FIO_SFUNC int fio___openssl_pem_password_cb(char *buf, - int size, - int rwflag, - void *u) { - const char *password = (const char *)u; - size_t password_len = FIO_STRLEN(password); - if (password_len > (size_t)size) - return -1; - FIO_MEMCPY(buf, password, password_len); - return (int)password_len; - (void)rwflag; +/** + * Updates a Hash using JSON data. + * + * Parsing errors and non-dictionary object JSON data are silently ignored, + * attempting to update the Hash as much as possible before any errors + * encountered. + * + * Conflicting Hash data is overwritten (preferring the new over the old). + * + * Returns the number of bytes consumed. On Error, 0 is returned and no data is + * consumed. + */ +SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), + update_json)(FIOBJ hash, fio_str_info_s str) { + if (hash == FIOBJ_INVALID) + return 0; + fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash}; + size_t consumed = fio_json_parse(&p.p, str.buf, str.len); + fiobj_free(p.key); + if (p.top != hash) + fiobj_free(p.top); + return consumed; } -FIO_SFUNC int fio___openssl_alpn_selector_cb(SSL *ssl, - const unsigned char **out, - unsigned char *outlen, - const unsigned char *in, - unsigned int inlen, - void *tls_) { - fio_s *io = (fio_s *)SSL_get_ex_data(ssl, 0); - fio___openssl_context_s *ctx = (fio___openssl_context_s *)tls_; +/** Returns a JSON valid FIOBJ String, representing the object. */ +SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed_p) { + fiobj_json_parser_s p = {.top = FIOBJ_INVALID}; + register const size_t consumed = fio_json_parse(&p.p, str.buf, str.len); + if (consumed_p) { + *consumed_p = consumed; + } + if (!consumed || p.p.depth) { + if (p.top) { + FIO_LOG_DEBUG("WARNING - JSON failed secondary validation, no on_error"); + } +#ifdef DEBUG + FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, p.top, 0); + FIO_LOG_DEBUG("JSON data being deleted:\n%s", + FIO_NAME2(fiobj, cstr)(s).buf); + fiobj_free(s); +#endif + fiobj_free(p.stack[0]); + p.top = FIOBJ_INVALID; + } + fiobj_free(p.key); + return p.top; +} +#endif - const unsigned char *end = in + inlen; - char buf[256]; +/** Uses JSON (JavaScript) notation to find data in an object structure. Returns + * a temporary object. */ +SFUNC FIOBJ fiobj_json_find(FIOBJ o, fio_str_info_s n) { for (;;) { - uint8_t len = in[0]; - FIO_MEMCPY(buf, in + 1, len); - buf[len] = 0; - if (fio_tls_alpn_select(ctx->tls, buf, (size_t)len, io)) { - in += len + 1; - if (in < end) + top: + if (!n.len || (n.len == 1 && n.buf[0] == '.')) + return o; + switch (FIOBJ_TYPE_CLASS(o)) { + case FIOBJ_T_ARRAY: { + if (n.len <= 2 || n.buf[0] != '[' || n.buf[1] < '0' || n.buf[1] > '9') + return FIOBJ_INVALID; + size_t i = 0; + ++n.buf; + --n.len; + while (n.len && fio_c2i(n.buf[0]) < 10) { + i = (i * 10) + fio_c2i(n.buf[0]); + ++n.buf; + --n.len; + } + if (!n.len || n.buf[0] != ']' || i > 0xFFFFFFFFU) + return FIOBJ_INVALID; + o = fiobj_array_get(o, (uint32_t)i); + ++n.buf; + --n.len; + if (n.len) { + if (n.buf[0] == '.') { + ++n.buf; + --n.len; + } else if (n.buf[0] != '[') { + return FIOBJ_INVALID; + } continue; - FIO_LOG_DDEBUG2("(%d) ALPN Failed! No protocol name match for %p", - (int)fio_thread_getpid(), - io); - return SSL_TLSEXT_ERR_ALERT_FATAL; + } + return o; + } + case FIOBJ_T_HASH: { + FIOBJ tmp = fiobj_hash_get2(o, n.buf, n.len); + if (tmp != FIOBJ_INVALID) + return tmp; + char *end = n.buf + n.len - 1; + while (end > n.buf) { + while (end > n.buf && end[0] != '.' && end[0] != '[') + --end; + if (end == n.buf) + return FIOBJ_INVALID; + const size_t t_len = end - n.buf; + tmp = fiobj_hash_get2(o, n.buf, t_len); + if (tmp != FIOBJ_INVALID) { + o = tmp; + n.len -= t_len + (end[0] == '.'); + n.buf = end + (end[0] == '.'); + goto top; + } + --end; + } + } /* fall through */ + default: return FIOBJ_INVALID; } - *out = in + 1; - *outlen = len; - FIO_LOG_DDEBUG2("(%d) TLS ALPN set to: %s for %p", - (int)fio_thread_getpid(), - buf, - io); - return SSL_TLSEXT_ERR_OK; - (void)tls_; } } - /* ***************************************************************************** -Public Context Builder +FIOBJ cleanup ***************************************************************************** */ -FIO_SFUNC int fio___openssl_each_cert(struct fio_tls_each_s *e, - const char *server_name, - const char *public_cert_file, - const char *private_key_file, - const char *pk_password) { - fio___openssl_context_s *s = (fio___openssl_context_s *)e->udata; - if (public_cert_file && private_key_file) { /* load certificate */ - SSL_CTX_set_default_passwd_cb(s->ctx, fio___openssl_pem_password_cb); - SSL_CTX_set_default_passwd_cb_userdata(s->ctx, (void *)pk_password); - FIO_LOG_DDEBUG2("loading TLS certificates: %s & %s", - public_cert_file, - private_key_file); - /* Set the key and cert */ - if (SSL_CTX_use_certificate_chain_file(s->ctx, public_cert_file) <= 0) { - ERR_print_errors_fp(stderr); - FIO_ASSERT(0, - "OpenSSL couldn't open PEM file for certificate: %s", - public_cert_file); - } - - if (SSL_CTX_use_PrivateKey_file(s->ctx, - private_key_file, - SSL_FILETYPE_PEM) <= 0) { - ERR_print_errors_fp(stderr); - FIO_ASSERT(0, - "OpenSSL couldn't open PEM file for private key: %s", - private_key_file); - } - SSL_CTX_set_default_passwd_cb(s->ctx, NULL); - SSL_CTX_set_default_passwd_cb_userdata(s->ctx, NULL); - } else { /* self signed */ - if (!server_name || !strlen(server_name)) - server_name = (const char *)"localhost"; - X509 *cert = fio_tls_create_self_signed(server_name); - SSL_CTX_use_certificate(s->ctx, cert); - SSL_CTX_use_PrivateKey(s->ctx, fio___openssl_pkey); - } - return 0; -} +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIOBJ_EXTERN_OBJ +#undef FIOBJ_EXTERN_OBJ_IMP +#undef FIO_FIOBJ +#endif /* FIO_FIOBJ */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -FIO_SFUNC int fio___openssl_each_alpn(struct fio_tls_each_s *e, - const char *protocol_name, - void (*on_selected)(fio_s *)) { - fio_str_info_s *str = (fio_str_info_s *)e->udata2; - size_t len = FIO_STRLEN(protocol_name); - if (len > 255 || (len + str->len >= str->capa)) { - FIO_LOG_ERROR("ALPN protocol name/list overflow."); - return -1; - } - str->buf[str->len++] = (len & 0xFF); - FIO_MEMCPY(str->buf + str->len, protocol_name, len); - str->len += len; - return 0; - (void)on_selected; -} -FIO_SFUNC int fio___openssl_each_trust(struct fio_tls_each_s *e, - const char *public_cert_file) { - X509_STORE *store = (X509_STORE *)e->udata2; - if (public_cert_file) /* trust specific certificate */ - X509_STORE_load_file(store, public_cert_file); - else { /* trust system's trust */ - const char *path = getenv(X509_get_default_cert_dir_env()); - if (!path) - path = X509_get_default_cert_dir(); - if (path) - X509_STORE_load_path(store, path); - } - return 0; -} -/** Helper that converts a `fio_tls_s` into the implementation's context. */ -FIO_SFUNC void *fio___openssl_build_context(fio_tls_s *tls, uint8_t is_client) { - fio___openssl_context_s *ctx = - (fio___openssl_context_s *)FIO_MEM_REALLOC(NULL, 0, sizeof(*ctx), 0); - FIO_ASSERT_ALLOC(ctx); - FIO_LEAK_COUNTER_ON_ALLOC(fio___openssl_context_s); - *ctx = (fio___openssl_context_s){ - .ctx = SSL_CTX_new((is_client ? TLS_client_method : TLS_server_method)()), - .tls = fio_tls_dup(tls), - }; - SSL_CTX_set_mode(ctx->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); - SSL_CTX_set_mode(ctx->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); - SSL_CTX_clear_mode(ctx->ctx, SSL_MODE_AUTO_RETRY); + IO Reactor - an Evented IO Reactor, Single-Threaded - X509_STORE *store = NULL; - if (fio_tls_trust_count(tls)) { - SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, NULL); - store = X509_STORE_new(); - SSL_CTX_set_cert_store(ctx->ctx, store); - } else { - SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, NULL); - } - if (!fio_tls_cert_count(tls) && !is_client) { - /* add self-signed certificate to context */ - X509 *cert = fio_tls_create_self_signed("localhost"); - SSL_CTX_use_certificate(ctx->ctx, cert); - SSL_CTX_use_PrivateKey(ctx->ctx, fio___openssl_pkey); - } - fio_tls_each(tls, - .udata = ctx, - .udata2 = store, - .each_cert = fio___openssl_each_cert, - .each_trust = fio___openssl_each_trust); - if (fio_tls_alpn_count(tls)) { - FIO_STR_INFO_TMP_VAR(alpn_list, 1023); - fio_tls_each(tls, - .udata = ctx, - .udata2 = &alpn_list, - .each_alpn = fio___openssl_each_alpn); - if (SSL_CTX_set_alpn_protos(ctx->ctx, - (const unsigned char *)alpn_list.buf, - (unsigned int)alpn_list.len)) { - FIO_LOG_ERROR("SSL_CTX_set_alpn_protos failed."); - } else { - SSL_CTX_set_alpn_select_cb(ctx->ctx, fio___openssl_alpn_selector_cb, ctx); - SSL_CTX_set_next_proto_select_cb( - ctx->ctx, - (int (*)(SSL *, - unsigned char **, - unsigned char *, - const unsigned char *, - unsigned int, - void *))fio___openssl_alpn_selector_cb, - (void *)ctx); - } - } - return ctx; -} -/* ***************************************************************************** -IO functions +Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_IO___H) +#define H___FIO_IO___H +/* ***************************************************************************** +IO Reactor Settings -/** Called to perform a non-blocking `read`, same as the system call. */ -FIO_SFUNC ssize_t fio___openssl_read(int fd, - void *buf, - size_t len, - void *tls_ctx) { - ssize_t r; - SSL *ssl = (SSL *)tls_ctx; - errno = 0; - if (len > INT_MAX) - len = INT_MAX; - r = SSL_read(ssl, buf, (int)len); - if (r > 0) - return r; - if (errno == EWOULDBLOCK || errno == EAGAIN) - return (ssize_t)-1; +At this point, define any MACROs and customizable settings available to the +developer. +***************************************************************************** */ - switch ((r = (ssize_t)SSL_get_error(ssl, (int)r))) { - case SSL_ERROR_SSL: /* fall through */ - case SSL_ERROR_SYSCALL: /* fall through */ - case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ - case SSL_ERROR_NONE: /* fall through */ - case SSL_ERROR_WANT_CONNECT: /* fall through */ - case SSL_ERROR_WANT_ACCEPT: /* fall through */ - case SSL_ERROR_WANT_WRITE: /* fall through */ - r = SSL_write_ex(ssl, (void *)&r, 0, (size_t *)&r); /* fall through */ - case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ - case SSL_ERROR_WANT_READ: /* fall through */ -#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ - case SSL_ERROR_WANT_ASYNC: /* fall through */ +#ifndef FIO_IO_BUFFER_PER_WRITE +/** Control the size of the on-stack buffer used for `write` events. */ +#define FIO_IO_BUFFER_PER_WRITE 65536U #endif - default: errno = EWOULDBLOCK; return (r = -1); - } - (void)fd; -} -/** Sends any unsent internal data. Returns 0 only if all data was sent. */ -FIO_SFUNC int fio___openssl_flush(int fd, void *tls_ctx) { - return 0; - (void)fd, (void)tls_ctx; -#if 0 /* no flushing necessary? */ - int r; - char buf[8] = {0}; - size_t count = 0; - SSL *ssl = (SSL *)tls_ctx; - r = SSL_write_ex(ssl, buf, 0, &count); - if (count) - return 1; - if (r > 0) - return 0; - switch ((r = SSL_get_error(ssl, r))) { - case SSL_ERROR_SSL: /* fall through */ - case SSL_ERROR_SYSCALL: /* fall through */ - case SSL_ERROR_NONE: /* fall through */ - case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ - case SSL_ERROR_WANT_CONNECT: /* fall through */ - case SSL_ERROR_WANT_ACCEPT: /* fall through */ - case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ - case SSL_ERROR_WANT_READ: /* fall through */ - SSL_read_ex(ssl, buf, 0, &count); /* fall through */ - case SSL_ERROR_WANT_WRITE: /* fall through */ -#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ - case SSL_ERROR_WANT_ASYNC: /* fall through */ -#endif - default: errno = EWOULDBLOCK; return -1; - } +#ifndef FIO_IO_THROTTLE_LIMIT +/** IO will be throttled (no `on_data` events) if outgoing buffer is large. */ +#define FIO_IO_THROTTLE_LIMIT 2097152U #endif -} -/** Called to perform a non-blocking `write`, same as the system call. */ -FIO_SFUNC ssize_t fio___openssl_write(int fd, - const void *buf, - size_t len, - void *tls_ctx) { - ssize_t r = -1; - if (!buf || !len || !tls_ctx) - return r; - SSL *ssl = (SSL *)tls_ctx; - errno = 0; - if (len > INT_MAX) - len = INT_MAX; - r = SSL_write(ssl, buf, (int)len); - if (r > 0) - return r; - if (errno == EWOULDBLOCK || errno == EAGAIN) - return -1; +#ifndef FIO_IO_TIMEOUT_MAX +/** Controls the maximum and default timeout in milliseconds (5 minutes). */ +#define FIO_IO_TIMEOUT_MAX 300000 +#endif - switch ((r = (ssize_t)SSL_get_error(ssl, (int)r))) { - case SSL_ERROR_SSL: /* fall through */ - case SSL_ERROR_SYSCALL: /* fall through */ - case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ - case SSL_ERROR_NONE: /* fall through */ - case SSL_ERROR_WANT_CONNECT: /* fall through */ - case SSL_ERROR_WANT_ACCEPT: /* fall through */ - case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ - case SSL_ERROR_WANT_WRITE: /* fall through */ - case SSL_ERROR_WANT_READ: /* fall through */ -#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ - case SSL_ERROR_WANT_ASYNC: /* fall through */ +#ifndef FIO_IO_SHUTDOWN_TIMEOUT +/* Sets the hard timeout (in milliseconds) for the reactor's shutdown loop. */ +#define FIO_IO_SHUTDOWN_TIMEOUT 10000 #endif - default: errno = EWOULDBLOCK; return (r = -1); - } - (void)fd; -} +#ifndef FIO_IO_COUNT_STORAGE +#ifdef DEBUG +#define FIO_IO_COUNT_STORAGE 1 +#else +#define FIO_IO_COUNT_STORAGE 0 +#endif +#endif /* ***************************************************************************** -Per-Connection Builder +IO Types ***************************************************************************** */ -FIO_LEAK_COUNTER_DEF(fio___SSL) - -/** called once the IO was attached and the TLS object was set. */ -FIO_SFUNC void fio___openssl_start(fio_s *io) { - fio___openssl_context_s *ctx_parent = - (fio___openssl_context_s *)fio_tls_get(io); - FIO_ASSERT_DEBUG(ctx_parent, "OpenSSL Context missing!"); - - SSL *ssl = SSL_new(ctx_parent->ctx); - FIO_LEAK_COUNTER_ON_ALLOC(fio___SSL); - fio_tls_set(io, (void *)ssl); +/** The main protocol object type. See `struct fio_io_protocol_s`. */ +typedef struct fio_io_protocol_s fio_io_protocol_s; - /* attach socket */ - FIO_LOG_DDEBUG2("(%d) allocated new TLS context for %p.", - (int)fio_thread_getpid(), - (void *)io); - BIO *bio = BIO_new_socket(fio_fd_get(io), 0); - SSL_set_bio(ssl, bio, bio); - SSL_set_ex_data(ssl, 0, (void *)io); - if (SSL_is_server(ssl)) - SSL_accept(ssl); - else - SSL_connect(ssl); -} +/** The IO functions used by the protocol object. */ +typedef struct fio_io_functions_s fio_io_functions_s; -/* ***************************************************************************** -Closing Connections -***************************************************************************** */ +/** The main IO object type. Should be treated as an opaque pointer. */ +typedef struct fio_io_s fio_io_s; -/** Decreases a fio_tls_s object's reference count, or frees the object. */ -FIO_SFUNC void fio___openssl_finish(int fd, void *tls_ctx) { - SSL *ssl = (SSL *)tls_ctx; - SSL_shutdown(ssl); - (void)fd; -} +/** An opaque type used for the SSL/TLS helper functions. */ +typedef struct fio_io_tls_s fio_io_tls_s; -/* ***************************************************************************** -Per-Connection Cleanup -***************************************************************************** */ +/** Message structure, as received by the `on_message` subscription callback. */ +typedef struct fio_msg_s fio_msg_s; -/** Decreases a fio_tls_s object's reference count, or frees the object. */ -FIO_SFUNC void fio___openssl_cleanup(void *tls_ctx) { - SSL *ssl = (SSL *)tls_ctx; - SSL_shutdown(ssl); - FIO_LEAK_COUNTER_ON_FREE(fio___SSL); - SSL_free(ssl); -} +/** The IO Async Queue type. */ +typedef struct fio_io_async_s fio_io_async_s; /* ***************************************************************************** -Context Cleanup +Starting / Stopping the IO Reactor ***************************************************************************** */ -static void fio___openssl_free_context_task(void *tls_ctx, void *ignr_) { - fio___openssl_context_s *ctx = (fio___openssl_context_s *)tls_ctx; - FIO_LEAK_COUNTER_ON_FREE(fio___openssl_context_s); - SSL_CTX_free(ctx->ctx); - fio_tls_free(ctx->tls); - FIO_MEM_FREE(ctx, sizeof(*ctx)); - (void)ignr_; -} - -/** Builds a local TLS context out of the fio_tls_s object. */ -static void fio___openssl_free_context(void *tls_ctx) { - fio_srv_defer(fio___openssl_free_context_task, tls_ctx, NULL); -} -/* ***************************************************************************** -IO Functions Structure -***************************************************************************** */ +/** Stopping the IO reactor. */ +SFUNC void fio_io_stop(void); -/* Returns the OpenSSL IO functions (implementation) */ -SFUNC fio_io_functions_s fio_openssl_io_functions(void) { - return (fio_io_functions_s){ - .build_context = fio___openssl_build_context, - .free_context = fio___openssl_free_context, - .start = fio___openssl_start, - .read = fio___openssl_read, - .write = fio___openssl_write, - .flush = fio___openssl_flush, - .cleanup = fio___openssl_cleanup, - }; -} +/** Adds `workers` amount of workers to the root IO reactor process. */ +SFUNC void fio_io_add_workers(int workers); -/* Setup OpenSSL as TLS IO default */ -FIO_CONSTRUCTOR(fio___openssl_setup_default) { - static fio_io_functions_s FIO___OPENSSL_IO_FUNCS = { - .build_context = fio___openssl_build_context, - .free_context = fio___openssl_free_context, - .start = fio___openssl_start, - .read = fio___openssl_read, - .write = fio___openssl_write, - .flush = fio___openssl_flush, - .cleanup = fio___openssl_cleanup, - }; - fio_tls_default_io_functions(&FIO___OPENSSL_IO_FUNCS); -#ifdef SIGPIPE - fio_signal_monitor(SIGPIPE, NULL, NULL); /* avoid OpenSSL issue... */ -#endif -} +/** Starts the IO reactor, using optional `workers` processes. Will BLOCK! */ +SFUNC void fio_io_start(int workers); /* ***************************************************************************** -OpenSSL Helpers Cleanup +The IO Reactor's State ***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#endif /* HAVE_OPENSSL */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_PUBSUB /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** - - +/** Returns true if IO reactor running and 0 if stopped or shutting down. */ +SFUNC int fio_io_is_running(void); +/** Returns true if the current process is the IO reactor's master process. */ +SFUNC int fio_io_is_master(void); - Pub/Sub Services for IPC / Server applications +/** Returns true if the current process is an IO reactor's worker process. */ +SFUNC int fio_io_is_worker(void); +/** Returns the number or workers the IO reactor will actually run. */ +SFUNC uint16_t fio_io_workers(int workers_requested); +/** Returns current process id. */ +SFUNC int fio_io_pid(void); +/** Returns the root / master process id. */ +SFUNC int fio_io_root_pid(void); -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_PUBSUB) && !defined(H___FIO_PUBSUB___H) && \ - !defined(FIO___RECURSIVE_INCLUDE) -#define H___FIO_PUBSUB___H +/** Returns the last millisecond when the IO reactor polled for events. */ +SFUNC int64_t fio_io_last_tick(void); /* ***************************************************************************** -Pub/Sub - message format +Listening to Incoming Connections ***************************************************************************** */ -/** Message structure, as received by the `on_message` subscription callback. */ -struct fio_msg_s { - /** A connection (if any) to which the subscription belongs. */ - fio_s *io; - /** The `udata` argument associated with the subscription. */ - void *udata; - /** Message ID. */ - uint64_t id; - /** Milliseconds since epoch. */ - uint64_t published; - /** - * A channel name, allowing for pub/sub patterns. - * - * NOTE: this is a shared copy - do NOT mutate the channel name string. - */ - fio_buf_info_s channel; +/** Arguments for the fio_io_listen function */ +typedef struct fio_io_listen_args { /** - * The actual message. + * The binding address in URL format. Defaults to: tcp://0.0.0.0:3000 * - * NOTE: this is a shared copy - do NOT mutate the message payload string. - **/ - fio_buf_info_s message; - /** Channel name namespace. Negative values are reserved. */ - int16_t filter; - /** flag indicating if the message is JSON data or binary/text. */ - uint8_t is_json; -}; - -/* ***************************************************************************** -Pub/Sub - Subscribe / Unsubscribe -***************************************************************************** */ - -/** Possible arguments for the fio_subscribe method. */ -typedef struct { - /** - * The subscription owner - if none, the subscription is owned by the system. + * Note: `.url` accept an optional query for building a TLS context. * - * Note: + * Possible query values include: * - * Both the system and the `io` objects each manage channel listing - * which allows only a single subscription to the same channel. + * - `tls` or `ssl` (no value): sets TLS as active, possibly self-signed. + * - `tls=` or `ssl=`: value is a prefix for "key.pem" and "cert.pem". + * - `key=` and `cert=`: file paths for ".pem" files. * - * This means a single subscription per channel per IO and a single - * subscription per channel for the global system unless managing the - * subscription handle manually. - */ - fio_s *io; - /** - * A named `channel` to which the message was sent. + * i.e.: * - * Subscriptions require a match by both channel name and namespace filter. - */ - fio_buf_info_s channel; - /** - * The callback to be called for each message forwarded to the subscription. + * fio_io_listen(.url = "0.0.0.0:3000/?tls", ...); + * fio_io_listen(.url = "0.0.0.0:3000/?tls=./", ...); + * // same as: + * fio_io_listen(.url = "0.0.0.0:3000/" + * "?key=./key.pem" + * "&cert=./cert.pem", ...); */ - void (*on_message)(fio_msg_s *msg); - /** An optional callback for when a subscription is canceled. */ - void (*on_unsubscribe)(void *udata); - /** The opaque udata value is ignored and made available to the callbacks. */ + const char *url; + /** The `fio_io_protocol_s` that will be assigned to incoming + * connections. */ + fio_io_protocol_s *protocol; + /** The default `udata` set for (new) incoming connections. */ void *udata; - /** Replay cached messages (if any) since supplied time in milliseconds. */ - uint64_t replay_since; - /** - * OPTIONAL: subscription handle return value - should be NULL when using - * automatic memory management with the IO or global environment. - * - * When set, the `io` pointer will be ignored and the subscription object - * handle will be written to the `subscription_handle_ptr` which MUST be - * used when unsubscribing. - * - * NOTE: this could cause subscriptions and memory leaks unless properly - * handled. - */ - uintptr_t *subscription_handle_ptr; + /** TLS object used for incoming connections (ownership moved to listener). */ + fio_io_tls_s *tls; /** - * A numerical namespace `filter` subscribers need to match. - * - * Negative values are reserved for facil.io framework extensions. + * Called when the a listening socket starts to listen. * - * Filer channels are bound to the processes and workers, they are NOT - * forwarded to engines and can be used for inter process communication (IPC). - */ - int16_t filter; - /** If set, pattern matching will be used (name is a pattern). */ - uint8_t is_pattern; - /** If set, subscription will be limited to the root / master process. */ - uint8_t master_only; -} fio_subscribe_args_s; - -/** - * Subscribes to a channel / filter pair. - * - * The on_unsubscribe callback will be called on failure. - */ -SFUNC void fio_subscribe(fio_subscribe_args_s args); + * May be called multiple times (i.e., if the IO reactor stops and restarts). + */ + void (*on_start)(fio_io_protocol_s *protocol, void *udata); + /** + * Called during listener cleanup. + * + * This will be called separately for every process before exiting. + */ + void (*on_stop)(fio_io_protocol_s *protocol, void *udata); + /** + * Selects a queue that will be used to schedule a pre-accept task. + * May be used to test user thread stress levels before accepting connections. + */ + fio_io_async_s *queue_for_accept; + /** When forking the IO reactor - limits `listen` to the root process. */ + uint8_t on_root; + /** Hides "started/stopped listening" messages from log (if set). */ + uint8_t hide_from_log; +} fio_io_listen_args; /** - * Subscribes to a channel / filter pair. + * Sets up a network service on a listening socket. * - * See `fio_subscribe_args_s` for details. + * Returns a self-destructible listener handle on success or NULL on error. */ -#define fio_subscribe(...) fio_subscribe((fio_subscribe_args_s){__VA_ARGS__}) +SFUNC void *fio_io_listen(fio_io_listen_args args); +#define fio_io_listen(...) fio_io_listen((fio_io_listen_args){__VA_ARGS__}) -/** - * Cancels an existing subscriptions. - * - * Accepts the same arguments as `fio_subscribe`, except the `udata` and - * callback details are ignored (no need to provide `udata` or callback - * details). - * - * If a `subscription_handle_ptr` was provided it should contain the value of - * the subscription handle returned. - * - * Returns -1 if the subscription could not be found. Otherwise returns 0. - */ -SFUNC int fio_unsubscribe(fio_subscribe_args_s args); +/** Notifies a listener to stop listening. */ +SFUNC void fio_io_listen_stop(void *listener); -/** - * Cancels an existing subscriptions. - * - * Accepts the same arguments as `fio_subscribe`, except the `udata` and - * callback details are ignored (no need to provide `udata` or callback - * details). - * - * Returns -1 if the subscription could not be found. Otherwise returns 0. - */ -#define fio_unsubscribe(...) \ - fio_unsubscribe((fio_subscribe_args_s){__VA_ARGS__}) +/** Returns the URL on which the listener is listening. */ +SFUNC fio_buf_info_s fio_io_listener_url(void *listener); -/* A callback for IO subscriptions - sends raw message data. */ -FIO_SFUNC void FIO_ON_MESSAGE_SEND_MESSAGE(fio_msg_s *msg); +/** Returns true if the listener protocol has an attached TLS context. */ +SFUNC int fio_io_listener_is_tls(void *listener); /* ***************************************************************************** -Pub/Sub - Publish +Connecting as a Client ***************************************************************************** */ -/** A pub/sub engine data structure. See details later on. */ -typedef struct fio_pubsub_engine_s fio_pubsub_engine_s; +/** Named arguments for fio_io_connect */ +typedef struct { + /** The URL to connect to (may contain TLS hints in query / `tls` scheme). */ + const char *url; + /** Connection protocol (once connection established). */ + fio_io_protocol_s *protocol; + /** Called in case of a failed connection, use for cleanup. */ + void (*on_failed)(fio_io_protocol_s *protocol, void *udata); + /** Opaque user data (set only once connection was established). */ + void *udata; + /** TLS builder object for TLS connections. */ + fio_io_tls_s *tls; + /** Connection timeout in milliseconds (defaults to 30 seconds). */ + uint32_t timeout; +} fio_io_connect_args_s; -/** Publishing and on_message callback arguments. */ -typedef struct fio_publish_args_s { - /** The pub/sub engine that should be used to forward this message. */ - fio_pubsub_engine_s const *engine; - /** If `from` is specified, it will be skipped (won't receive message) - * UNLESS a non-native `engine` is specified. */ - fio_s *from; - /** Message ID (if missing, a random ID will be generated). */ - uint64_t id; - /** Milliseconds since epoch (if missing, defaults to "now"). */ - uint64_t published; - /** The target named channel. */ - fio_buf_info_s channel; - /** The message body / content. */ - fio_buf_info_s message; - /** A numeral namespace for channel names. Negative values are reserved. */ - int16_t filter; - /** A flag indicating if the message is JSON data or not. */ - uint8_t is_json; -} fio_publish_args_s; +/** Connects to a specific URL, returning the `fio_io_s` IO object or `NULL`. */ +SFUNC fio_io_s *fio_io_connect(fio_io_connect_args_s args); + +#define fio_io_connect(url_, ...) \ + fio_io_connect((fio_io_connect_args_s){.url = url_, __VA_ARGS__}) + +/* ***************************************************************************** +IO Operations +***************************************************************************** */ /** - * Publishes a message to the relevant subscribers (if any). - * - * By default the message is sent using the `FIO_PUBSUB_DEFAULT` engine (set by - * default to `FIO_PUBSUB_LOCAL` which publishes to all processes, including the - * calling process). - * - * To limit the message only to other processes (exclude the calling process), - * use the `FIO_PUBSUB_SIBLINGS` engine. - * - * To limit the message only to the calling process, use the - * `FIO_PUBSUB_PROCESS` engine. + * Attaches the socket in `fd` to the facio.io engine (reactor). * - * To limit the message only to the root process, use the `FIO_PUBSUB_ROOT` - * engine. - */ -SFUNC void fio_publish(fio_publish_args_s args); -/** - * Publishes a message to the relevant subscribers (if any). + * * `fd` should point to a valid socket. * - * By default the message is sent using the `FIO_PUBSUB_DEFAULT` engine (set by - * default to `FIO_PUBSUB_LOCAL` which publishes to all processes, including the - * calling process). + * * `protocol` may be the existing protocol or NULL (for partial hijack). * - * To limit the message only to other processes (exclude the calling process), - * use the `FIO_PUBSUB_SIBLINGS` engine. + * * `udata` is opaque user data and may be any value, including NULL. * - * To limit the message only to the calling process, use the - * `FIO_PUBSUB_PROCESS` engine. + * * `tls` is a context for Transport Layer (Security) and can be used to + * redirect read/write operations, as set by the protocol. * - * To limit the message only to the root process, use the `FIO_PUBSUB_ROOT` - * engine. + * Returns NULL on error. the `fio_io_s` pointer must NOT be used except + * within proper callbacks. */ -#define fio_publish(...) fio_publish((fio_publish_args_s){__VA_ARGS__}) +SFUNC fio_io_s *fio_io_attach_fd(int fd, + fio_io_protocol_s *protocol, + void *udata, + void *tls); + +/** Sets a new protocol object. `NULL` is a valid "only-write" protocol. */ +SFUNC fio_io_protocol_s *fio_io_protocol_set(fio_io_s *io, + fio_io_protocol_s *protocol); /** - * Defers the current callback, so it will be called again for the message. + * Returns a pointer to the current protocol object. * - * After calling this function, the `msg` object must NOT be accessed again. + * If `protocol` wasn't properly set, the pointer might be NULL or invalid. + * + * If `protocol` wasn't attached yet, may return the previous protocol. */ -SFUNC void fio_pubsub_message_defer(fio_msg_s *msg); +IFUNC fio_io_protocol_s *fio_io_protocol(fio_io_s *io); -/* ***************************************************************************** -Pub/Sub - History and Event Replay - TODO!!! -***************************************************************************** */ +/** Returns the a pointer to the memory buffer required by the protocol. */ +IFUNC void *fio_io_buffer(fio_io_s *io); -/** Sets the maximum number of messages to be stored in the history store. */ -// SFUNC void fio_pubsub_store_limit(size_t messages); +/** Returns the length of the `buf` buffer. */ +IFUNC size_t fio_io_buffer_len(fio_io_s *io); -/* ***************************************************************************** -Pub/Sub - defaults and builtin pub/sub engines -***************************************************************************** */ +/** Associates a new `udata` pointer with the IO, returning the old `udata` */ +IFUNC void *fio_io_udata_set(fio_io_s *io, void *udata); -/** Flag bits for internal usage (message exchange network format). */ -typedef enum { - /* pub/sub messages */ - FIO___PUBSUB_JSON = 1, - FIO___PUBSUB_PROCESS = 2, - FIO___PUBSUB_ROOT = 4, - FIO___PUBSUB_SIBLINGS = 8, - FIO___PUBSUB_WORKERS = (8 | 2), - FIO___PUBSUB_LOCAL = (8 | 4 | 2), - FIO___PUBSUB_REMOTE = 16, - FIO___PUBSUB_CLUSTER = (16 | 8 | 4 | 2), - FIO___PUBSUB_REPLAY = 32, /* history replay message */ +/** Returns the `udata` pointer associated with the IO. */ +IFUNC void *fio_io_udata(fio_io_s *io); - /* internal messages */ - FIO___PUBSUB_SPECIAL = 128, - FIO___PUBSUB_SUB = (128 | 1), - FIO___PUBSUB_UNSUB = (128 | 2), - FIO___PUBSUB_IDENTIFY = (128 | 4), /* identify remote connection */ - FIO___PUBSUB_FORWARDER = (128 | 8), /* forward to external engine */ - FIO___PUBSUB_PING = (128 | 16), +/** Associates a new `tls` pointer with the IO, returning the old `tls` */ +IFUNC void *fio_io_tls_set(fio_io_s *io, void *tls); - FIO___PUBSUB_HISTORY_START = (128 | 32), - FIO___PUBSUB_HISTORY_END = (128 | 64), -} fio___pubsub_msg_flags_e; +/** Returns the `tls` pointer associated with the IO. */ +IFUNC void *fio_io_tls(fio_io_s *io); -/** Used to publish the message exclusively to the root / master process. */ -#define FIO_PUBSUB_ROOT ((fio_pubsub_engine_s *)FIO___PUBSUB_ROOT) -/** Used to publish the message only within the current process. */ -#define FIO_PUBSUB_PROCESS ((fio_pubsub_engine_s *)FIO___PUBSUB_PROCESS) -/** Used to publish the message except within the current process. */ -#define FIO_PUBSUB_SIBLINGS ((fio_pubsub_engine_s *)FIO___PUBSUB_SIBLINGS) -/** Used to publish the message for this process, its siblings and root. */ -#define FIO_PUBSUB_LOCAL ((fio_pubsub_engine_s *)FIO___PUBSUB_LOCAL) -/** Used to publish the message to any possible publishers. */ -#define FIO_PUBSUB_CLUSTER ((fio_pubsub_engine_s *)FIO___PUBSUB_CLUSTER) +/** Returns the socket file descriptor (fd) associated with the IO. */ +IFUNC int fio_io_fd(fio_io_s *io); -#if defined(FIO_EXTERN) /* static definitions can't be easily repeated. */ -/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ -SFUNC const fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT; +/** Resets a socket's timeout counter. */ +SFUNC void fio_io_touch(fio_io_s *io); /** - * The pattern matching callback used for pattern matching. - * - * Returns 1 on a match or 0 if the string does not match the pattern. + * Reads data to the buffer, if any data exists. Returns the number of bytes + * read. * - * By default, the value is set to `fio_glob_match` (see facil.io's C STL). + * NOTE: zero (`0`) is a valid return value meaning no data was available. */ -SFUNC uint8_t (*FIO_PUBSUB_PATTERN_MATCH)(fio_str_info_s pattern, - fio_str_info_s channel); -#endif +SFUNC size_t fio_io_read(fio_io_s *io, void *buf, size_t len); -/* ***************************************************************************** -Message metadata (advance usage API) -***************************************************************************** */ +typedef struct { + /** The buffer with the data to send (if no file descriptor) */ + void *buf; + /** The file descriptor to send (if no buffer) */ + intptr_t fd; + /** The length of the data to be sent. On files, 0 = the whole file. */ + size_t len; + /** The length of the data to be sent. On files, 0 = the whole file. */ + size_t offset; + /** + * If this is a buffer, the de-allocation function used to free it. + * + * If NULL, the buffer will NOT be de-allocated. + */ + void (*dealloc)(void *); + /** If non-zero, makes a copy of the buffer or keeps a file open. */ + uint8_t copy; +} fio_io_write_args_s; /** - * The number of different metadata callbacks that can be attached. - * - * Effects performance. - * - * The default value should be enough for the following metadata objects: - * - WebSocket server headers. - * - WebSocket client (header + masked message copy). - * - EventSource (SSE) encoded named channel and message. + * Writes data to the outgoing buffer and schedules the buffer to be sent. */ -#ifndef FIO___PUBSUB_METADATA_STORE_LIMIT -#define FIO___PUBSUB_METADATA_STORE_LIMIT 4 -#endif +SFUNC void fio_io_write2(fio_io_s *io, fio_io_write_args_s args); +#define fio_io_write2(io, ...) \ + fio_io_write2(io, (fio_io_write_args_s){__VA_ARGS__}) -/** Pub/Sub Metadata callback type. */ -typedef void *(*fio_msg_metadata_fn)(fio_msg_s *); +/** Helper macro for a common fio_io_write2 (copies the buffer). */ +#define fio_io_write(io, buf_, len_) \ + fio_io_write2(io, .buf = (buf_), .len = (len_), .copy = 1) /** - * It's possible to attach metadata to facil.io pub/sub messages before they are - * published. - * - * This allows, for example, messages to be encoded as network packets for - * outgoing protocols (i.e., encoding for WebSocket transmissions), improving - * performance in large network based broadcasting. + * Sends data from a file as if it were a single atomic packet (sends up to + * length bytes or until EOF is reached). * - * Up to `FIO___PUBSUB_METADATA_STORE_LIMIT` metadata callbacks can be attached. + * Once the file was sent, the `source_fd` will be closed using `close`. * - * The callback should return a `void *` pointer. + * The file will be buffered to the socket chunk by chunk, so that memory + * consumption is capped. * - * To remove a callback, call `fio_message_metadata_remove` with the returned - * value. + * `offset` dictates the starting point for the data to be sent and length sets + * the maximum amount of data to be sent. * - * The cluster messaging system allows some messages to be flagged as JSON and - * this flag is available to the metadata callback. + * Closes the file on error. + */ +#define fio_io_sendfile(io, source_fd, offset_, bytes) \ + fio_io_write2((io), \ + .fd = (source_fd), \ + .offset = (size_t)(offset_), \ + .len = (bytes)) + +/** Marks the IO for closure as soon as scheduled data was sent. */ +SFUNC void fio_io_close(fio_io_s *io); + +/** Marks the IO for immediate closure. */ +SFUNC void fio_io_close_now(fio_io_s *io); + +/** + * Increases a IO's reference count, so it won't be automatically destroyed + * when all tasks have completed. * - * Returns zero (0) on success or -1 on failure. + * Use this function in order to use the IO outside of a scheduled task. * - * Multiple `fio_message_metadata_add` calls increase a reference count and - * should be matched by the same number of `fio_message_metadata_remove`. + * This function is thread-safe. */ -SFUNC int fio_message_metadata_add(fio_msg_metadata_fn metadata_func, - void (*cleanup)(void *)); +SFUNC fio_io_s *fio_io_dup(fio_io_s *io); /** - * Removed the metadata callback. + * Decreases a IO's reference count, so it could be automatically destroyed + * when all other tasks have completed. * - * Removal might be delayed if live metatdata exists. + * Use this function once finished with a IO that was `dup`-ed. + * + * This function is thread-safe. */ -SFUNC void fio_message_metadata_remove(fio_msg_metadata_fn metadata_func); +SFUNC void fio_io_free(fio_io_s *io); -/** Finds the message's metadata, returning the data or NULL. */ -SFUNC void *fio_message_metadata(fio_msg_s *msg, - fio_msg_metadata_fn metadata_func); +/** Suspends future `on_data` events for the IO. */ +SFUNC void fio_io_suspend(fio_io_s *io); + +/** Listens for future `on_data` events related to the IO. */ +SFUNC void fio_io_unsuspend(fio_io_s *io); + +/** Returns 1 if the IO handle was suspended. */ +SFUNC int fio_io_is_suspended(fio_io_s *io); + +/** Returns 1 if the IO handle is marked as open. */ +SFUNC int fio_io_is_open(fio_io_s *io); + +/** Returns the approximate number of bytes in the outgoing buffer. */ +SFUNC size_t fio_io_backlog(fio_io_s *io); /* ***************************************************************************** -Pub/Sub Middleware and Extensions ("Engines") +Task Scheduling ***************************************************************************** */ +/** Schedules a task for delayed execution. This function is thread-safe. */ +SFUNC void fio_io_defer(void (*task)(void *, void *), + void *udata1, + void *udata2); + +/** Schedules a timer bound task, see `fio_timer_schedule`. */ +SFUNC void fio_io_run_every(fio_timer_schedule_args_s args); /** - * facil.io can be linked with external Pub/Sub services using "engines". - * - * Engines MUST provide the listed function pointers and should be attached - * using the `fio_pubsub_attach` function. + * Schedules a timer bound task, see `fio_timer_schedule`. * - * Engines that were connected / attached using `fio_pubsub_attach` MUST - * disconnect / detach, before being destroyed, by using the `fio_pubsub_detach` - * function. + * Possible "named arguments" (fio_timer_schedule_args_s members) include: * - * When an engine received a message to publish, it should call the - * `fio_publish` function with the engine to which the message is forwarded. - * i.e.: + * * The timer function. If it returns a non-zero value, the timer stops: + * int (*fn)(void *, void *) + * * Opaque user data: + * void *udata1 + * * Opaque user data: + * void *udata2 + * * Called when the timer is done (finished): + * void (*on_stop)(void *, void *) + * * Timer interval, in milliseconds: + * uint32_t every + * * The number of times the timer should be performed. -1 == infinity: + * int32_t repetitions + */ +#define fio_io_run_every(...) \ + fio_io_run_every((fio_timer_schedule_args_s){__VA_ARGS__}) + +/** Returns a pointer for the IO reactor's queue. */ +SFUNC fio_queue_s *fio_io_queue(void); + +/**************************************************************************/ /** +Protocol IO Functions +============ + +The Protocol struct uses IO callbacks to allow an easy way to override the +system's IO functions. + +This defines Transport Layer callbacks that facil.io will treat as non-blocking +system calls and allows any protocol to easily add a secure (SSL/TLS) flavor if +desired. +*/ +struct fio_io_functions_s { + /** Helper that converts a `fio_io_tls_s` into the implementation's context. + */ + void *(*build_context)(fio_io_tls_s *tls, uint8_t is_client); + /** Helper to free the context built by build_context. */ + void (*free_context)(void *context); + /** called when a new IO is first attached to a valid protocol. */ + void (*start)(fio_io_s *io); + /** Called to perform a non-blocking `read`, same as the system call. */ + ssize_t (*read)(int fd, void *buf, size_t len, void *context); + /** Called to perform a non-blocking `write`, same as the system call. */ + ssize_t (*write)(int fd, const void *buf, size_t len, void *context); + /** Sends any unsent internal data. Returns 0 only if all data was sent. */ + int (*flush)(int fd, void *context); + /** Called when the IO object finished sending all data before closure. */ + void (*finish)(int fd, void *context); + /** Called after the IO object is closed, used to cleanup its `tls` object. */ + void (*cleanup)(void *context); +}; + +/**************************************************************************/ /** +The Protocol +============ + +The Protocol struct defines the callbacks used for a family of connections and +sets their behavior. The Protocol struct is part of facil.io's core design. + +Protocols are usually global objects and the same protocol can be assigned to +multiple IO handles. + +All the callbacks receive a IO handle, which is used instead of the system's +file descriptor and protects callbacks and IO operations from sending data to +incorrect clients (possible `fd` "recycling"). +*/ +struct fio_io_protocol_s { + /** + * Reserved / private data - used by facil.io internally. + * MUST be initialized to zero. + */ + struct { + /* A linked list of currently attached IOs (ordered) - do NOT alter. */ + FIO_LIST_HEAD ios; + /* A linked list of other protocols used by IO core - do NOT alter. */ + FIO_LIST_NODE protocols; + /* internal flags - do NOT alter after initial initialization to zero. */ + uintptr_t flags; + } reserved; + /** Called when an IO is attached to the protocol. */ + void (*on_attach)(fio_io_s *io); + /** Called when a data is available. */ + void (*on_data)(fio_io_s *io); + /** called once all pending `fio_write` calls are finished. */ + void (*on_ready)(fio_io_s *io); + + /** + * Called when the IO reactor is shutting down, immediately before closing the + * connection. + * + * After the `on_shutdown` callback returns, the socket is marked for closure. + * + * Once the socket was marked for closure, facil.io will allow a limited + * amount of time for data to be sent, after which the socket might be closed + * even if the client did not consume all buffered data. + */ + void (*on_shutdown)(fio_io_s *io); + /** + * Called when a connection's timeout was reached + * + * Can be set to `fio_io_touch` if timeout is irrelevant (i.e., UDP). + */ + void (*on_timeout)(fio_io_s *io); + /** Used as a default `on_message` when an IO object subscribes. */ + + /** Called after the connection was closed (once per IO). */ + void (*on_close)(void *iobuf, void *udata); + + void (*on_pubsub)(struct fio_msg_s *msg); + /** Allows user specific protocol agnostic callbacks. */ + void (*on_user1)(fio_io_s *io, void *user_data); + /** Allows user specific protocol agnostic callbacks. */ + void (*on_user2)(fio_io_s *io, void *user_data); + /** Allows user specific protocol agnostic callbacks. */ + void (*on_user3)(fio_io_s *io, void *user_data); + /** Reserved for future protocol agnostic callbacks. */ + void (*on_reserved)(fio_io_s *io, void *user_data); + /** + * Defines Transport Layer callbacks that facil.io will treat as non-blocking + * system calls. + */ + fio_io_functions_s io_functions; + /** + * The timeout value in milliseconds for all connections using this protocol. + * + * Limited to FIO_IO_TIMEOUT_MAX seconds. Zero (0) == FIO_IO_TIMEOUT_MAX + */ + uint32_t timeout; + /** The number of bytes to allocate for the fio_io_buf buffer. */ + uint32_t buffer_size; +}; + +/** Performs a task for each IO in the stated protocol. */ +SFUNC size_t fio_io_protocol_each(fio_io_protocol_s *protocol, + void (*task)(fio_io_s *, void *udata2), + void *udata2); + +/* ***************************************************************************** +Connection Object Links / Environment +***************************************************************************** */ + +/** Named arguments for the `fio_io_env_set` function. */ +typedef struct { + /** A numerical type filter. Defaults to 0. Negative values are reserved. */ + intptr_t type; + /** The name for the link. The name and type uniquely identify the object. */ + fio_buf_info_s name; + /** The object being linked to the connection. */ + void *udata; + /** A callback that will be called once the connection is closed. */ + void (*on_close)(void *data); + /** Set to true (1) if the name string's life lives as long as the `env` . */ + uint8_t const_name; +} fio_io_env_set_args_s; + +/** Named arguments for the `fio_io_env_unset` function. */ +typedef struct { + /** A numerical type filter. Should be the same as used with + * `fio_io_env_set` */ + intptr_t type; + /** The name of the object. Should be the same as used with `fio_io_env_set` + */ + fio_buf_info_s name; +} fio_io_env_get_args_s; + +/** Returns the named `udata` associated with the IO object (or `NULL`). */ +SFUNC void *fio_io_env_get(fio_io_s *io, fio_io_env_get_args_s); + +/** Returns the named `udata` associated with the IO object (or `NULL`). */ +#define fio_io_env_get(io, ...) \ + fio_io_env_get(io, (fio_io_env_get_args_s){__VA_ARGS__}) + +/** + * Links an object to a connection's lifetime / environment. * - * fio_publish( - * .engine = FIO_PUBSUB_LOCAL, - * .channel = channel_name, - * .message = msg_body); + * The `on_close` callback will be called once the connection has died. * - * IMPORTANT: The callbacks will be called by the main IO thread, so they should - * never block. Long tasks should copy the data and scheduling an external task - * (i.e., using `fio_srv_defer`). + * If the `io` is NULL, the value will be set for the global environment. */ -struct fio_pubsub_engine_s { - /** Called after the engine was detached, may be used for cleanup. */ - void (*detached)(const fio_pubsub_engine_s *eng); - /** Subscribes to a channel. Called ONLY in the Root (master) process. */ - void (*subscribe)(const fio_pubsub_engine_s *eng, - fio_buf_info_s channel, - int16_t filter); - /** Subscribes to a pattern. Called ONLY in the Root (master) process. */ - void (*psubscribe)(const fio_pubsub_engine_s *eng, - fio_buf_info_s channel, - int16_t filter); - /** Unsubscribes to a channel. Called ONLY in the Root (master) process. */ - void (*unsubscribe)(const fio_pubsub_engine_s *eng, - fio_buf_info_s channel, - int16_t filter); - /** Unsubscribe to a pattern. Called ONLY in the Root (master) process. */ - void (*punsubscribe)(const fio_pubsub_engine_s *eng, - fio_buf_info_s channel, - int16_t filter); - /** Publishes a message through the engine. Called by any worker / thread. */ - void (*publish)(const fio_pubsub_engine_s *eng, fio_msg_s *msg); -}; +SFUNC void fio_io_env_set(fio_io_s *io, fio_io_env_set_args_s); /** - * Attaches an engine, so it's callback can be called by facil.io. + * Links an object to a connection's lifetime, calling the `on_close` callback + * once the connection has died. * - * The `(p)subscribe` callback will be called for every existing channel. + * If the `io` is NULL, the value will be set for the global environment, in + * which case the `on_close` callback will only be called once the process + * exits. * - * This can be called multiple times resulting in re-running the `(p)subscribe` - * callbacks. + * This is a helper MACRO that allows the function to be called using named + * arguments. + */ +#define fio_io_env_set(io, ...) \ + fio_io_env_set(io, (fio_io_env_set_args_s){__VA_ARGS__}) + +/** + * Un-links an object from the connection's lifetime, so it's `on_close` + * callback will NOT be called. * - * NOTE: engines are automatically detached from child processes but can be - * safely used even so - messages are always forwarded to the engine attached to - * the root (master) process. + * Returns 0 on success and -1 if the object couldn't be found. + */ +SFUNC int fio_io_env_unset(fio_io_s *io, fio_io_env_get_args_s); + +/** + * Un-links an object from the connection's lifetime, so it's `on_close` + * callback will NOT be called. * - * NOTE: engines should publish events to `FIO_PUBSUB_LOCAL`. + * Returns 0 on success and -1 if the object couldn't be found. + * + * This is a helper MACRO that allows the function to be called using named + * arguments. */ -SFUNC void fio_pubsub_attach(fio_pubsub_engine_s *engine); +#define fio_io_env_unset(io, ...) \ + fio_io_env_unset(io, (fio_io_env_get_args_s){__VA_ARGS__}) -/** Schedules an engine for Detachment, so it could be safely destroyed. */ -SFUNC void fio_pubsub_detach(fio_pubsub_engine_s *engine); +/** + * Removes an object from the connection's lifetime / environment, calling it's + * `on_close` callback as if the connection was closed. + */ +SFUNC int fio_io_env_remove(fio_io_s *io, fio_io_env_get_args_s); + +/** + * Removes an object from the connection's lifetime / environment, calling it's + * `on_close` callback as if the connection was closed. + * + * This is a helper MACRO that allows the function to be called using named + * arguments. + */ +#define fio_io_env_remove(io, ...) \ + fio_io_env_remove(io, (fio_io_env_get_args_s){__VA_ARGS__}) /* ***************************************************************************** -Pub/Sub Clustering and Security +TLS Context Helper Types ***************************************************************************** */ -/** Sets the current IPC socket address (can't be changed while running). */ -SFUNC int fio_pubsub_ipc_url_set(char *str, size_t len); +/** Performs a `new` operation, returning a new `fio_io_tls_s` context. */ +SFUNC fio_io_tls_s *fio_io_tls_new(void); -/** Returns a pointer to the current IPC socket address. */ -SFUNC const char *fio_pubsub_ipc_url(void); +/** Takes a parsed URL and optional TLS target and returns a TLS if needed. */ +SFUNC fio_io_tls_s *fio_io_tls_from_url(fio_io_tls_s *target_or_null, + fio_url_s url); + +/** Performs a `dup` operation, increasing the object's reference count. */ +SFUNC fio_io_tls_s *fio_io_tls_dup(fio_io_tls_s *); + +/** Performs a `free` operation, reducing the reference count and freeing. */ +SFUNC void fio_io_tls_free(fio_io_tls_s *); + +/** + * Adds a certificate a new SSL/TLS context / settings object (SNI support). + * + * fio_io_tls_cert_add(tls, "www.example.com", + * "public_key.pem", + * "private_key.pem", NULL ); + * + * NOTE: Except for the `tls` and `server_name` arguments, all arguments might + * be `NULL`, which a context builder (`fio_io_functions_s`) should + * treat as a request for a self-signed certificate. It may be silently ignored. + */ +SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password); /** - * Sets a (possibly shared) secret for securing pub/sub communication. + * Adds an ALPN protocol callback to the SSL/TLS context. * - * If `secret` is `NULL`, the environment variable `"SECRET"` will be used or. + * The first protocol added will act as the default protocol to be selected. * - * If secret is never set, a random secret will be generated. + * A `NULL` protocol name will be silently ignored. * - * NOTE: secrets produce a SHA-512 Hash that is used to produce 256 bit keys. + * A `NULL` callback (`on_selected`) will be silently replaced with a no-op. */ -SFUNC void fio_pubsub_secret_set(char *secret, size_t len); - -/** Auto-peer detection and pub/sub multi-machine clustering using `port`. */ -SFUNC void fio_pubsub_broadcast_on_port(int16_t port); +SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *tls, + const char *protocol_name, + void (*on_selected)(fio_io_s *)); -/* ***************************************************************************** +/** Calls the `on_selected` callback for the `fio_io_tls_s` object. */ +SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *tls, + const char *protocol_name, + size_t name_length, + fio_io_s *); +/** + * Adds a certificate to the "trust" list, which automatically adds a peer + * verification requirement. + * + * If `public_cert_file` is `NULL`, implementation is expected to add the + * system's default trust registry. + * + * Note: when the `fio_io_tls_s` object is used for server connections, this + * should limit connections to clients that connect using a trusted certificate. + * + * fio_io_tls_trust_add(tls, "google-ca.pem" ); + */ +SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *, + const char *public_cert_file); +/** + * Returns the number of `fio_io_tls_cert_add` instructions. + * + * This could be used when deciding if to add a NULL instruction (self-signed). + * + * If `fio_io_tls_cert_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls); -Pub/Sub Implementation +/** + * Returns the number of registered ALPN protocol names. + * + * This could be used when deciding if protocol selection should be delegated to + * the ALPN mechanism, or whether a protocol should be immediately assigned. + * + * If no ALPN protocols are registered, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls); +/** + * Returns the number of `fio_io_tls_trust_add` instructions. + * + * This could be used when deciding if to disable peer verification or not. + * + * If `fio_io_tls_trust_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls); +/** Arguments (and info) for `fio_io_tls_each`. */ +typedef struct fio_io_tls_each_s { + fio_io_tls_s *tls; + void *udata; + void *udata2; + int (*each_cert)(struct fio_io_tls_each_s *, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password); + int (*each_alpn)(struct fio_io_tls_each_s *, + const char *protocol_name, + void (*on_selected)(fio_io_s *)); + int (*each_trust)(struct fio_io_tls_each_s *, const char *public_cert_file); +} fio_io_tls_each_s; -The implementation has a big number of interconnected modules: -- Distribution Channels (`fio_channel_s` and `FIO___PUBSUB_POSTOFFICE`) -- Subscriptions (`fio_subscription_s`) -- Metadata Management. -- External Distribution Engines (`fio_pubsub_engine_s`) -- Message format and their network exchange protocols (`fio___pubsub_message_s`) +/** Calls callbacks for certificate, trust certificate and ALPN added. */ +SFUNC int fio_io_tls_each(fio_io_tls_each_s); -Message wire format (as 64 bit numerals in little endien encoding): -[0] Message ID -[1] Publication time (milliseconds since epoch) -[2] 16 bit filter | 16 bit channel len | 24 bit message len | 8 bit flags -| --- encryption starts --- | -| X bytes - (channel name, + 1 NUL terminator) | -| Y bytes - (message data, + 1 NUL terminator) | -| --- encryption ends --- | -| 16 bytes - (optional) message MAC | -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/** `fio_io_tls_each` helper macro, see `fio_io_tls_each_s` for named + * arguments. */ +#define fio_io_tls_each(tls_, ...) \ + fio_io_tls_each(((fio_io_tls_each_s){.tls = tls_, __VA_ARGS__})) -#undef FIO___PUBSUB_MESSAGE_HEADER -#define FIO___PUBSUB_MESSAGE_HEADER 24 -/* header + 2 NUL bytes (message + channel) + 16 byte MAC */ -#undef FIO___PUBSUB_MESSAGE_OVERHEAD_NET -#define FIO___PUBSUB_MESSAGE_OVERHEAD_NET (FIO___PUBSUB_MESSAGE_HEADER + 18) -/* extra 2 NUL bytes (after message & channel name) */ -#undef FIO___PUBSUB_MESSAGE_OVERHEAD -#define FIO___PUBSUB_MESSAGE_OVERHEAD (FIO___PUBSUB_MESSAGE_OVERHEAD_NET + 2) +/** If `NULL` returns current default, otherwise sets it. */ +SFUNC fio_io_functions_s fio_io_tls_default_functions(fio_io_functions_s *); /* ***************************************************************************** -Pub/Sub - defaults and builtin pub/sub engines +IO Async Queue - Worker Threads for non-IO tasks ***************************************************************************** */ -/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ -SFUNC const fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; +/** The IO Async Queue type. */ +struct fio_io_async_s { + fio_queue_s *q; + uint32_t count; + fio_queue_s queue; + FIO_LIST_NODE node; +}; /** - * The pattern matching callback used for pattern matching. + * Initializes an IO Async Queue (multi-threaded task queue). * - * Returns 1 on a match or 0 if the string does not match the pattern. + * The queue automatically spawns threads and shuts down as the IO reactor + * starts or stops. * - * By default, the value is set to `fio_glob_match` (see facil.io's C STL). + * It is recommended that the `fio_io_async_s` be allocated as a static + * variable, as its memory must remain valid throughout the lifetime of the + * IO reactor's app. */ -SFUNC uint8_t (*FIO_PUBSUB_PATTERN_MATCH)(fio_str_info_s, - fio_str_info_s) = fio_glob_match; +#define FIO_IO_ASYN_INIT ((fio_io_async_s){0}) -/* a mock callback for subscriptions */ -FIO_SFUNC void fio___subscription_mock_cb(fio_msg_s *msg) { (void)msg; } +/** Returns the current task queue associated with the IO Async Queue. */ +FIO_IFUNC fio_queue_s *fio_io_async_queue(fio_io_async_s *q) { return q->q; } -/* A callback for IO subscriptions. */ -FIO_SFUNC void fio___subscription_call_protocol(fio_msg_s *msg) { - if (!msg->io) - return; - fio_protocol_s *p = fio_protocol_get(msg->io); - FIO_ASSERT_DEBUG(p, "every IO object should have a protocol, always"); - p->on_pubsub(msg); -} +/** + * Attaches an IO Async Queue for use in multi-threaded (non IO) tasks. + * + * This function can be called multiple times for the same (or other) queue, as + * long as the async queue (`fio_io_async_s`) was previously initialized using + * `FIO_IO_ASYN_INIT` or zeroed out. i.e.: + * + * static fio_io_async_s SLOW_HTTP_TASKS = FIO_IO_ASYN_INIT; + * fio_io_async_attach(&SLOW_HTTP_TASKS, 32); + */ +SFUNC void fio_io_async_attach(fio_io_async_s *q, uint32_t threads); -#ifndef FIO___PUBSUB_CLUSTER_BACKLOG -#define FIO___PUBSUB_CLUSTER_BACKLOG (1UL << 12) -#endif +/** Pushes a task to an IO Async Queue (macro helper). */ +#define fio_io_async(q_, ...) fio_queue_push((q_)->q, __VA_ARGS__) /* ***************************************************************************** -PostOffice Distribution types - Channel and Subscription Core Types +IO API Finish ***************************************************************************** */ +#endif /* FIO_IO */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -/** The Distribution Channel: manages subscriptions to named channels. */ -typedef struct fio_channel_s { - FIO_LIST_HEAD subscriptions; - FIO_LIST_HEAD history; - uint32_t name_len; - int16_t filter; - uint8_t is_pattern; - char name[]; -} fio_channel_s; - -/** The Channel Map: maps named channels. */ -FIO_SFUNC void fio___channel_on_create(fio_channel_s *ch); -FIO_SFUNC void fio___channel_on_destroy(fio_channel_s *ch); + IO Reactor - an Evented IO Reactor, Single-Threaded -/** - * Reference counting: `fio_channel_dup(ch)` / `fio_channel_free(ch)` - */ -#define FIO_REF_NAME fio_channel -#define FIO_REF_FLEX_TYPE char -#define FIO_REF_DESTROY(ch) fio___channel_on_destroy(&(ch)) -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_IO_TYPES___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) +#define H___FIO_IO_TYPES___H -/** The Subscription: contains subscriber data. */ -typedef struct fio_subscription_s { - FIO_LIST_NODE node; - FIO_LIST_NODE history; - FIO_LIST_NODE history_active; - uint64_t replay_since; - fio_s *io; - fio_channel_s *channel; - void (*on_message)(fio_msg_s *msg); - void (*on_unsubscribe)(void *udata); - void *udata; -} fio_subscription_s; +#define FIO___IO_GET_TIME_MILLI() fio_time2milli(fio_time_real()) -/** - * Reference counting: `fio_subscription_dup(sb)` / `fio_subscription_free(sb)` - */ -FIO_SFUNC void fio___pubsub_subscription_on_destroy(fio_subscription_s *sub); -#define FIO_REF_NAME fio_subscription -#define FIO_REF_DESTROY(obj) fio___pubsub_subscription_on_destroy(&(obj)) -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +/* ***************************************************************************** +IO environment support (`env`) +***************************************************************************** */ -/** The Message Container */ +/** An object that can be linked to any facil.io connection (fio_s). */ typedef struct { - fio_msg_s data; - void *metadata[FIO___PUBSUB_METADATA_STORE_LIMIT]; - uint8_t metadata_is_initialized; /* to compact this we need to change all? */ - char buf[]; -} fio___pubsub_message_s; - -/* returns the internal message object. */ -FIO_IFUNC fio___pubsub_message_s *fio___pubsub_msg2internal(fio_msg_s *msg); + void (*on_close)(void *data); + void *udata; +} fio___io_env_obj_s; -/** Callback called when a message is destroyed (reference counting). */ -FIO_SFUNC void fio___pubsub_message_on_destroy(fio___pubsub_message_s *m); +/* unordered `env` dictionary style map */ +#define FIO_UMAP_NAME fio___io_env +#define FIO_MAP_KEY_KSTR +#define FIO_MAP_VALUE fio___io_env_obj_s +#define FIO_MAP_VALUE_DESTROY(o) \ + do { \ + if ((o).on_close) \ + (o).on_close((o).udata); \ + } while (0) +#define FIO_MAP_DESTROY_AFTER_COPY 0 -/* Message reference counting */ -#define FIO_REF_NAME fio___pubsub_message -#define FIO_REF_DESTROY(obj) fio___pubsub_message_on_destroy(&(obj)) -#define FIO_REF_FLEX_TYPE char -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO___RECURSIVE_INCLUDE 1 +#define FIO___RECURSIVE_INCLUDE 1 #include FIO_INCLUDE_FILE #undef FIO___RECURSIVE_INCLUDE typedef struct { - size_t len; - uint64_t uuid[2]; - fio___pubsub_message_s *msg; - char buf[FIO___PUBSUB_MESSAGE_OVERHEAD_NET]; -} fio___pubsub_message_parser_s; + fio_thread_mutex_t lock; + fio___io_env_s env; +} fio___io_env_safe_s; -FIO_LEAK_COUNTER_DEF(fio___pubsub_message_parser_s) +#define FIO___IO_ENV_SAFE_INIT \ + { .lock = FIO_THREAD_MUTEX_INIT, .env = FIO_MAP_INIT } -FIO_SFUNC fio___pubsub_message_parser_s *fio___pubsub_message_parser_new(void) { - fio___pubsub_message_parser_s *p = - (fio___pubsub_message_parser_s *)FIO_MEM_REALLOC_(NULL, 0, sizeof(*p), 0); - FIO_ASSERT_ALLOC(p); - FIO_LEAK_COUNTER_ON_ALLOC(fio___pubsub_message_parser_s); - *p = (fio___pubsub_message_parser_s){0}; - return p; +FIO_IFUNC void *fio___io_env_safe_get(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_) { + void *r; + fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio_thread_mutex_lock(&e->lock); + r = fio___io_env_get(&e->env, hash, key).udata; + fio_thread_mutex_unlock(&e->lock); + return r; } -FIO_SFUNC void fio___pubsub_message_parser_free( - fio___pubsub_message_parser_s *p) { - if (!p) - return; - fio___pubsub_message_free(p->msg); - FIO_LEAK_COUNTER_ON_FREE(fio___pubsub_message_parser_s); - FIO_MEM_FREE_(p, sizeof(*p)); +FIO_IFUNC void fio___io_env_safe_set(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_, + fio___io_env_obj_s val, + uint8_t key_is_const) { + fio_str_info_s key = FIO_STR_INFO3(key_, len, !key_is_const); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio_thread_mutex_lock(&e->lock); + fio___io_env_set(&e->env, hash, key, val, NULL); + fio_thread_mutex_unlock(&e->lock); } -/* ***************************************************************************** -PostOffice Distribution types - The Distribution Channel Map -***************************************************************************** */ - -#define FIO___PUBSUB_CHANNEL_ENCODE_CAPA(filter_, is_pattern_) \ - (((size_t)(is_pattern_) << 16) | (size_t)(uint16_t)(filter_)) - -#define FIO___PUBSUB_CHANNEL2STR(ch) \ - FIO_STR_INFO3(ch->name, \ - ch->name_len, \ - FIO___PUBSUB_CHANNEL_ENCODE_CAPA(ch->filter, ch->is_pattern)) - -FIO_IFUNC int fio___channel_cmp(fio_channel_s *ch, fio_str_info_s s) { - fio_str_info_s c = FIO___PUBSUB_CHANNEL2STR(ch); - return FIO_STR_INFO_IS_EQ(c, s); +FIO_IFUNC int fio___io_env_safe_unset(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_) { + int r; + fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio___io_env_obj_s old; + fio_thread_mutex_lock(&e->lock); + r = fio___io_env_remove(&e->env, hash, key, &old); + fio_thread_mutex_unlock(&e->lock); + return r; } -FIO_IFUNC fio_channel_s *fio___channel_new_for_map(fio_str_info_s s) { - fio_channel_s *ch = fio_channel_new(s.len + 1); - FIO_ASSERT_ALLOC(ch); - *ch = (fio_channel_s){ - .subscriptions = FIO_LIST_INIT(ch->subscriptions), - .history = FIO_LIST_INIT(ch->history), - .name_len = (uint32_t)s.len, - .filter = (int16_t)(s.capa & 0xFFFFUL), - .is_pattern = (uint8_t)(s.capa >> 16), - }; - FIO_MEMCPY(ch->name, s.buf, s.len); - ch->name[s.len] = 0; - fio___channel_on_create(ch); - return ch; +FIO_IFUNC int fio___io_env_safe_remove(fio___io_env_safe_s *e, + char *key_, + size_t len, + intptr_t type_) { + int r; + fio_str_info_s key = FIO_STR_INFO3(key_, len, 0); + const uint64_t hash = fio_risky_hash(key_, len, (uint64_t)(type_)); + fio_thread_mutex_lock(&e->lock); + r = fio___io_env_remove(&e->env, hash, key, NULL); + fio_thread_mutex_unlock(&e->lock); + return r; } -#define FIO_MAP_NAME fio___channel_map -#define FIO_MAP_KEY fio_str_info_s -#define FIO_MAP_KEY_INTERNAL fio_channel_s * -#define FIO_MAP_KEY_FROM_INTERNAL(k_) FIO___PUBSUB_CHANNEL2STR(k_) -#define FIO_MAP_KEY_COPY(dest, src) ((dest) = fio___channel_new_for_map((src))) -#define FIO_MAP_KEY_CMP(a, b) fio___channel_cmp((a), (b)) -#define FIO_MAP_HASH_FN(str) fio_risky_hash(str.buf, str.len, str.capa) -#define FIO_MAP_KEY_DESTROY(key) fio_channel_free((key)) -#define FIO_MAP_KEY_DISCARD(key) -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +FIO_IFUNC void fio___io_env_safe_destroy(fio___io_env_safe_s *e) { + fio___io_env_destroy(&e->env); /* no need to lock, performed in IO thread. */ + fio_thread_mutex_destroy(&e->lock); + *e = (fio___io_env_safe_s)FIO___IO_ENV_SAFE_INIT; +} /* ***************************************************************************** -Pub/Sub Subscription destruction +Protocol Type Initialization ***************************************************************************** */ -/* calls the on_unsubscribe callback. */ -FIO_SFUNC void fio___pubsub_subscription_on_destroy__task(void *fnp, - void *udata) { - union { - void *p; - void (*fn)(void *udata); - } u = {.p = fnp}; - u.fn(udata); +static void fio___io_on_ev_mock_sus(fio_io_s *io) { fio_io_suspend(io); } +static void fio___io_on_ev_mock(fio_io_s *io) { (void)(io); } +static void fio___io_on_ev_pubsub_mock(struct fio_msg_s *msg) { (void)(msg); } +static void fio___io_on_user_mock(fio_io_s *io, void *i_) { + (void)io, (void)i_; } +static void fio___io_on_close_mock(void *p1, void *p2) { (void)p1, (void)p2; } +static void fio___io_on_ev_on_timeout(fio_io_s *io) { fio_io_close_now(io); } -FIO_SFUNC void fio___pubsub_subscription_on_destroy(fio_subscription_s *s) { - if (s->on_unsubscribe) { - union { - void *p; - void (*fn)(void *udata); - } u = {.fn = s->on_unsubscribe}; - fio_queue_push(fio_srv_queue(), - fio___pubsub_subscription_on_destroy__task, - u.p, - s->udata); - } +/* Called to perform a non-blocking `read`, same as the system call. */ +static ssize_t fio___io_func_default_read(int fd, + void *buf, + size_t len, + void *tls) { + return fio_sock_read(fd, buf, len); + (void)tls; } -/* ***************************************************************************** -Pub/Sub Subscription map (for mapping Master only subscriptions) -***************************************************************************** */ - -/** Performs Housekeeping and defers the on_unsubscribe callback. */ -FIO_IFUNC void fio___pubsub_subscription_unsubscribe(fio_subscription_s *s); - -/* define a helper map to manage master only subscription. */ -#define FIO_MAP_KEY_KSTR -#define FIO_MAP_NAME fio___postoffice_msmap -#define FIO_MAP_VALUE fio_subscription_s * -#define FIO_MAP_VALUE_DESTROY(s) fio___pubsub_subscription_unsubscribe(s) -#define FIO___RECURSIVE_INCLUDE -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE - -/* ***************************************************************************** -Pub/Sub Remote Connection Uniqueness -***************************************************************************** */ - -/* Managing Remote Connection Uniqueness */ -#define FIO_MAP_NAME fio___pubsub_broadcast_connected -#define FIO_MAP_KEY uint64_t -#define FIO___RECURSIVE_INCLUDE -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE - -/* ***************************************************************************** -Pub/Sub Engine Map -***************************************************************************** */ - -/* Managing Remote Connection Uniqueness */ -#define FIO_MAP_NAME fio___pubsub_engines -#define FIO_MAP_KEY fio_pubsub_engine_s * -#define FIO_MAP_HASH_FN(e) fio_risky_ptr(e) -#define FIO_MAP_RECALC_HASH 1 -#define FIO_MAP_KEY_DESTROY(e) \ - do { \ - e->detached(e); \ - e = NULL; \ - } while (0) -#define FIO_MAP_KEY_DISCARD(e) -#define FIO___RECURSIVE_INCLUDE -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE - -/* ***************************************************************************** -Message Uniqueness Map for filtering remote connection broadcasts -***************************************************************************** */ +/** Called to perform a non-blocking `write`, same as the system call. */ +static ssize_t fio___io_func_default_write(int fd, + const void *buf, + size_t len, + void *tls) { + return fio_sock_write(fd, buf, len); + (void)tls; +} +/** Sends any unsent internal data. Returns 0 only if all data was sent. */ +static int fio___io_func_default_flush(int fd, void *tls) { + return 0; + (void)fd, (void)tls; +} +/** Sends any unsent internal data. Returns 0 only if all data was sent. */ +static void fio___io_func_default_finish(int fd, void *tls) { + (void)fd, (void)tls; +} +static void fio___io_func_default_cleanup(void *p1) { (void)p1; } -/* Managing Remote Connection Uniqueness */ -#define FIO_MAP_NAME fio___pubsub_message_map -#define FIO_MAP_KEY fio___pubsub_message_s * -#define FIO_MAP_KEY_COPY(d_, e_) (d_ = fio___pubsub_message_dup(e_)) -#define FIO_MAP_KEY_CMP(a, b) \ - (a->data.id == b->data.id && a->data.published == b->data.published) -#define FIO_MAP_KEY_DESTROY(e) fio___pubsub_message_free(e) -#define FIO_MAP_HASH_FN(m) fio_risky_num(m->data.id, m->data.published) -#define FIO_MAP_RECALC_HASH 1 -#define FIO_MAP_LRU FIO___PUBSUB_CLUSTER_BACKLOG -#define FIO_MAP_KEY_DISCARD(e) -#define FIO___RECURSIVE_INCLUDE -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +/** Builds a local TLS context out of the fio_io_tls_s object. */ +static void *fio___io_func_default_build_context(fio_io_tls_s *tls, + uint8_t is_client) { + if (!tls) + return NULL; + FIO_ASSERT(0, + "SSL/TLS `build_context` was called, but no SSL/TLS " + "implementation found."); + return NULL; + (void)tls, (void)is_client; +} +/** Builds a local TLS context out of the fio_io_tls_s object. */ +static void fio___io_func_default_free_context(void *context) { + if (!context) + return; + FIO_ASSERT(0, + "SSL/TLS `free_context` was called, but no SSL/TLS " + "implementation found."); + (void)context; +} -/* ***************************************************************************** -Pub/Sub Post Office State -***************************************************************************** */ -#ifndef FIO___IPC_LEN -#define FIO___IPC_LEN 256 -#endif +static void fio___io_func_free_context_caller_task(void *fn_ptr, + void *context) { + union { + void (*free_context)(void *context); + void *fn_ptr; + } u = {.fn_ptr = fn_ptr}; + u.free_context(context); +} -FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_close(void *udata); -FIO_SFUNC void fio___pubsub_protocol_on_timeout(fio_s *io); +static void fio___io_func_free_context_caller(void (*free_context)(void *), + void *context) { + union { + void (*free_context)(void *context); + void *fn_ptr; + } u = {.free_context = free_context}; + fio_queue_push(fio_io_queue(), + fio___io_func_free_context_caller_task, + u.fn_ptr, + context); +} -static struct FIO___PUBSUB_POSTOFFICE { - fio_u128 uuid; - fio_u512 secret; - fio___channel_map_s channels; - fio___channel_map_s patterns; - struct { - uint8_t publish; - uint8_t local; - uint8_t remote; - } filter; - uint8_t secret_is_random; - FIO___LOCK_TYPE lock; - fio___pubsub_engines_s engines; - FIO_LIST_NODE history_active; - FIO_LIST_NODE history_waiting; - fio___postoffice_msmap_s master_subscriptions; - fio___postoffice_msmap_s global_subscriptions; - fio___pubsub_broadcast_connected_s remote_uuids; - fio___pubsub_message_map_s remote_messages; - fio___pubsub_message_map_s history_messages; - struct { - fio_protocol_s ipc; - fio_protocol_s remote; - } protocol; - fio_s *broadcaster; - struct { - fio_msg_metadata_fn build; - void (*cleanup)(void *); - size_t ref; - } metadata[FIO___PUBSUB_METADATA_STORE_LIMIT]; - char ipc_url[FIO___IPC_LEN]; -} FIO___PUBSUB_POSTOFFICE = { - .filter = - { - .publish = (FIO___PUBSUB_PROCESS | FIO___PUBSUB_ROOT), - .local = (FIO___PUBSUB_SIBLINGS), - .remote = FIO___PUBSUB_REMOTE, - }, - .lock = FIO___LOCK_INIT, - .protocol = - { - .ipc = - { - .on_attach = fio___pubsub_protocol_on_attach, - .on_data = fio___pubsub_protocol_on_data_master, - .on_close = fio___pubsub_protocol_on_close, - .on_timeout = fio_touch, - }, - .remote = - { - .on_attach = fio___pubsub_protocol_on_attach, - .on_data = fio___pubsub_protocol_on_data_remote, - .on_close = fio___pubsub_protocol_on_close, - .on_timeout = fio___pubsub_protocol_on_timeout, - }, - }, -}; +FIO_SFUNC void fio___io_init_protocol(fio_io_protocol_s *pr, _Bool has_tls) { + pr->reserved.protocols = FIO_LIST_INIT(pr->reserved.protocols); + pr->reserved.ios = FIO_LIST_INIT(pr->reserved.ios); + fio_io_functions_s io_fn = { + .build_context = fio___io_func_default_build_context, + .free_context = fio___io_func_default_free_context, + .start = fio___io_on_ev_mock, + .read = fio___io_func_default_read, + .write = fio___io_func_default_write, + .flush = fio___io_func_default_flush, + .finish = fio___io_func_default_finish, + .cleanup = fio___io_func_default_cleanup, + }; + if (has_tls) + io_fn = fio_io_tls_default_functions(NULL); + if (!pr->on_attach) + pr->on_attach = fio___io_on_ev_mock; + if (!pr->on_data) + pr->on_data = fio___io_on_ev_mock_sus; + if (!pr->on_ready) + pr->on_ready = fio___io_on_ev_mock; + if (!pr->on_close) + pr->on_close = fio___io_on_close_mock; + if (!pr->on_shutdown) + pr->on_shutdown = fio___io_on_ev_mock; + if (!pr->on_timeout) + pr->on_timeout = fio___io_on_ev_on_timeout; + if (!pr->on_pubsub) + pr->on_pubsub = fio___io_on_ev_pubsub_mock; + if (!pr->on_user1) + pr->on_user1 = fio___io_on_user_mock; + if (!pr->on_user2) + pr->on_user2 = fio___io_on_user_mock; + if (!pr->on_user3) + pr->on_user3 = fio___io_on_user_mock; + if (!pr->on_reserved) + pr->on_reserved = fio___io_on_user_mock; + if (!pr->io_functions.build_context) + pr->io_functions.build_context = io_fn.build_context; + if (!pr->io_functions.free_context) + pr->io_functions.free_context = io_fn.free_context; + if (!pr->io_functions.start) + pr->io_functions.start = io_fn.start; + if (!pr->io_functions.read) + pr->io_functions.read = io_fn.read; + if (!pr->io_functions.write) + pr->io_functions.write = io_fn.write; + if (!pr->io_functions.flush) + pr->io_functions.flush = io_fn.flush; + if (!pr->io_functions.finish) + pr->io_functions.finish = io_fn.finish; + if (!pr->io_functions.cleanup) + pr->io_functions.cleanup = io_fn.cleanup; + if (!pr->timeout) + pr->timeout = FIO_IO_TIMEOUT_MAX; + /* round up to nearest 16 byte size */ + pr->buffer_size = ((pr->buffer_size + 15ULL) & (~15ULL)); +} -/** Returns the secret key for a message with stated `rndm` value. */ -FIO_IFUNC const void *fio___pubsub_secret_key(uint64_t rndm) { - return (void *)&FIO___PUBSUB_POSTOFFICE.secret.u8[rndm & 15]; +/* the FIO___MOCK_PROTOCOL is used to manage hijacked / zombie connections. */ +static fio_io_protocol_s FIO___IO_MOCK_PROTOCOL; + +FIO_IFUNC void fio___io_init_protocol_test(fio_io_protocol_s *pr, + _Bool has_tls) { + if (!fio_atomic_or(&pr->reserved.flags, 1)) + fio___io_init_protocol(pr, has_tls); } /* ***************************************************************************** -PostOffice Helpers +IO Reactor State Machine ***************************************************************************** */ -/** Sets the current IPC socket address (shouldn't be changed while running). */ -SFUNC int fio_pubsub_ipc_url_set(char *str, size_t len) { - if (fio_srv_is_running() || len >= FIO___IPC_LEN) - return -1; - fio_str_info_s url = - FIO_STR_INFO3(FIO___PUBSUB_POSTOFFICE.ipc_url, 0, FIO___IPC_LEN); - fio_string_write2(&url, NULL, FIO_STRING_WRITE_STR2(str, len)); - return 0; +#define FIO___IO_FLAG_WAKEUP (1U) + +SFUNC struct { + fio_poll_s poll; + int64_t tick; + fio_queue_s queue; + uint32_t flags; + uint16_t workers; + uint8_t is_worker; + volatile uint8_t stop; + fio_timer_queue_s timer; + int wakeup_fd; + fio_thread_pid_t root_pid; + fio_thread_pid_t pid; + fio___io_env_safe_s env; + FIO_LIST_NODE protocols; + FIO_LIST_NODE async; + fio_io_s *wakeup; +} FIO___IO = { + .tick = 0, + .wakeup_fd = -1, + .stop = 1, +}; + +/** Stopping the IO reactor. */ +SFUNC void fio_io_stop(void) { FIO___IO.stop = 1; } + +/** Returns current process id. */ +SFUNC int fio_io_pid(void) { return FIO___IO.pid; } + +/** Returns the root / master process id. */ +SFUNC int fio_io_root_pid(void) { return FIO___IO.root_pid; } + +/** Returns true if running and 0 if stopped or shutting down. */ +SFUNC int fio_io_is_running(void) { return !FIO___IO.stop; } + +/** Returns true if the current process is the master process. */ +SFUNC int fio_io_is_master(void) { return FIO___IO.root_pid == FIO___IO.pid; } + +/** Returns true if the current process is a worker process. */ +SFUNC int fio_io_is_worker(void) { return FIO___IO.is_worker; } + +/** Returns the last millisecond when the polled for IO events. */ +SFUNC int64_t fio_io_last_tick(void) { return FIO___IO.tick; } + +FIO_SFUNC void fio___io_wakeup(void); +void fio_io_defer___(void); +/** Schedules a task for delayed execution. This function is thread-safe. */ +SFUNC void fio_io_defer FIO_NOOP(void (*task)(void *, void *), + void *udata1, + void *udata2) { + fio_queue_push(&FIO___IO.queue, task, udata1, udata2); + fio___io_wakeup(); } -/** Returns the current IPC socket address (shouldn't be changed). */ -SFUNC const char *fio_pubsub_ipc_url(void) { - return FIO___PUBSUB_POSTOFFICE.ipc_url; + +FIO_IFUNC void fio___io_defer_no_wakeup(void (*task)(void *, void *), + void *udata1, + void *udata2) { + fio_queue_push(&FIO___IO.queue, task, udata1, udata2); } -/** Sets a (possibly shared) secret for securing pub/sub communication. */ -SFUNC void fio_pubsub_secret_set(char *str, size_t len) { - FIO___PUBSUB_POSTOFFICE.secret_is_random = 0; - uint64_t fallback_secret = 0; - if (!str || !len) { - if ((str = getenv("SECRET"))) { - const char *secret_length = getenv("SECRET_LENGTH"); - len = secret_length ? fio_atol((char **)&secret_length) : 0; - if (!len) - len = strlen(str); - } else { - fallback_secret = fio_rand64(); - str = (char *)&fallback_secret; - len = sizeof(fallback_secret); - FIO___PUBSUB_POSTOFFICE.secret_is_random = 1; - } - } - FIO___PUBSUB_POSTOFFICE.secret = fio_sha512(str, len); +void fio_io_run_every___(void); +/** Schedules a timer bound task, see `fio_timer_schedule`. */ +SFUNC void fio_io_run_every FIO_NOOP(fio_timer_schedule_args_s args) { + args.start_at = FIO___IO.tick; + fio_timer_schedule FIO_NOOP(&FIO___IO.timer, args); } +/** Returns a pointer for the IO reactor's queue. */ +SFUNC fio_queue_s *fio_io_queue(void) { return &FIO___IO.queue; } + /* ***************************************************************************** -Postoffice History Control +IO Type ***************************************************************************** */ -FIO_SFUNC void fio___pubub_on_history_start(void *ignr_1, void *ignr_2) { - (void)ignr_1, (void)ignr_2; - if (!FIO_LIST_IS_EMPTY(&FIO___PUBSUB_POSTOFFICE.history_active)) +#define FIO___IO_FLAG_OPEN ((uint32_t)1U) +#define FIO___IO_FLAG_SUSPENDED ((uint32_t)2U) +#define FIO___IO_FLAG_THROTTLED ((uint32_t)4U) +#define FIO___IO_FLAG_CLOSE ((uint32_t)8U) +#define FIO___IO_FLAG_CLOSE_REMOTE ((uint32_t)16U) +#define FIO___IO_FLAG_CLOSE_ERROR ((uint32_t)32U) +#define FIO___IO_FLAG_TOUCH ((uint32_t)64U) +#define FIO___IO_FLAG_WRITE_SCHD ((uint32_t)128U) +#define FIO___IO_FLAG_POLLIN_SET ((uint32_t)256U) +#define FIO___IO_FLAG_POLLOUT_SET ((uint32_t)512U) + +#define FIO___IO_FLAG_PREVENT_ON_DATA \ + (FIO___IO_FLAG_SUSPENDED | FIO___IO_FLAG_THROTTLED | FIO___IO_FLAG_CLOSE | \ + FIO___IO_FLAG_CLOSE_REMOTE | FIO___IO_FLAG_CLOSE_ERROR) + +#define FIO___IO_FLAG_POLL_SET \ + (FIO___IO_FLAG_POLLIN_SET | FIO___IO_FLAG_POLLOUT_SET) + +#define FIO___IO_FLAG_SET(io, flag_to_set) \ + fio_atomic_or(&(io)->flags, flag_to_set) +#define FIO___IO_FLAG_UNSET(io, flag_to_unset) \ + fio_atomic_and(&(io)->flags, ~(flag_to_unset)) + +static void fio___io_poll_on_data_schd(void *io); +static void fio___io_poll_on_ready_schd(void *io); +static void fio___io_poll_on_close_schd(void *io); + +/** The main IO object type. Should be treated as an opaque pointer. */ +struct fio_io_s { + int fd; + uint32_t flags; + FIO_LIST_NODE node; + void *udata; + void *tls; + fio_io_protocol_s *pr; + fio_stream_s out; + fio___io_env_safe_s env; +#if FIO_IO_COUNT_STORAGE + size_t total_sent; + size_t total_recieved; +#endif + int64_t active; +}; + +FIO_IFUNC void fio___io_monitor_in(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) IO monitoring Input for %d (called)", + fio_io_pid(), + io->fd); + if (io->flags & FIO___IO_FLAG_PREVENT_ON_DATA) + return; + if ((FIO___IO_FLAG_SET(io, FIO___IO_FLAG_POLLIN_SET) & + FIO___IO_FLAG_POLLIN_SET)) { return; - FIO_LIST_EACH(fio_subscription_s, - history_active, - &FIO___PUBSUB_POSTOFFICE.history_active, - s) { - FIO_LIST_REMOVE(&s->history); - FIO_LIST_REMOVE(&s->history_active); - FIO_LIST_PUSH(&s->channel->history, &s->history); - FIO_LIST_PUSH(&FIO___PUBSUB_POSTOFFICE.history_active, &s->history_active); } + fio_poll_monitor(&FIO___IO.poll, io->fd, (void *)io, POLLIN); + FIO_LOG_DDEBUG2("(%d) IO monitoring Input for %d", fio_io_pid(), io->fd); +} +FIO_IFUNC void fio___io_monitor_out(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) IO monitoring Output for %d (called)", + fio_io_pid(), + io->fd); + if (io->flags & FIO___IO_FLAG_WRITE_SCHD) + return; + if ((FIO___IO_FLAG_SET(io, FIO___IO_FLAG_POLLOUT_SET) & + FIO___IO_FLAG_POLLOUT_SET)) + return; + fio_poll_monitor(&FIO___IO.poll, io->fd, (void *)io, POLLOUT); + FIO_LOG_DDEBUG2("(%d) IO monitoring Output for %d", fio_io_pid(), io->fd); } -FIO_SFUNC void fio___pubub_on_history_end(void *ignr_1, void *ignr_2) { - (void)ignr_1, (void)ignr_2; - FIO_LIST_EACH(fio_subscription_s, - history_active, - &FIO___PUBSUB_POSTOFFICE.history_active, - s) { - FIO_LIST_REMOVE(&s->history); - FIO_LIST_REMOVE(&s->history_active); - } +FIO_IFUNC void fio___io_monitor_forget(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) IO monitoring Removed for %d (called)", + fio_io_pid(), + io->fd); + if (!(FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_POLL_SET) & + FIO___IO_FLAG_POLL_SET)) + return; + fio_poll_forget(&FIO___IO.poll, io->fd); + FIO_LOG_DDEBUG2("(%d) IO monitoring Removed for %d", fio_io_pid(), io->fd); } -/* ***************************************************************************** -Postoffice Metadata Control -***************************************************************************** */ +FIO_SFUNC void fio___io_destroy(fio_io_s *io) { + fio_io_protocol_s *pr = io->pr; + FIO_LIST_REMOVE(&io->node); +#if FIO_IO_COUNT_STORAGE + FIO_LOG_DDEBUG2( + "(%d) detaching and destroying %p (fd %d): %zu/%zu bytes received/sent", + FIO___IO.pid, + (void *)io, + io->fd, + io->total_recieved, + io->total_sent); +#else + FIO_LOG_DDEBUG2("(%d) detaching and destroying %p (fd %d).", + FIO___IO.pid, + (void *)io, + io->fd); +#endif + /* store info, as it might be freed if the protocol is freed. */ + if (FIO_LIST_IS_EMPTY(&io->pr->reserved.ios)) + FIO_LIST_REMOVE_RESET(&io->pr->reserved.protocols); + /* call on_stop / free callbacks . */ + pr->io_functions.cleanup(io->tls); + pr->on_close((void *)(io + 1), io->udata); + fio___io_env_safe_destroy(&io->env); + fio_sock_close(io->fd); + fio_stream_destroy(&io->out); + fio___io_monitor_forget(io); + FIO_LOG_DDEBUG2("(%d) IO closed and destroyed for fd %d", + fio_io_pid(), + io->fd); +} -/* Returns zero (0) on success or -1 on failure. */ -SFUNC int fio_message_metadata_add(fio_msg_metadata_fn metadata_func, - void (*cleanup)(void *)) { - for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; - ++i) { /* test existing */ - if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1) && - metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) - return 0; - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); +#define FIO_REF_NAME fio___io +#define FIO_REF_TYPE fio_io_s +#define FIO_REF_FLEX_TYPE uint8_t +#define FIO_REF_DESTROY(io) fio___io_destroy(&io) +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +FIO_SFUNC void fio___io_protocol_set(void *io_, void *pr_) { + fio_io_s *io = (fio_io_s *)io_; + fio_io_protocol_s *pr = (fio_io_protocol_s *)pr_; + fio_io_protocol_s *old = io->pr; + if (!pr) + pr = &FIO___IO_MOCK_PROTOCOL; + fio___io_init_protocol_test(pr, (io->tls != NULL)); + FIO_LIST_REMOVE(&io->node); + if (FIO_LIST_IS_EMPTY(&old->reserved.ios)) + FIO_LIST_REMOVE_RESET(&old->reserved.protocols); + if (FIO_LIST_IS_EMPTY(&pr->reserved.ios)) + FIO_LIST_PUSH(&FIO___IO.protocols, &pr->reserved.protocols); + FIO_LIST_PUSH(&pr->reserved.ios, &io->node); + io->pr = pr; + FIO_LOG_DDEBUG2("(%d) protocol set for IO with fd %d", + fio_io_pid(), + fio_io_fd(io)); + pr->on_attach(io); + /* avoid calling `start` and setting `on_ready` more than once */ + if (old == &FIO___IO_MOCK_PROTOCOL) { + pr->io_functions.start(io); + fio___io_monitor_out(io); } - for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; - ++i) { /* insert if available */ - if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); - continue; - } - FIO___PUBSUB_POSTOFFICE.metadata[i].build = metadata_func; - FIO___PUBSUB_POSTOFFICE.metadata[i].cleanup = cleanup; - return 0; + fio___io_monitor_in(io); + fio___io_free2(io); +} + +/** Performs a task for each IO in the stated protocol. */ +SFUNC size_t fio_io_protocol_each(fio_io_protocol_s *protocol, + void (*task)(fio_io_s *, void *udata2), + void *udata2) { + size_t count = 0; + if (!protocol || !protocol->reserved.protocols.next) + return count; + FIO_LIST_EACH(fio_io_s, node, &protocol->reserved.ios, io) { + task(io, udata2); + ++count; } - return -1; + return count; +} + +/* Attaches the socket in `fd` to the facio.io engine (reactor). */ +SFUNC fio_io_s *fio_io_attach_fd(int fd, + fio_io_protocol_s *pr, + void *udata, + void *tls) { + fio_io_s *io = NULL; + if (fd == -1) + goto error; + io = fio___io_new2(pr->buffer_size); + *io = (fio_io_s){ + .fd = fd, + .flags = FIO___IO_FLAG_OPEN, + .pr = &FIO___IO_MOCK_PROTOCOL, + .node = FIO_LIST_INIT(io->node), + .udata = udata, + .tls = tls, + .active = FIO___IO.tick, + }; + fio_sock_set_non_block(fd); + FIO_LOG_DDEBUG2("(%d) attaching fd %d to IO object %p", + fio_io_pid(), + fd, + (void *)io); + fio_io_defer(fio___io_protocol_set, (void *)fio___io_dup2(io), (void *)pr); + return io; + +error: + pr->on_close(NULL, udata); + pr->io_functions.cleanup(tls); + return io; +} + +/** Sets a new protocol object. `NULL` is a valid "only-write" protocol. */ +SFUNC fio_io_protocol_s *fio_io_protocol_set(fio_io_s *io, + fio_io_protocol_s *pr) { + fio_io_defer(fio___io_protocol_set, (void *)fio___io_dup2(io), (void *)pr); + return pr; } /** - * Removed the metadata callback. + * Returns a pointer to the current protocol object. * - * Removal might be delayed if live metatdata - * exists. + * If `protocol` wasn't properly set, the pointer might be NULL or invalid. + * + * If `protocol` wasn't attached yet, may return the previous protocol. */ -SFUNC void fio_message_metadata_remove(fio_msg_metadata_fn metadata_func) { - for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; - ++i) { /* test existing */ - if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1) && - metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) { - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); - } - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); - } +IFUNC fio_io_protocol_s *fio_io_protocol(fio_io_s *io) { return io->pr; } + +/** Returns the a pointer to the memory buffer required by the protocol. */ +IFUNC void *fio_io_buffer(fio_io_s *io) { return (void *)(io + 1); } + +/** Returns the length of the `buffer` buffer. */ +IFUNC size_t fio_io_buffer_len(fio_io_s *io) { + return fio___io_metadata_flex_len(io); } -/** Finds the message's metadata, returning the data or NULL. */ -SFUNC void *fio_message_metadata(fio_msg_s *msg, - fio_msg_metadata_fn metadata_func) { - for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; - ++i) { /* test existing */ - if (FIO___PUBSUB_POSTOFFICE.metadata[i].ref && - metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) { - return fio___pubsub_msg2internal(msg)->metadata[i]; - } - } - return NULL; +/** Associates a new `udata` pointer with the IO, returning the old `udata` */ +IFUNC void *fio_io_udata_set(fio_io_s *io, void *udata) { + void *old = io->udata; + io->udata = udata; + return old; } -/* ***************************************************************************** -Listening to Local Connections (IPC) -***************************************************************************** */ +/** Returns the `udata` pointer associated with the IO. */ +IFUNC void *fio_io_udata(fio_io_s *io) { return io->udata; } -#if defined(DEBUG) -#define FIO___PUBSUB_HIDE_FROM_LOG 0 -#else -#define FIO___PUBSUB_HIDE_FROM_LOG 1 -#endif -/** Starts listening to IPC connections on a local socket. */ -FIO_IFUNC void fio___pubsub_ipc_listen(void *ignr_) { +/** Associates a new `tls` pointer with the IO, returning the old `tls` */ +IFUNC void *fio_io_tls_set(fio_io_s *io, void *tls) { + void *old = io->tls; + io->tls = tls; + return old; +} + +/** Returns the `tls` pointer associated with the IO. */ +IFUNC void *fio_io_tls(fio_io_s *io) { return io->tls; } + +/** Returns the socket file descriptor (fd) associated with the IO. */ +IFUNC int fio_io_fd(fio_io_s *io) { return io->fd; } + +FIO_SFUNC void fio___io_touch(void *io_, void *ignr_) { + fio_io_s *io = (fio_io_s *)io_; + fio_atomic_and(&io->flags, ~FIO___IO_FLAG_TOUCH); + io->active = FIO___IO.tick; + FIO_LIST_REMOVE(&io->node); /* timeout IO ordering */ + FIO_LIST_PUSH(&io->pr->reserved.ios, &io->node); + fio___io_free2(io); (void)ignr_; - if (fio_srv_is_worker()) { - FIO_LOG_DEBUG2("(%d) pub/sub IPC socket skipped - no workers are spawned.", - fio_srv_pid()); - return; +} + +/* Resets a socket's timeout counter. */ +SFUNC void fio_io_touch(fio_io_s *io) { + if (!(fio_atomic_or(&io->flags, FIO___IO_FLAG_TOUCH) & FIO___IO_FLAG_TOUCH)) + fio_queue_push_urgent(&FIO___IO.queue, fio___io_touch, fio___io_dup2(io)); +} + +/** + * Reads data to the buffer, if any data exists. Returns the number of bytes + * read. + * + * NOTE: zero (`0`) is a valid return value meaning no data was available. + */ +SFUNC size_t fio_io_read(fio_io_s *io, void *buf, size_t len) { + if (!io) + return 0; + ssize_t r = io->pr->io_functions.read(io->fd, buf, len, io->tls); + if (r > 0) { +#if FIO_IO_COUNT_STORAGE + io->total_recieved += r; +#endif + fio_io_touch(io); + return r; } - FIO_ASSERT(fio_srv_listen(.url = FIO___PUBSUB_POSTOFFICE.ipc_url, - .protocol = &FIO___PUBSUB_POSTOFFICE.protocol.ipc, - .on_root = 1, - .hide_from_log = FIO___PUBSUB_HIDE_FROM_LOG), - "(%d) pub/sub couldn't open a socket for IPC\n\t\t%s", - fio_srv_pid(), - FIO___PUBSUB_POSTOFFICE.ipc_url); + if ((unsigned)(!len) | + ((unsigned)(r == -1) & ((unsigned)(errno == EAGAIN) | + (errno == EWOULDBLOCK) | (errno == EINTR)))) + return 0; + fio_io_close(io); + return 0; } -#undef FIO___PUBSUB_HIDE_FROM_LOG -/* ***************************************************************************** -Postoffice Constructor / Destructor -***************************************************************************** */ +FIO_SFUNC void fio___io_write2_dealloc_task(void *fn, void *data) { + union { + void *ptr; + void (*fn)(void *); + } u = {.ptr = fn}; + u.fn(data); +} -/* listens for IPC connections. */ -FIO_SFUNC void fio___pubsub_ipc_listen(void *); -/* protocol functions. */ -FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_s *io); -FIO_SFUNC void fio___pubsub_protocol_on_close(void *udata); +FIO_SFUNC void fio___io_write2(void *io_, void *packet_) { + fio_io_s *io = (fio_io_s *)io_; + fio_stream_packet_s *packet = (fio_stream_packet_s *)packet_; + if (!(io->flags & FIO___IO_FLAG_OPEN)) + goto io_closed; -FIO_SFUNC void fio___pubsub_at_exit(void *ignr_) { + fio_stream_add(&io->out, packet); + fio___io_poll_on_ready_schd((void *)io); + fio___io_free2(io); + return; + +io_closed: + fio_stream_packet_free(packet); + fio___io_free2(io); +} + +void fio_io_write2___(void); +/** + * Writes data to the outgoing buffer and schedules the buffer to be sent. + */ +SFUNC void fio_io_write2 FIO_NOOP(fio_io_s *io, fio_io_write_args_s args) { + fio_stream_packet_s *packet = NULL; + if (!io) + goto io_error_null; + if (args.buf) { + packet = fio_stream_pack_data(args.buf, + args.len, + args.offset, + args.copy, + args.dealloc); + } else if ((unsigned)(args.fd + 1) > 1) { + packet = fio_stream_pack_fd((int)args.fd, args.len, args.offset, args.copy); + } + if (!packet) + goto error; + if ((io->flags & FIO___IO_FLAG_CLOSE)) + goto write_called_after_close; + fio_io_defer(fio___io_write2, (void *)fio___io_dup2(io), (void *)packet); + return; + +error: /* note: `dealloc` already called by the `fio_stream` error handler. */ + FIO_LOG_ERROR("couldn't create %zu bytes long user-packet for IO %p (%d)", + args.len, + (void *)io, + (io ? io->fd : -1)); + return; + +write_called_after_close: + FIO_LOG_DEBUG2("`write` called after `close` was called for IO."); + { + union { + void *ptr; + void (*fn)(fio_stream_packet_s *); + } u = {.fn = fio_stream_pack_free}; + fio___io_defer_no_wakeup(fio___io_write2_dealloc_task, u.ptr, packet); + } + return; + +io_error_null: + FIO_LOG_ERROR("(%d) `fio_write2` called for invalid IO (NULL)", FIO___IO.pid); + if (args.dealloc) { + union { + void *ptr; + void (*fn)(void *); + } u = {.fn = args.dealloc}; + fio___io_defer_no_wakeup(fio___io_write2_dealloc_task, u.ptr, args.buf); + if ((unsigned)(args.fd + 1) > 1) + close((int)args.fd); + } +} + +/** Marks the IO for closure as soon as scheduled data was sent. */ +SFUNC void fio_io_close(fio_io_s *io) { + if (io && (io->flags & FIO___IO_FLAG_OPEN) && + !(FIO___IO_FLAG_SET(io, FIO___IO_FLAG_CLOSE) & FIO___IO_FLAG_CLOSE)) { + FIO_LOG_DDEBUG2("(%d) scheduling IO %p (fd %d) for closure", + fio_io_pid(), + (void *)io, + io->fd); + fio___io_poll_on_ready_schd((void *)io); + } +} + +/** Marks the IO for immediate closure. */ +SFUNC void fio_io_close_now(fio_io_s *io) { + if (!io) + return; + FIO_LOG_DDEBUG2("(%d) pre-destruction close called for fd %d", + fio_io_pid(), + fio_io_fd(io)); + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_CLOSE); + if ((FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_OPEN) & FIO___IO_FLAG_OPEN)) + fio_io_free(io); +} + +/** + * Increases a IO's reference count, so it won't be automatically destroyed + * when all tasks have completed. + * + * Use this function in order to use the IO outside of a scheduled task. + * + * This function is thread-safe. + */ +SFUNC fio_io_s *fio_io_dup(fio_io_s *io) { return fio___io_dup2(io); } + +SFUNC void fio___io_free_task(void *io_, void *ignr_) { + fio___io_free2((fio_io_s *)io_); (void)ignr_; - fio_queue_perform_all(fio_srv_queue()); - fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.master_subscriptions); - fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.global_subscriptions); - fio___pubsub_broadcast_connected_destroy( - &FIO___PUBSUB_POSTOFFICE.remote_uuids); - fio___pubsub_message_map_destroy(&FIO___PUBSUB_POSTOFFICE.remote_messages); - fio___pubsub_message_map_destroy(&FIO___PUBSUB_POSTOFFICE.history_messages); - fio___pubsub_engines_destroy(&FIO___PUBSUB_POSTOFFICE.engines); - FIO___LOCK_DESTROY(FIO___PUBSUB_POSTOFFICE.lock); - fio_queue_perform_all(fio_srv_queue()); +} +/** Free IO (reference) - thread-safe */ +SFUNC void fio_io_free(fio_io_s *io) { + fio___io_defer_no_wakeup(fio___io_free_task, (void *)io, NULL); } -/** Callback called by the letter protocol entering a child processes. */ -FIO_SFUNC void fio___pubsub_on_enter_child(void *ignr_) { +/** Suspends future "on_data" events for the IO. */ +SFUNC void fio_io_suspend(fio_io_s *io) { + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_SUSPENDED); +} + +SFUNC void fio___io_unsuspend(void *io_, void *ignr_) { + fio_io_s *io = (fio_io_s *)io_; + fio___io_monitor_in(io); (void)ignr_; - FIO___PUBSUB_POSTOFFICE.protocol.ipc.on_data = - fio___pubsub_protocol_on_data_worker; +} - FIO___PUBSUB_POSTOFFICE.filter.publish = FIO___PUBSUB_PROCESS; - FIO___PUBSUB_POSTOFFICE.filter.local = - (FIO___PUBSUB_SIBLINGS | FIO___PUBSUB_ROOT); - FIO___PUBSUB_POSTOFFICE.filter.remote = 0; - fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.master_subscriptions); - fio___pubsub_engines_destroy(&FIO___PUBSUB_POSTOFFICE.engines); - if (!fio_srv_attach_fd(fio_sock_open2(FIO___PUBSUB_POSTOFFICE.ipc_url, - FIO_SOCK_CLIENT | FIO_SOCK_NONBLOCK), - &FIO___PUBSUB_POSTOFFICE.protocol.ipc, - NULL, - NULL)) { - FIO_LOG_FATAL("(%d) couldn't connect to pub/sub socket @ %s", - fio_srv_pid(), - FIO___PUBSUB_POSTOFFICE.ipc_url); - fio_thread_kill(fio_srv_root_pid(), SIGINT); - FIO_ASSERT(0, "fatal error encountered"); - } +/** Listens for future "on_data" events related to the IO. */ +SFUNC void fio_io_unsuspend(fio_io_s *io) { + if ((FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_SUSPENDED) & + FIO___IO_FLAG_SUSPENDED)) + fio_io_defer(fio___io_unsuspend, (void *)io, NULL); } -FIO_CONSTRUCTOR(fio_postoffice_init) { - FIO___PUBSUB_POSTOFFICE.engines = (fio___pubsub_engines_s)FIO_MAP_INIT; - fio_pubsub_secret_set(NULL, 0); /* allocate a random secret */ - for (size_t i = 0; i < sizeof(FIO___PUBSUB_POSTOFFICE.uuid) / 8; ++i) - FIO___PUBSUB_POSTOFFICE.uuid.u64[i] = fio_rand64(); - fio_str_info_s url = - FIO_STR_INFO3(FIO___PUBSUB_POSTOFFICE.ipc_url, 0, FIO___IPC_LEN); +/** Returns 1 if the IO handle was suspended. */ +SFUNC int fio_io_is_suspended(fio_io_s *io) { + return (int)((io->flags & FIO___IO_FLAG_SUSPENDED) / FIO___IO_FLAG_SUSPENDED); +} - fio_string_write2(&url, - NULL, - FIO_STRING_WRITE_STR1((char *)"priv://facil_io_tmp_"), - FIO_STRING_WRITE_HEX(fio_rand64()), - FIO_STRING_WRITE_STR1((char *)".sock")); - fio_state_callback_add(FIO_CALL_PRE_START, fio___pubsub_ipc_listen, NULL); - fio_state_callback_add(FIO_CALL_IN_CHILD, fio___pubsub_on_enter_child, NULL); - fio_state_callback_add(FIO_CALL_AT_EXIT, fio___pubsub_at_exit, NULL); - /* TODO!!! */ - FIO___PUBSUB_POSTOFFICE.protocol.ipc = (fio_protocol_s){ - .on_attach = fio___pubsub_protocol_on_attach, - .on_data = fio___pubsub_protocol_on_data_master, - .on_close = fio___pubsub_protocol_on_close, - .on_timeout = fio_touch, - }; - FIO___PUBSUB_POSTOFFICE.protocol.remote = (fio_protocol_s){ - .on_attach = fio___pubsub_protocol_on_attach, - .on_data = fio___pubsub_protocol_on_data_remote, - .on_close = fio___pubsub_protocol_on_close, - .on_timeout = fio_touch, - }; +/** Returns 1 if the IO handle is marked as open. */ +SFUNC int fio_io_is_open(fio_io_s *io) { + return (int)((io->flags & FIO___IO_FLAG_OPEN) / FIO___IO_FLAG_OPEN); +} + +/** Returns the approximate number of bytes in the outgoing buffer. */ +SFUNC size_t fio_io_backlog(fio_io_s *io) { + return fio_stream_length(&io->out); } /* ***************************************************************************** -Subscription Setup +Connection Object Links / Environment ***************************************************************************** */ -/** Completes the subscription request. */ -FIO_IFUNC void fio___pubsub_subscribe_task(void *sub_, void *ignr_) { - fio_subscription_s *sub = (fio_subscription_s *)sub_; - union { - FIO_LIST_HEAD *ls; - fio_str_info_s *str; - } uptr = {.ls = &sub->node}; - const fio_str_info_s ch_name = *uptr.str; - fio_channel_s **ch_ptr = - fio___channel_map_node2key_ptr(fio___channel_map_set_ptr( - &FIO___PUBSUB_POSTOFFICE.channels + (ch_name.capa >> 16), - ch_name)); - fio_bstr_free(ch_name.buf); - sub->node = FIO_LIST_INIT(sub->node); - sub->history = FIO_LIST_INIT(sub->history); - sub->history_active = FIO_LIST_INIT(sub->history_active); - if (FIO_UNLIKELY(!ch_ptr)) - goto no_channel; - sub->channel = ch_ptr[0]; - FIO_LIST_PUSH(&(ch_ptr[0]->subscriptions), &sub->node); - if (sub->replay_since) { - FIO_LIST_PUSH(&FIO___PUBSUB_POSTOFFICE.history_waiting, &sub->history); - /* TODO: publish history request event to the cluster. */ +void *fio_io_env_get___(void); /* IDE Marker */ +/** Returns the named `udata` associated with the IO object (or `NULL`). */ +SFUNC void *fio_io_env_get FIO_NOOP(fio_io_s *io, fio_io_env_get_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + return fio___io_env_safe_get(e, a.name.buf, a.name.len, a.type); +} + +void fio_io_env_set___(void); /* IDE Marker */ +/** Links an object to a connection's lifetime / environment. */ +SFUNC void fio_io_env_set FIO_NOOP(fio_io_s *io, fio_io_env_set_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + fio___io_env_safe_set(e, + a.name.buf, + a.name.len, + a.type, + (fio___io_env_obj_s){.on_close = a.on_close, a.udata}, + a.const_name); +} + +int fio_io_env_unset___(void); /* IDE Marker */ +/** Un-links an object from the connection's lifetime, so it's `on_close` */ +SFUNC int fio_io_env_unset FIO_NOOP(fio_io_s *io, fio_io_env_get_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + return fio___io_env_safe_unset(e, a.name.buf, a.name.len, a.type); +} + +int fio_io_env_remove___(void); /* IDE Marker */ +/** + * Removes an object from the connection's lifetime / environment, calling it's + * `on_close` callback as if the connection was closed. + */ +SFUNC int fio_io_env_remove FIO_NOOP(fio_io_s *io, fio_io_env_get_args_s a) { + fio___io_env_safe_s *e = io ? &io->env : &FIO___IO.env; + return fio___io_env_safe_remove(e, a.name.buf, a.name.len, a.type); +} + +/* ***************************************************************************** +Event handling +***************************************************************************** */ + +static void fio___io_poll_on_data(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_POLLIN_SET); + if (!(io->flags & FIO___IO_FLAG_PREVENT_ON_DATA)) { + /* this also tests for the suspended / throttled / closing flags */ + io->pr->on_data(io); + fio___io_monitor_in(io); + } else if ((io->flags & FIO___IO_FLAG_OPEN)) { + fio___io_monitor_out(io); } + fio___io_free2(io); return; -no_channel: - fio___pubsub_subscription_unsubscribe(sub); - (void)ignr_; } -/** Unsubscribes a node and destroys the channel if no more subscribers. */ -FIO_IFUNC void fio___pubsub_unsubscribe_task(void *sub_, void *ignr_) { - fio_subscription_s *sub = (fio_subscription_s *)sub_; - fio_channel_s *ch = sub->channel; - fio___channel_map_s *map; - FIO_LIST_REMOVE(&sub->node); - FIO_LIST_REMOVE(&sub->history); - FIO_LIST_REMOVE(&sub->history_active); - if (FIO_UNLIKELY(!ch)) - goto no_channel; - - if (FIO_LIST_IS_EMPTY(&ch->subscriptions)) { - map = &FIO___PUBSUB_POSTOFFICE.channels + ch->is_pattern; - fio___channel_map_remove(map, FIO___PUBSUB_CHANNEL2STR(ch), NULL); - if (!fio___channel_map_count(map)) - fio___channel_map_destroy(map); +static void fio___io_poll_on_ready(void *io_, void *ignr_) { + (void)ignr_; +#if DEBUG + errno = 0; +#endif + fio_io_s *io = (fio_io_s *)io_; + char buf_mem[FIO_IO_BUFFER_PER_WRITE]; + size_t total = 0; + FIO___IO_FLAG_UNSET(io, + (FIO___IO_FLAG_POLLOUT_SET | FIO___IO_FLAG_WRITE_SCHD)); + FIO_LOG_DDEBUG2("(%d) poll_on_ready callback for fd %d", + fio_io_pid(), + fio_io_fd(io)); + if (!(io->flags & FIO___IO_FLAG_OPEN)) + goto finish; + for (;;) { + size_t len = FIO_IO_BUFFER_PER_WRITE; + char *buf = buf_mem; + fio_stream_read(&io->out, &buf, &len); + if (!len) + break; + ssize_t r = io->pr->io_functions.write(io->fd, buf, len, io->tls); + if (r > 0) { + FIO_LOG_DDEBUG2("(%d) written %zu bytes to fd %d", + FIO___IO.pid, + (size_t)r, + io->fd); + total += r; + fio_stream_advance(&io->out, r); + continue; + } + if (r == -1) { + if ((errno == EWOULDBLOCK) || (errno == EAGAIN)) + break; + if (errno == EINTR) + continue; + } + goto connection_error; } - sub->channel = NULL; + if (total) { + fio___io_touch((void *)fio___io_dup2(io), NULL); +#if FIO_IO_COUNT_STORAGE + io->total_sent += total; +#endif + } + if (fio_stream_any(&io->out) || io->pr->io_functions.flush(io->fd, io->tls)) { + if (fio_stream_length(&io->out) >= FIO_IO_THROTTLE_LIMIT) { + if (!(io->flags & FIO___IO_FLAG_THROTTLED)) + FIO_LOG_DDEBUG2("(%d), throttled IO %p (fd %d)", + FIO___IO.pid, + (void *)io, + io->fd); + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_THROTTLED); + } + fio___io_monitor_out(io); + } else if ((io->flags & FIO___IO_FLAG_CLOSE)) { + io->pr->io_functions.finish(io->fd, io->tls); + fio_io_close_now(io); + } else { + if ((io->flags & FIO___IO_FLAG_THROTTLED)) { + FIO___IO_FLAG_UNSET(io, FIO___IO_FLAG_THROTTLED); + fio___io_monitor_in(io); + } + FIO_LOG_DDEBUG2("(%d) calling on_ready for %p (fd %d) - %zu data left.", + FIO___IO.pid, + (void *)io, + io->fd, + fio_stream_length(&io->out)); + io->pr->on_ready(io); + } + +finish: + fio___io_free2(io); + return; + +connection_error: +#if DEBUG + if (fio_stream_any(&io->out)) + FIO_LOG_DERROR( + "(%d) IO write failed (%d), disconnecting: %p (fd %d)\n\tError: %s", + FIO___IO.pid, + errno, + (void *)io, + io->fd, + strerror(errno)); +#endif + fio_io_close_now(io); + fio___io_free2(io); +} -no_channel: - fio_subscription_free(sub); - return; +static void fio___io_poll_on_close(void *io_, void *ignr_) { (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + FIO___IO_FLAG_SET(io, FIO___IO_FLAG_CLOSE_REMOTE); + FIO_LOG_DEBUG2("(%d) fd %d closed by remote peer", FIO___IO.pid, io->fd); + fio_io_close_now(io); + fio___io_free2(io); } -/** Performs Housekeeping and defers the on_unsubscribe callback. */ -FIO_IFUNC void fio___pubsub_subscription_unsubscribe(fio_subscription_s *s) { - if (!s) - return; - s->on_message = fio___subscription_mock_cb; - fio_queue_push(fio_srv_queue(), - fio___pubsub_unsubscribe_task, - (void *)s, - NULL); +static void fio___io_poll_on_timeout(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + io->pr->on_timeout(io); + fio___io_free2(io); } -/** Subscribes to a named channel in the numerical filter's namespace. */ -void fio_subscribe___(void); /* sublimetext marker */ -SFUNC void fio_subscribe FIO_NOOP(fio_subscribe_args_s args) { - fio_subscription_s *s = NULL; - union { - FIO_LIST_HEAD *ls; - fio_str_info_s *str; - } uptr; - if (args.channel.len > 0xFFFFUL) - goto sub_error; - s = fio_subscription_new(); - if (!s) - goto sub_error; - - *s = (fio_subscription_s){ - .replay_since = args.replay_since, - .io = args.io, - .on_message = - (args.on_message ? args.on_message - : (args.io ? fio___subscription_call_protocol - : fio___subscription_mock_cb)), - .on_unsubscribe = args.on_unsubscribe, - .udata = args.udata, - }; - args.is_pattern = !!args.is_pattern; /* make sure this is either 1 or zero */ - uptr.ls = &s->node; - *uptr.str = FIO_STR_INFO3( - (args.channel.len - ? fio_bstr_write(NULL, args.channel.buf, args.channel.len) - : NULL), - args.channel.len, - FIO___PUBSUB_CHANNEL_ENCODE_CAPA(args.filter, args.is_pattern)); +/* ***************************************************************************** +Event scheduling +***************************************************************************** */ - if (args.subscription_handle_ptr) - goto has_handle; - if (args.master_only) - goto is_master_only; - if (!args.io) - goto is_global; +static void fio___io_poll_on_data_schd(void *io) { + FIO_LOG_DDEBUG2("(%d) `on_data` scheduled for fd %d.", + fio_io_pid(), + fio_io_fd((fio_io_s *)io)); + fio___io_defer_no_wakeup(fio___io_poll_on_data, + (void *)fio___io_dup2((fio_io_s *)io), + NULL); +} +static void fio___io_poll_on_ready_schd(void *io) { + if (!(FIO___IO_FLAG_SET((fio_io_s *)io, FIO___IO_FLAG_WRITE_SCHD) & + FIO___IO_FLAG_WRITE_SCHD)) { + FIO_LOG_DDEBUG2("(%d) `on_ready` scheduled for fd %d.", + fio_io_pid(), + fio_io_fd((fio_io_s *)io)); + fio___io_defer_no_wakeup(fio___io_poll_on_ready, + (void *)fio___io_dup2((fio_io_s *)io), + NULL); + } +} +static void fio___io_poll_on_close_schd(void *io) { + FIO_LOG_DDEBUG2("(%d) remote closure for fd %d.", + fio_io_pid(), + fio_io_fd((fio_io_s *)io)); + fio___io_defer_no_wakeup(fio___io_poll_on_close, + (void *)fio___io_dup2((fio_io_s *)io), + NULL); +} - fio_srv_defer(fio___pubsub_subscribe_task, (void *)s, NULL); - fio_env_set(args.io, - .type = (intptr_t)(0LL - (((2ULL | (!!args.is_pattern)) << 16) | - (uint16_t)args.filter)), - .name = args.channel, - .udata = s, - .on_close = - (void (*)(void *))fio___pubsub_subscription_unsubscribe); - return; +/* ***************************************************************************** +Timeout Review +***************************************************************************** */ -has_handle: - fio_srv_defer(fio___pubsub_subscribe_task, (void *)s, NULL); - *args.subscription_handle_ptr = (uintptr_t)s; - return; +/** Schedules the timeout event for any timed out IO object */ +static int fio___io_review_timeouts(void) { + int c = 0; + static time_t last_to_review = 0; + /* test timeouts at whole second intervals */ + if (last_to_review + 1000 > FIO___IO.tick) + return c; + last_to_review = FIO___IO.tick; + const int64_t now_milli = FIO___IO.tick; -is_master_only: - if (!fio_srv_is_master()) - goto error_not_on_master; -is_global: - if (1) { /* so C++ can jump even though there's a new var here */ - fio_srv_defer(fio___pubsub_subscribe_task, (void *)s, NULL); - uint64_t hashed_value = - fio_risky_hash(args.channel.buf, - args.channel.len, - args.filter | ((size_t)args.is_pattern << 20)); - FIO___LOCK_LOCK(FIO___PUBSUB_POSTOFFICE.lock); - fio___postoffice_msmap_set( - &FIO___PUBSUB_POSTOFFICE.master_subscriptions + (!args.master_only), - hashed_value, - FIO_STR_INFO2(args.channel.buf, args.channel.len), - s, - NULL); - FIO___LOCK_UNLOCK(FIO___PUBSUB_POSTOFFICE.lock); + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_ASSERT_DEBUG(pr->reserved.flags, "protocol object flags unmarked?!"); + if (!pr->timeout || pr->timeout > FIO_IO_TIMEOUT_MAX) + pr->timeout = FIO_IO_TIMEOUT_MAX; + int64_t limit = now_milli - ((int64_t)pr->timeout); + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + FIO_ASSERT_DEBUG(io->pr == pr, "IO protocol ownership error"); + if (io->active >= limit) + break; + FIO_LOG_DDEBUG2("(%d) scheduling timeout for %p (fd %d)", + FIO___IO.pid, + (void *)io, + io->fd); + fio___io_defer_no_wakeup(fio___io_poll_on_timeout, + (void *)fio___io_dup2(io), + NULL); + ++c; + } } - return; + return c; +} -error_not_on_master: - fio_bstr_free(uptr.str->buf); - s->node = FIO_LIST_INIT(s->node); - s->history = FIO_LIST_INIT(s->history); - fio_subscription_free(s); - FIO_LOG_WARNING( - "(%d) master-only subscription attempt on a non-master process: %.*s", - fio_srv_pid(), - (int)args.channel.len, - args.channel.buf); - return; +/* ***************************************************************************** +Wakeup Protocol +***************************************************************************** */ -sub_error: - FIO_LOG_ERROR("(%d) pub/sub subscription/channel cannot be created?" - "\n\t%zu bytes long\n\t%.*s...", - fio_srv_pid(), - args.channel.len, - (int)(args.channel.len > 10 ? 7 : args.channel.len), - args.channel.buf); - FIO_LOG_ERROR("failed to allocate a new subscription"); - if (args.on_unsubscribe) { - union { - void *p; - void (*fn)(void *udata); - } u = {.fn = args.on_unsubscribe}; - fio_queue_push(fio_srv_queue(), - fio___pubsub_subscription_on_destroy__task, - u.p, - args.udata); - } - return; +FIO_SFUNC void fio___io_wakeup_cb(fio_io_s *io) { + char buf[512]; + ssize_t r = fio_sock_read(fio_io_fd(io), buf, 512); + (void)r; + FIO_LOG_DDEBUG2("(%d) fio___io_wakeup called", FIO___IO.pid); + FIO___IO_FLAG_UNSET(&FIO___IO, FIO___IO_FLAG_WAKEUP); +} +FIO_SFUNC void fio___io_wakeup_on_close(void *ignr1_, void *ignr2_) { + fio_sock_close(FIO___IO.wakeup_fd); + FIO___IO.wakeup = NULL; + FIO___IO.wakeup_fd = -1; + FIO_LOG_DDEBUG2("(%d) fio___io_wakeup destroyed", FIO___IO.pid); + (void)ignr1_, (void)ignr2_; } -/** Cancels an existing subscriptions. */ -void fio_unsubscribe___(void); /* sublimetext marker */ -int fio_unsubscribe FIO_NOOP(fio_subscribe_args_s args) { - if (args.subscription_handle_ptr) - goto has_handle; - if (args.master_only || !args.io) - goto is_global; - - return fio_env_remove( - args.io, - .type = (intptr_t)(0LL - (((2ULL | (!!args.is_pattern)) << 16) | - (uint16_t)args.filter)), - .name = args.channel); +FIO_SFUNC void fio___io_wakeup(void) { + if (!FIO___IO.wakeup || (FIO___IO_FLAG_SET(&FIO___IO, FIO___IO_FLAG_WAKEUP) & + FIO___IO_FLAG_WAKEUP)) + return; + char buf[1] = {(char)~0}; + ssize_t ignr = fio_sock_write(FIO___IO.wakeup_fd, buf, 1); + (void)ignr; +} -has_handle: - fio___pubsub_subscription_unsubscribe( - *(fio_subscription_s **)args.subscription_handle_ptr); - return 0; +static fio_io_protocol_s FIO___IO_WAKEUP_PROTOCOL = { + .on_data = fio___io_wakeup_cb, + .on_close = fio___io_wakeup_on_close, + .on_timeout = fio_io_touch, +}; -is_global: - if (1) { - int r; - uint64_t hashed_value = - fio_risky_hash(args.channel.buf, - args.channel.len, - args.filter | ((size_t)args.is_pattern << 20)); - FIO___LOCK_LOCK(FIO___PUBSUB_POSTOFFICE.lock); - r = fio___postoffice_msmap_remove( - &FIO___PUBSUB_POSTOFFICE.master_subscriptions + (!args.master_only), - hashed_value, - FIO_STR_INFO3(args.channel.buf, args.channel.len, (size_t)-1), - NULL); - FIO___LOCK_UNLOCK(FIO___PUBSUB_POSTOFFICE.lock); - return r; +FIO_SFUNC void fio___io_wakeup_init(void) { + if (FIO___IO.wakeup) + return; + int fds[2]; + if (pipe(fds)) { + FIO_LOG_ERROR("(%d) couldn't open wakeup pipes, fio___io_wakeup disabled.", + FIO___IO.pid); + return; } + fio_sock_set_non_block(fds[0]); + fio_sock_set_non_block(fds[1]); + FIO___IO.wakeup_fd = fds[1]; + FIO___IO.wakeup = fio_io_attach_fd(fds[0], + &FIO___IO_WAKEUP_PROTOCOL, + (void *)(uintptr_t)fds[1], + NULL); + FIO_LOG_DDEBUG2("(%d) fio___io_wakeup initialized", FIO___IO.pid); } /* ***************************************************************************** -Pub/Sub Message Distribution (local process) +TLS Context Type and Helpers ***************************************************************************** */ -/* performs the subscription callback */ -FIO_IFUNC void fio___subscription_on_message_task(void *s_, void *m_) { - fio_subscription_s *s = (fio_subscription_s *)s_; - fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; - struct { - fio_msg_s msg; - fio___pubsub_message_s *m; - uintptr_t flag; - } container = { - .msg = m->data, - .m = m, - }; - container.msg.io = s->io; - container.msg.udata = s->udata; - container.msg.is_json = !!(container.msg.is_json & FIO___PUBSUB_JSON); - s->on_message(&container.msg); - s->udata = container.msg.udata; - if (container.flag) - goto reschedule; - fio_subscription_free(s); - fio___pubsub_message_free(m); - return; -reschedule: - fio_queue_push(fio_srv_queue(), fio___subscription_on_message_task, s_, m_); +typedef struct { + fio_keystr_s nm; + void (*fn)(fio_io_s *); +} fio___io_tls_alpn_s; + +typedef struct { + fio_keystr_s nm; + fio_keystr_s public_cert_file; + fio_keystr_s private_key_file; + fio_keystr_s pk_password; +} fio___io_tls_cert_s; + +typedef struct { + fio_keystr_s nm; +} fio___io_tls_trust_s; + +#undef FIO_TYPEDEF_IMAP_REALLOC +#undef FIO_TYPEDEF_IMAP_FREE +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC(p, size_old, size, copy) realloc(p, size) +#define FIO_TYPEDEF_IMAP_FREE(ptr, len) free(ptr) +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE 0 + +#define FIO___IO_ALPN_HASH(o) ((uint16_t)fio_keystr_hash(o->nm)) +#define FIO___IO_ALPN_CMP(a, b) fio_keystr_is_eq(a->nm, b->nm) +#define FIO___IO_ALPN_VALID(o) fio_keystr_buf(&o->nm).len + +FIO_TYPEDEF_IMAP_ARRAY(fio___io_tls_alpn_map, + fio___io_tls_alpn_s, + uint16_t, + FIO___IO_ALPN_HASH, + FIO___IO_ALPN_CMP, + FIO___IO_ALPN_VALID) +FIO_TYPEDEF_IMAP_ARRAY(fio___io_tls_trust_map, + fio___io_tls_trust_s, + uint16_t, + FIO___IO_ALPN_HASH, + FIO___IO_ALPN_CMP, + FIO___IO_ALPN_VALID) +FIO_TYPEDEF_IMAP_ARRAY(fio___io_tls_cert_map, + fio___io_tls_cert_s, + uint16_t, + FIO___IO_ALPN_HASH, + FIO___IO_ALPN_CMP, + FIO_IMAP_ALWAYS_VALID) + +#undef FIO___IO_ALPN_HASH +#undef FIO___IO_ALPN_CMP +#undef FIO___IO_ALPN_VALID +#undef FIO_TYPEDEF_IMAP_REALLOC +#undef FIO_TYPEDEF_IMAP_FREE +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC +#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE + +struct fio_io_tls_s { + fio___io_tls_cert_map_s cert; + fio___io_tls_alpn_map_s alpn; + fio___io_tls_trust_map_s trust; + uint8_t trust_sys; /** Set to 1 if system certificate registry is trusted */ +}; + +#define FIO___RECURSIVE_INCLUDE 1 +#define FIO_REF_NAME fio_io_tls +#define FIO_REF_DESTROY(tls) \ + do { \ + FIO_IMAP_EACH(fio___io_tls_alpn_map, &tls.alpn, i) { \ + fio_keystr_destroy(&tls.alpn.ary[i].nm, FIO_STRING_FREE_KEY); \ + } \ + FIO_IMAP_EACH(fio___io_tls_trust_map, &tls.trust, i) { \ + fio_keystr_destroy(&tls.trust.ary[i].nm, FIO_STRING_FREE_KEY); \ + } \ + FIO_IMAP_EACH(fio___io_tls_cert_map, &tls.cert, i) { \ + fio_keystr_destroy(&tls.cert.ary[i].nm, FIO_STRING_FREE_KEY); \ + fio_keystr_destroy(&tls.cert.ary[i].public_cert_file, \ + FIO_STRING_FREE_KEY); \ + fio_keystr_destroy(&tls.cert.ary[i].private_key_file, \ + FIO_STRING_FREE_KEY); \ + fio_keystr_destroy(&tls.cert.ary[i].pk_password, FIO_STRING_FREE_KEY); \ + } \ + fio___io_tls_alpn_map_destroy(&tls.alpn); \ + fio___io_tls_trust_map_destroy(&tls.trust); \ + fio___io_tls_cert_map_destroy(&tls.cert); \ + } while (0) +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +/** Performs a `new` operation, returning a new `fio_io_tls_s` context. */ +SFUNC fio_io_tls_s *fio_io_tls_new(void) { + fio_io_tls_s *r = fio_io_tls_new2(); + FIO_ASSERT_ALLOC(r); + *r = (fio_io_tls_s){.trust_sys = 0}; + return r; } -/* returns the internal message object. */ -FIO_IFUNC fio___pubsub_message_s *fio___pubsub_msg2internal(fio_msg_s *msg) { - return *(fio___pubsub_message_s **)(msg + 1); +/** Performs a `dup` operation, increasing the object's reference count. */ +SFUNC fio_io_tls_s *fio_io_tls_dup(fio_io_tls_s *tls) { + return fio_io_tls_dup2(tls); } -/** Defers the current callback, so it will be called again for the message. */ -SFUNC void fio_pubsub_message_defer(fio_msg_s *msg) { - ((uintptr_t *)(msg + 1))[1] = 1; +/** Performs a `free` operation, reducing the reference count and freeing. */ +SFUNC void fio_io_tls_free(fio_io_tls_s *tls) { + if (!tls) + return; + fio_io_tls_free2(tls); } -/* distributes a message to all of a channel's subscribers */ -FIO_SFUNC void fio___pubsub_channel_deliver_task(void *ch_, void *m_) { - fio_channel_s *ch = (fio_channel_s *)ch_; - fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; - FIO_LIST_HEAD *head = (&ch->subscriptions); - _Bool is_history = !!(m->data.is_json & FIO___PUBSUB_REPLAY); - head += is_history; - if (m->data.io) { /* move as many `if` statements as possible out of loops. */ - if (is_history) { - FIO_LIST_EACH(fio_subscription_s, node, head, s) { - if (m->data.io != s->io && m->data.published >= s->replay_since) - fio_queue_push( - fio_srv_queue(), - (void (*)(void *, void *))fio___subscription_on_message_task, - fio_subscription_dup(s), - fio___pubsub_message_dup(m)); - } - } else { - FIO_LIST_EACH(fio_subscription_s, node, head, s) { - if (m->data.io != s->io) - fio_queue_push( - fio_srv_queue(), - (void (*)(void *, void *))fio___subscription_on_message_task, - fio_subscription_dup(s), - fio___pubsub_message_dup(m)); - } - } - } else { - if (is_history) { - FIO_LIST_EACH(fio_subscription_s, node, head, s) { - if (m->data.published >= s->replay_since) - fio_queue_push( - fio_srv_queue(), - (void (*)(void *, void *))fio___subscription_on_message_task, - fio_subscription_dup(s), - fio___pubsub_message_dup(m)); +/** Takes a parsed URL and optional TLS target and returns a TLS if needed. */ +SFUNC fio_io_tls_s *fio_io_tls_from_url(fio_io_tls_s *tls, fio_url_s url) { + /* test for TLS info in URL */ + fio_url_tls_info_s tls_info = fio_url_is_tls(url); + if (!tls_info.tls) + return tls; + + if (!tls && tls_info.tls) + tls = fio_io_tls_new(); + + if (tls_info.key.buf && tls_info.cert.buf) { + const char *tmp = NULL; + FIO_STR_INFO_TMP_VAR(host_tmp, 512); + FIO_STR_INFO_TMP_VAR(key_tmp, 128); + FIO_STR_INFO_TMP_VAR(cert_tmp, 128); + FIO_STR_INFO_TMP_VAR(pass_tmp, 128); + if (url.host.len < 512 && url.host.buf) + fio_string_write(&host_tmp, NULL, url.host.buf, url.host.len); + else + host_tmp.buf = NULL; + + if (tls_info.key.len < 124 && tls_info.cert.len < 124 && + tls_info.pass.len < 124) { + fio_string_write(&key_tmp, NULL, tls_info.key.buf, tls_info.key.len); + fio_string_write(&cert_tmp, NULL, tls_info.cert.buf, tls_info.cert.len); + if (tls_info.pass.len) + fio_string_write(&pass_tmp, NULL, tls_info.pass.buf, tls_info.pass.len); + else + pass_tmp.buf = NULL; + + if (tls_info.key.buf == + tls_info.cert.buf) { /* assume value is prefix / folder */ + if ((tmp = getenv(cert_tmp.buf))) { + fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); + if (buf_tmp.len < 124) { + key_tmp.len = cert_tmp.len = buf_tmp.len; + FIO_MEMCPY(key_tmp.buf, buf_tmp.buf, buf_tmp.len); + FIO_MEMCPY(cert_tmp.buf, buf_tmp.buf, buf_tmp.len); + } + } + fio_string_write(&key_tmp, NULL, "key.pem", 7); + fio_string_write(&cert_tmp, NULL, "cert.pem", 8); + } else { + if ((tmp = getenv(key_tmp.buf))) { + fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); + if (buf_tmp.len < 124) { + key_tmp.len = buf_tmp.len; + FIO_MEMCPY(key_tmp.buf, buf_tmp.buf, buf_tmp.len); + } + } + + if ((tmp = getenv(cert_tmp.buf))) { + fio_buf_info_s buf_tmp = FIO_BUF_INFO1((char *)tmp); + if (buf_tmp.len < 124) { + cert_tmp.len = buf_tmp.len; + FIO_MEMCPY(cert_tmp.buf, buf_tmp.buf, buf_tmp.len); + } + } + + if (tls_info.key.len < 5 || + (fio_buf2u32u(tls_info.key.buf + (tls_info.key.len - 4)) | + 0x20202020UL) != fio_buf2u32u(".pem")) { + fio_string_write(&key_tmp, NULL, ".pem", 4); + } + if (tls_info.cert.len < 5 || + (fio_buf2u32u(tls_info.cert.buf + (tls_info.cert.len - 4)) | + 0x20202020UL) != fio_buf2u32u(".pem")) { + fio_string_write(&cert_tmp, NULL, ".pem", 4); + } } + fio_io_tls_cert_add(tls, + host_tmp.buf, + cert_tmp.buf, + key_tmp.buf, + pass_tmp.buf); } else { - FIO_LIST_EACH(fio_subscription_s, node, head, s) { - fio_queue_push( - fio_srv_queue(), - (void (*)(void *, void *))fio___subscription_on_message_task, - fio_subscription_dup(s), - fio___pubsub_message_dup(m)); - } + FIO_LOG_ERROR("TLS files in `fio_io_listen` URL too long, " + "construct TLS object separately"); } } - fio___pubsub_message_free(m); - fio_channel_free(ch); + return tls; } -/** Callback called when a letter is destroyed (reference counting). */ -FIO_SFUNC void fio___pubsub_message_metadata_init(fio___pubsub_message_s *m); -/** distributes a message to all matching channels */ -FIO_SFUNC void fio___pubsub_message_deliver(fio___pubsub_message_s *m) { - fio___pubsub_message_metadata_init(m); /* metadata initialization */ - fio_str_info_s ch_name = - FIO_STR_INFO3(m->data.channel.buf, - m->data.channel.len, - FIO___PUBSUB_CHANNEL_ENCODE_CAPA(m->data.filter, 0)); - fio_channel_s **ch_ptr = fio___channel_map_node2key_ptr( - fio___channel_map_get_ptr(&FIO___PUBSUB_POSTOFFICE.channels, ch_name)); - if (ch_ptr) - fio_queue_push(fio_srv_queue(), - fio___pubsub_channel_deliver_task, - fio_channel_dup(ch_ptr[0]), - fio___pubsub_message_dup(m)); - FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.patterns, i) { - if (i.node->key->filter == m->data.filter && - FIO_PUBSUB_PATTERN_MATCH(i.key, ch_name)) - fio_queue_push(fio_srv_queue(), - fio___pubsub_channel_deliver_task, - fio_channel_dup(i.node->key), - fio___pubsub_message_dup(m)); +/** Adds a certificate a new SSL/TLS context / settings object (SNI support). */ +SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *t, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password) { + if (!t) + return t; + fio___io_tls_cert_s o = { + .nm = fio_keystr_init(FIO_STR_INFO1((char *)server_name), + FIO_STRING_ALLOC_KEY), + .public_cert_file = + fio_keystr_init(FIO_STR_INFO1((char *)public_cert_file), + FIO_STRING_ALLOC_KEY), + .private_key_file = + fio_keystr_init(FIO_STR_INFO1((char *)private_key_file), + FIO_STRING_ALLOC_KEY), + .pk_password = fio_keystr_init(FIO_STR_INFO1((char *)pk_password), + FIO_STRING_ALLOC_KEY), + }; + fio___io_tls_cert_s *old = fio___io_tls_cert_map_get(&t->cert, o); + if (old) + goto replace_old; + fio___io_tls_cert_map_set(&t->cert, o, 1); + return t; +replace_old: + fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); + fio_keystr_destroy(&old->public_cert_file, FIO_STRING_FREE_KEY); + fio_keystr_destroy(&old->private_key_file, FIO_STRING_FREE_KEY); + fio_keystr_destroy(&old->pk_password, FIO_STRING_FREE_KEY); + *old = o; + return t; +} + +/** + * Adds an ALPN protocol callback to the SSL/TLS context. + * + * The first protocol added will act as the default protocol to be selected. + * + * Except for the `tls` and `protocol_name` arguments, all arguments can be + * NULL. + */ +SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *t, + const char *protocol_name, + void (*on_selected)(fio_io_s *)) { + if (!t || !protocol_name) + return t; + if (!on_selected) + on_selected = fio___io_on_ev_mock; + size_t pr_name_len = strlen(protocol_name); + if (pr_name_len > 255) { + FIO_LOG_ERROR( + "fio_io_tls_alpn_add called with name longer than 255 chars!"); + return t; + } + fio___io_tls_alpn_s o = { + .nm = fio_keystr_init(FIO_STR_INFO2((char *)protocol_name, pr_name_len), + FIO_STRING_ALLOC_KEY), + .fn = on_selected, + }; + fio___io_tls_alpn_s *old = fio___io_tls_alpn_map_get(&t->alpn, o); + if (old) + goto replace_old; + fio___io_tls_alpn_map_set(&t->alpn, o, 1); + return t; +replace_old: + fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); + *old = o; + return t; +} + +/** Calls the `on_selected` callback for the `fio_io_tls_s` object. */ +SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *t, + const char *protocol_name, + size_t name_length, + fio_io_s *io) { + if (!t || !protocol_name) + return -1; + fio___io_tls_alpn_s seeking = { + .nm = fio_keystr_tmp(protocol_name, (uint32_t)name_length)}; + fio___io_tls_alpn_s *alpn = fio___io_tls_alpn_map_get(&t->alpn, seeking); + if (!alpn) { + FIO_LOG_DDEBUG2("TLS ALPN %.*s not found in %zu long list", + (int)name_length, + protocol_name, + t->alpn.count); + return -1; + } + alpn->fn(io); + return 0; +} + +/** + * Adds a certificate to the "trust" list, which automatically adds a peer + * verification requirement. + * + * If `public_cert_file` is `NULL`, adds the system's default trust registry. + * + * Note: when the `fio_io_tls_s` object is used for server connections, this + * will limit connections to clients that connect using a trusted certificate. + * + * fio_io_tls_trust_add(tls, "google-ca.pem" ); + */ +SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *t, + const char *public_cert_file) { + if (!t) + return t; + if (!public_cert_file) { + t->trust_sys = 1; + return t; } + fio___io_tls_trust_s o = { + .nm = fio_keystr_init(FIO_STR_INFO1((char *)public_cert_file), + FIO_STRING_ALLOC_KEY), + }; + fio___io_tls_trust_s *old = fio___io_tls_trust_map_get(&t->trust, o); + if (old) + goto replace_old; + fio___io_tls_trust_map_set(&t->trust, o, 1); + return t; +replace_old: + fio_keystr_destroy(&old->nm, FIO_STRING_FREE_KEY); + *old = o; + return t; +} + +/** + * Returns the number of `fio_io_tls_cert_add` instructions. + * + * This could be used when deciding if to add a NULL instruction (self-signed). + * + * If `fio_io_tls_cert_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls) { + return tls ? tls->cert.count : 0; +} + +/** + * Returns the number of registered ALPN protocol names. + * + * This could be used when deciding if protocol selection should be delegated to + * the ALPN mechanism, or whether a protocol should be immediately assigned. + * + * If no ALPN protocols are registered, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls) { + return tls ? tls->alpn.count : 0; } -FIO_SFUNC void fio___pubsub_message_deliver_task(void *m_, void *ignr_) { - fio___pubsub_message_deliver((fio___pubsub_message_s *)m_); - fio___pubsub_message_free((fio___pubsub_message_s *)m_); - (void)ignr_; +/** + * Returns the number of `fio_io_tls_trust_add` instructions. + * + * This could be used when deciding if to disable peer verification or not. + * + * If `fio_io_tls_trust_add` was never called, zero (0) is returned. + */ +SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls) { + return tls ? tls->trust.count : 0; } -/* ***************************************************************************** -Pub/Sub Message Type (internal data carrying structure) -***************************************************************************** */ - -/** Callback called when a letter is destroyed (reference counting). */ -FIO_SFUNC void fio___pubsub_message_metadata_init(fio___pubsub_message_s *m) { - if (fio_atomic_or(&m->metadata_is_initialized, 1)) { - return; +/** Calls callbacks for certificate, trust certificate and ALPN added. */ +void fio_io_tls_each___(void); /* IDE Marker*/ +SFUNC int fio_io_tls_each FIO_NOOP(fio_io_tls_each_s a) { + if (!a.tls) + return -1; + if (a.each_cert) { + FIO_IMAP_EACH(fio___io_tls_cert_map, &a.tls->cert, i) { + if (a.each_cert(&a, + fio_keystr_buf(&a.tls->cert.ary[i].nm).buf, + fio_keystr_buf(&a.tls->cert.ary[i].public_cert_file).buf, + fio_keystr_buf(&a.tls->cert.ary[i].private_key_file).buf, + fio_keystr_buf(&a.tls->cert.ary[i].pk_password).buf)) + return -1; + } } - fio_msg_s msg = m->data; - msg.is_json &= FIO___PUBSUB_JSON; - for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; ++i) { - if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { - m->metadata[i] = FIO___PUBSUB_POSTOFFICE.metadata[i].build(&msg); - continue; + if (a.each_alpn) { + FIO_IMAP_EACH(fio___io_tls_alpn_map, &a.tls->alpn, i) { + if (a.each_alpn(&a, + fio_keystr_buf(&a.tls->alpn.ary[i].nm).buf, + a.tls->alpn.ary[i].fn)) + return -1; } - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); } -} - -/** Callback called when a letter is destroyed (reference counting). */ -FIO_SFUNC void fio___pubsub_message_metadata_free(fio___pubsub_message_s *m) { - if (!m->metadata_is_initialized) - return; - for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; ++i) { - if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { - FIO___PUBSUB_POSTOFFICE.metadata[i].cleanup(m->metadata[i]); - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + if (a.each_trust) { + if (a.tls->trust_sys && a.each_trust(&a, NULL)) + return -1; + FIO_IMAP_EACH(fio___io_tls_trust_map, &a.tls->trust, i) { + if (a.each_trust(&a, fio_keystr_buf(&a.tls->trust.ary[i].nm).buf)) + return -1; } - fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); } - m->metadata_is_initialized = 0; -} - -/** Callback called when a letter is destroyed (reference counting). */ -FIO_SFUNC void fio___pubsub_message_on_destroy(fio___pubsub_message_s *m) { - fio___pubsub_message_metadata_free(m); + return 0; } -FIO_IFUNC fio___pubsub_message_s *fio___pubsub_message_alloc(void *header) { - fio___pubsub_message_s *m; - const size_t channel_len = fio_buf2u16_le((char *)header + 18); - const size_t message_len = fio_buf2u24_le((char *)header + 20); - m = fio___pubsub_message_new(((channel_len + message_len) << 1) + - FIO___PUBSUB_MESSAGE_OVERHEAD); - FIO_ASSERT_ALLOC(m); - m->data = (fio_msg_s){ - .udata = m->buf + channel_len + message_len + 2, - .channel = FIO_BUF_INFO2(m->buf, channel_len), - .message = FIO_BUF_INFO2(m->buf + channel_len + 1, message_len), +/** If `NULL` returns current default, otherwise sets it. */ +SFUNC fio_io_functions_s fio_io_tls_default_functions(fio_io_functions_s *f) { + static fio_io_functions_s default_io_functions = { + .build_context = fio___io_func_default_build_context, + .start = fio___io_on_ev_mock, + .read = fio___io_func_default_read, + .write = fio___io_func_default_write, + .flush = fio___io_func_default_flush, + .finish = fio___io_func_default_finish, + .cleanup = fio___io_func_default_cleanup, }; - return m; + if (!f) + return default_io_functions; + if (!f->build_context) + f->build_context = fio___io_func_default_build_context; + if (!f->start) + f->start = fio___io_on_ev_mock; + if (!f->read) + f->read = fio___io_func_default_read; + if (!f->write) + f->write = fio___io_func_default_write; + if (!f->flush) + f->flush = fio___io_func_default_flush; + if (!f->finish) + f->finish = fio___io_func_default_finish; + if (!f->cleanup) + f->cleanup = fio___io_func_default_cleanup; + default_io_functions = *f; + return default_io_functions; } -FIO_IFUNC fio___pubsub_message_s *fio___pubsub_message_author( - fio_publish_args_s args) { - fio___pubsub_message_s *m = - fio___pubsub_message_new(((args.message.len + args.channel.len) << 1) + - FIO___PUBSUB_MESSAGE_OVERHEAD); - FIO_ASSERT_ALLOC(m); - m->data = (fio_msg_s){ - .io = args.from, - .id = args.id ? args.id : fio_rand64(), - .published = args.published ? args.published - : (uint64_t)fio_time2milli(fio_time_real()), - .channel = FIO_BUF_INFO2(m->buf, args.channel.len), - .message = FIO_BUF_INFO2(m->buf + args.channel.len + 1, args.message.len), - .filter = args.filter, - .is_json = args.is_json, - }; - if (args.channel.len) - FIO_MEMCPY(m->data.channel.buf, args.channel.buf, args.channel.len); - m->data.channel.buf[args.channel.len] = 0; - if (args.message.buf) - FIO_MEMCPY(m->data.message.buf, args.message.buf, args.message.len); - m->data.message.buf[args.message.len] = 0; - return m; -} +/* ***************************************************************************** +IO Async Queues - Worker Threads for non-IO tasks +***************************************************************************** */ -FIO_SFUNC void fio___pubsub_message_encrypt(fio___pubsub_message_s *m) { - if (m->data.udata) - return; - const void *k = fio___pubsub_secret_key(m->data.id); - const uint64_t nonce[2] = {fio_risky_num(m->data.id, 0), m->data.published}; - uint8_t *pos = (uint8_t *)(m->data.message.buf + m->data.message.len + 1); - m->data.udata = (void *)pos; - fio_u2buf64_le(pos, m->data.id); - pos += 8; - fio_u2buf64_le(pos, m->data.published); - pos += 8; - fio_u2buf16_le(pos, (uint16_t)m->data.filter); - pos += 2; - fio_u2buf16_le(pos, (uint16_t)m->data.channel.len); - pos += 2; - fio_u2buf24_le(pos, (uint32_t)m->data.message.len); - pos += 3; - *(pos++) = m->data.is_json; - const size_t enc_len = m->data.channel.len + m->data.message.len + 2; - FIO_MEMCPY(pos, m->data.channel.buf, enc_len); - if (enc_len == 2) - return; - pos += enc_len; - fio_chacha20_poly1305_enc( - pos, - (void *)((char *)(m->data.udata) + FIO___PUBSUB_MESSAGE_HEADER), - m->data.channel.len + m->data.message.len + 2, - m->data.udata, - FIO___PUBSUB_MESSAGE_HEADER, - k, - nonce); -} +FIO_SFUNC void fio___io_async_start(fio_io_async_s *q) { + if (!q->count) + goto no_worker_threads; + q->q = &q->queue; + if (q->count > 4095) + goto failed; + fio_queue_workers_stop(&q->queue); + if (fio_queue_workers_add(&q->queue, (size_t)q->count)) + goto failed; + return; -FIO_SFUNC int fio___pubsub_message_decrypt(fio___pubsub_message_s *m) { - if (m->data.id) - return 0; - if (!m->data.udata) - return -1; - uint8_t *pos = (uint8_t *)(m->data.udata); - m->data.id = fio_buf2u64_le(pos); - pos += 8; - m->data.published = fio_buf2u64_le(pos); - pos += 8; - m->data.filter = fio_buf2u16_le(pos); - pos += 2; - m->data.channel = FIO_BUF_INFO2(m->buf, fio_buf2u16_le(pos)); - pos += 2; - m->data.message = - FIO_BUF_INFO2(m->buf + m->data.channel.len + 1, fio_buf2u24_le(pos)); - pos += 3; - m->data.is_json = *(pos++); - const void *k = fio___pubsub_secret_key(m->data.id); - uint64_t nonce[2] = {fio_risky_num(m->data.id, 0), m->data.published}; - const size_t enc_len = m->data.channel.len + m->data.message.len + 2; - FIO_MEMCPY(m->buf, pos, enc_len); - if (enc_len == 2) - return 0; - pos += enc_len; - return fio_chacha20_poly1305_dec(pos, - m->buf, - m->data.channel.len + m->data.message.len + - 2, - m->data.udata, - FIO___PUBSUB_MESSAGE_HEADER, - k, - nonce); +failed: + FIO_LOG_ERROR("IO Async Queue couldn't spawn threads!"); +no_worker_threads: + q->q = fio_io_queue(); + fio_queue_perform_all(&q->queue); +} +FIO_SFUNC void fio___io_async_stop(fio_io_async_s *q) { + q->q = fio_io_queue(); + fio_queue_workers_stop(&q->queue); + fio_queue_perform_all(&q->queue); + fio_queue_destroy(&q->queue); } -FIO_IFUNC void fio___pubsub_message_is_dirty(fio___pubsub_message_s *m) { - m->data.udata = NULL; +/** + * Attaches an IO Async Queue for use in multi-threaded (non IO) tasks. + * + * This function can be called multiple times for the same (or other) queue, as + * long as the async queue (`fio_io_async_s`) was previously initialized using + * `FIO_IO_ASYN_INIT` or zeroed out. i.e.: + * + * static fio_io_async_s SLOW_HTTP_TASKS = FIO_IO_ASYN_INIT; + * fio_io_async_attach(&SLOW_HTTP_TASKS, 32); + */ +SFUNC void fio_io_async_attach(fio_io_async_s *q, uint32_t threads) { + if (!q) + return; + if (!q->node.next) { + *q = (fio_io_async_s){ + .q = fio_io_queue(), + .count = threads, + .queue = FIO_QUEUE_STATIC_INIT(q->queue), + .node = FIO_LIST_INIT(q->node), + }; + FIO_LIST_PUSH(&FIO___IO.async, &q->node); + } + q->count = threads; + if (fio_io_is_running()) + fio___io_async_start(q); } /* ***************************************************************************** -Pub/Sub Message Object - IO helpers +Managing data after a fork ***************************************************************************** */ - -FIO_IFUNC void fio___pubsub_message_write2io(fio_s *io, void *m_) { - fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; - if (io == m->data.io) - return; - FIO_LOG_DDEBUG2("(%d) pub/sub sending IPC/peer message.", fio_srv_pid()); - fio___pubsub_message_encrypt(m); - fio_write2(io, - .buf = fio___pubsub_message_dup(m), - .len = (m->data.message.len + m->data.channel.len + - FIO___PUBSUB_MESSAGE_OVERHEAD_NET), - .offset = ((uintptr_t)(m->data.udata) - (uintptr_t)(m)), - .dealloc = (void (*)(void *))fio___pubsub_message_free); +FIO_SFUNC void fio___io_after_fork(void *ignr_) { + (void)ignr_; + FIO___IO.pid = fio_thread_getpid(); + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_queue_perform_all(&FIO___IO.queue); + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + fio_io_close_now(io); + } + } + fio_queue_perform_all(&FIO___IO.queue); + fio_queue_destroy(&FIO___IO.queue); } -/* A callback for IO subscriptions - sends raw message data. */ -FIO_SFUNC void FIO_ON_MESSAGE_SEND_MESSAGE(fio_msg_s *msg) { - if (!msg || !msg->message.len) - return; - fio___pubsub_message_s *m = fio___pubsub_msg2internal(msg); - fio_write2(msg->io, - .buf = fio___pubsub_message_dup(m), - .len = msg->message.len, - .offset = (size_t)(msg->message.buf - (char *)m), - .dealloc = (void (*)(void *))fio___pubsub_message_free); +FIO_SFUNC void fio___io_cleanup_at_exit(void *ignr_) { + fio___io_after_fork(ignr_); + fio_poll_destroy(&FIO___IO.poll); + fio___io_env_safe_destroy(&FIO___IO.env); +#if FIO_VALIDITY_MAP_USE + fio_validity_map_destroy(&FIO___IO.valid); +#if FIO_VALIDATE_IO_MUTEX + fio_thread_mutex_destroy(&FIO___IO.valid_lock); +#endif +#endif /* FIO_VALIDATE_IO_MUTEX / FIO_VALIDITY_MAP_USE */ + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_queue_perform_all(&FIO___IO.queue); + fio_timer_destroy(&FIO___IO.timer); + fio_queue_perform_all(&FIO___IO.queue); } /* ***************************************************************************** -Pub/Sub Message Routing +Initializing IO Reactor State ***************************************************************************** */ +FIO_CONSTRUCTOR(fio___io) { + fio_queue_init(&FIO___IO.queue); + FIO___IO.protocols = FIO_LIST_INIT(FIO___IO.protocols); + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + FIO___IO.root_pid = FIO___IO.pid = fio_thread_getpid(); + FIO___IO.async = FIO_LIST_INIT(FIO___IO.async); + fio___io_init_protocol(&FIO___IO_MOCK_PROTOCOL, 0); + fio_poll_init(&FIO___IO.poll, + .on_data = fio___io_poll_on_data_schd, + .on_ready = fio___io_poll_on_ready_schd, + .on_close = fio___io_poll_on_close_schd); + fio___io_init_protocol_test(&FIO___IO_MOCK_PROTOCOL, 0); + fio_state_callback_add(FIO_CALL_IN_CHILD, fio___io_after_fork, NULL); + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___io_cleanup_at_exit, NULL); +} -FIO_SFUNC void fio___pubsub_message_route(fio___pubsub_message_s *m) { - fio___pubsub_message_parser_s *p; - unsigned flags = m->data.is_json; - FIO_LOG_DDEBUG2("(%d) pub/sub routing message (%x)", - fio_srv_pid(), - (int)m->data.is_json); - - if (flags & FIO___PUBSUB_SPECIAL) - goto is_special_message; - - if ((FIO___PUBSUB_POSTOFFICE.filter.publish & flags)) - fio_queue_push(fio_srv_queue(), - fio___pubsub_message_deliver_task, - fio___pubsub_message_dup(m)); - - if ((FIO___PUBSUB_POSTOFFICE.filter.local & flags)) - fio_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, - fio___pubsub_message_write2io, - m); - - if ((FIO___PUBSUB_POSTOFFICE.filter.remote & flags)) - fio_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.remote, - fio___pubsub_message_write2io, - m); - return; - -is_special_message: - FIO_LOG_DDEBUG2("(%d) pub/sub internal subscription/ID message received", - fio_srv_pid()); - switch (flags) { - case FIO___PUBSUB_SPECIAL: /* TODO: run generic command on root */ break; - case FIO___PUBSUB_SUB: - fio_subscribe(.io = m->data.io, - .channel = m->data.channel, - .on_message = fio___subscription_mock_cb, - .filter = m->data.filter, - .is_pattern = (uint8_t)(m->data.id - 1)); - return; - case FIO___PUBSUB_UNSUB: - fio_unsubscribe(.io = m->data.io, - .channel = m->data.channel, - .on_message = fio___subscription_mock_cb, - .filter = m->data.filter, - .is_pattern = (uint8_t)(m->data.id - 1)); - return; +/* ***************************************************************************** +IO Types Finish +***************************************************************************** */ +#endif /* FIO_IO */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** - case FIO___PUBSUB_IDENTIFY: - p = (fio___pubsub_message_parser_s *)fio_udata_get(m->data.io); - if (p) { - p->uuid[0] = m->data.id; - p->uuid[1] = m->data.published; - fio___pubsub_broadcast_connected_set( - &FIO___PUBSUB_POSTOFFICE.remote_uuids, - p->uuid[0], - p->uuid[1]); - } - FIO_LOG_INFO("(cluster) identified new peer (%zu connections)", - fio___pubsub_broadcast_connected_count( - &FIO___PUBSUB_POSTOFFICE.remote_uuids)); - return; - case FIO___PUBSUB_FORWARDER: /* fall through */ - case (FIO___PUBSUB_FORWARDER | FIO___PUBSUB_JSON): - if (FIO___PUBSUB_POSTOFFICE.filter.remote) { /* root process */ - fio___pubsub_message_is_dirty(m); - m->data.message.len -= 8; - m->data.is_json &= FIO___PUBSUB_JSON; - fio_pubsub_engine_s *e = (fio_pubsub_engine_s *)(uintptr_t)fio_buf2u64u( - m->data.message.buf + m->data.message.len); - m->data.message.buf[m->data.message.len] = 0; - e->publish(e, &m->data); - } else { /* child process */ - fio_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, - fio___pubsub_message_write2io, - m); - } - return; + IO Reactor - an Evented IO Reactor, Single-Threaded - case FIO___PUBSUB_HISTORY_START: - FIO_LOG_DDEBUG2("(%d) pub/sub internal history start message received", - fio_srv_pid()); - /* TODO! */ - return; - case FIO___PUBSUB_HISTORY_END: - FIO_LOG_DDEBUG2("(%d) pub/sub internal history end message received", - fio_srv_pid()); - /* TODO! */ - return; - } - return; -} +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) && \ + !defined(H___FIO_IO_REACTOR___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) +#define H___FIO_IO_REACTOR___H /* ***************************************************************************** -Pub/Sub - Publish +The IO Reactor Cycle (the actual work) ***************************************************************************** */ -FIO_SFUNC void fio___publish_message_task(void *m_, void *ignr_) { - (void)ignr_; - fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; - fio___pubsub_message_route(m); - fio___pubsub_message_free(m); +static void fio___io_signal_handle(int sig, void *flg) { + ((uint8_t *)flg)[0] = 1; + (void)sig; } -/** Publishes a message to the relevant subscribers (if any). */ -void fio_publish___(void); /* SublimeText marker*/ -void fio_publish FIO_NOOP(fio_publish_args_s args) { - if (FIO_UNLIKELY(args.channel.len > 0xFFFFUL)) { - FIO_LOG_ERROR("(%d) pub/sub channel name too long (%zu bytes)", - fio_srv_pid(), - args.channel.len); - return; - } - if (FIO_UNLIKELY(args.message.len > 0xFFFFFFUL)) { - FIO_LOG_ERROR("(%d) pub/sub message payload too large (%zu bytes)", - fio_srv_pid(), - args.message.len); - return; +FIO_SFUNC void fio___io_tick(int timeout) { + static size_t performed_idle = 0; + size_t idle_round = (fio_poll_review(&FIO___IO.poll, timeout) == 0); + performed_idle &= idle_round; + idle_round &= (timeout > 0); + idle_round ^= performed_idle; + if ((idle_round & !FIO___IO.stop)) { + fio_state_callback_force(FIO_CALL_ON_IDLE); + performed_idle = 1; } - fio___pubsub_message_s *m; - fio_msg_s msg; + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_timer_push2queue(&FIO___IO.queue, &FIO___IO.timer, FIO___IO.tick); + for (size_t i = 0; i < 2048; ++i) + if (fio_queue_perform(&FIO___IO.queue)) + break; + fio___io_review_timeouts(); + fio_signal_review(); +} - if (!args.engine) { - args.engine = FIO_PUBSUB_DEFAULT; - if (!args.engine) - args.engine = FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; - if (args.filter < 0) - args.engine = FIO_PUBSUB_LOCAL; +FIO_SFUNC void fio___io_run_async_as_sync(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + unsigned repeat = 0; + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, pos) { + fio_queue_task_s t = fio_queue_pop(&pos->queue); + if (!t.fn) + continue; + t.fn(t.udata1, t.udata2); + repeat = 1; } - if ((uintptr_t)args.engine > 0xFFUL) - goto external_engine; - - m = fio___pubsub_message_author(args); - m->data.is_json = ((!!args.is_json) | ((uint8_t)(uintptr_t)args.engine)); - - fio_srv_defer(fio___publish_message_task, m, NULL); - return; + if (repeat) + fio_queue_push(&FIO___IO.queue, fio___io_run_async_as_sync); +} -external_engine: +FIO_SFUNC void fio___io_shutdown_task(void *shutdown_start_, void *a2) { + intptr_t shutdown_start = (intptr_t)shutdown_start_; + if (shutdown_start + FIO_IO_SHUTDOWN_TIMEOUT < FIO___IO.tick || + FIO_LIST_IS_EMPTY(&FIO___IO.protocols)) + return; + fio___io_tick(fio_queue_count(&FIO___IO.queue) ? 0 : 100); + fio_queue_push(&FIO___IO.queue, fio___io_run_async_as_sync); + fio_queue_push(&FIO___IO.queue, fio___io_shutdown_task, shutdown_start_, a2); +} - msg.message = args.message; - args.message.buf = NULL; - args.message.len += 8; +FIO_SFUNC void fio___io_shutdown(void) { + /* collect tick for shutdown start, to monitor for possible timeout */ + int64_t shutdown_start = FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + size_t connected = 0; + /* first notify that shutdown is starting */ + fio_state_callback_force(FIO_CALL_ON_SHUTDOWN); + /* preform on_shutdown callback for each connection and close */ + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + pr->on_shutdown(io); /* TODO / FIX: move callback to task? */ + fio_io_close(io); /* TODO / FIX: skip close on return value? */ + ++connected; + } + } + FIO_LOG_DEBUG2("(%d) IO Reactor shutting down with %zu connected clients", + fio_io_pid(), + connected); + /* cycle while connections exist. */ + fio_queue_push(&FIO___IO.queue, + fio___io_shutdown_task, + (void *)(intptr_t)shutdown_start, + NULL); + fio_queue_perform_all(&FIO___IO.queue); + /* in case of timeout, force close remaining connections. */ + connected = 0; + FIO_LIST_EACH(fio_io_protocol_s, + reserved.protocols, + &FIO___IO.protocols, + pr) { + FIO_LIST_EACH(fio_io_s, node, &pr->reserved.ios, io) { + fio_io_close_now(io); + ++connected; + } + } + FIO_LOG_DEBUG2("(%d) IO Reactor shutdown timeout/done with %zu clients", + fio_io_pid(), + connected); + /* perform remaining tasks. */ + fio_queue_perform_all(&FIO___IO.queue); +} - m = fio___pubsub_message_author(args); - m->data.is_json = ((!!args.is_json) | ((uint8_t)FIO___PUBSUB_FORWARDER)); - FIO_MEMCPY(m->data.message.buf, msg.message.buf, msg.message.len); - fio_u2buf64u(m->data.message.buf + msg.message.len, (uintptr_t)args.engine); - fio_srv_defer(fio___publish_message_task, m, NULL); +FIO_SFUNC void fio___io_work_task(void *ignr_1, void *ignr_2) { + if (FIO___IO.stop) + return; + fio___io_tick(fio_queue_count(&FIO___IO.queue) ? 0 : 500); + fio_queue_push(&FIO___IO.queue, fio___io_work_task, ignr_1, ignr_2); } -/* ***************************************************************************** -Pub/Sub Message on-the-wire parsing -***************************************************************************** */ +FIO_SFUNC void fio___io_work(int is_worker) { + FIO___IO.is_worker = is_worker; + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_start(q); + } -FIO_IFUNC void fio___pubsub_message_parse( - fio_s *io, - void (*cb)(fio_s *, fio___pubsub_message_s *)) { - fio___pubsub_message_parser_s *parser = - (fio___pubsub_message_parser_s *)fio_udata_get(io); - if (!parser) - return; - size_t existing = parser->len; - if (!parser->msg) { - while (existing < FIO___PUBSUB_MESSAGE_HEADER) { /* get message length */ - size_t consumed = fio_read(io, - parser->buf + existing, - FIO___PUBSUB_MESSAGE_OVERHEAD_NET - existing); - if (!consumed) { - parser->len = existing; - return; - } - existing += consumed; - } - parser->msg = fio___pubsub_message_alloc(parser->buf); - FIO_MEMCPY(parser->msg->data.udata, parser->buf, existing); + fio_queue_perform_all(&FIO___IO.queue); + if (is_worker) { + fio_state_callback_force(FIO_CALL_ON_START); } - /* known message length, read to end and publish */ - fio___pubsub_message_s *m = parser->msg; - const size_t needed = m->data.channel.len + m->data.message.len + - FIO___PUBSUB_MESSAGE_OVERHEAD_NET; - FIO_LOG_DDEBUG2("(%d) pub/sub parsing IPC/peer message (%zu/%zu bytes)", - fio_srv_pid(), - existing, - needed); - while (existing < needed) { - size_t consumed = - fio_read(io, (char *)m->data.udata + existing, needed - existing); - if (!consumed) { - parser->len = existing; - return; - } - existing += consumed; + fio___io_wakeup_init(); + fio_queue_push(&FIO___IO.queue, fio___io_work_task); + fio_queue_perform_all(&FIO___IO.queue); + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_stop(q); } - parser->msg = NULL; - parser->len = 0; - m->data.io = io; - if (fio___pubsub_message_decrypt(m)) { - FIO_LOG_SECURITY("(%d) pub/sub message decryption error", fio_srv_pid()); - fio_close_now(io); - } else { - cb(io, m); + fio___io_shutdown(); + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_stop(q); } - fio___pubsub_message_free(m); - return; /* consume no more than 1 message at a time */ + fio_queue_perform_all(&FIO___IO.queue); + fio_state_callback_force(FIO_CALL_ON_STOP); + fio_queue_perform_all(&FIO___IO.queue); + FIO___IO.workers = 0; } /* ***************************************************************************** -Pub/Sub Protocols +Worker Forking ***************************************************************************** */ +static void fio___io_spawn_worker(void *ignr_1, void *ignr_2); -FIO_SFUNC void fio___pubsub_on_message_master(fio_s *io, - fio___pubsub_message_s *msg) { - fio___pubsub_message_route(msg); - (void)io; -} -FIO_SFUNC void fio___pubsub_on_message_worker(fio_s *io, - fio___pubsub_message_s *msg) { - fio___pubsub_message_route(msg); - (void)io; -} -FIO_SFUNC void fio___pubsub_on_message_remote(fio_s *io, - fio___pubsub_message_s *msg) { - fio___pubsub_message_map_s *map = &FIO___PUBSUB_POSTOFFICE.remote_messages; - map += !!(msg->data.is_json & FIO___PUBSUB_REPLAY); - fio___pubsub_message_s *existing = fio___pubsub_message_map_set(map, msg); - if (existing != msg) - return; /* already received */ - fio___pubsub_message_route(msg); - (void)io; -} -FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_s *io) { - fio_udata_set(io, fio___pubsub_message_parser_new()); -} -FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_s *io) { - fio___pubsub_message_parse(io, fio___pubsub_on_message_master); -} -FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_s *io) { - fio___pubsub_message_parse(io, fio___pubsub_on_message_worker); +static void fio___io_wait_for_worker(void *thr_) { + fio_thread_t t = (fio_thread_t)thr_; + fio_thread_join(&t); } -FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_s *io) { - fio___pubsub_message_parse(io, fio___pubsub_on_message_remote); + +/** Worker sentinel */ +static void *fio___io_worker_sentinel(void *pid_data) { +#ifdef WEXITSTATUS + fio_thread_pid_t pid = (fio_thread_pid_t)(uintptr_t)pid_data; + int status = 0; + (void)status; + fio_thread_t thr = fio_thread_current(); + fio_state_callback_add(FIO_CALL_ON_STOP, + fio___io_wait_for_worker, + (void *)thr); + if (fio_thread_waitpid(pid, &status, 0) != pid && !FIO___IO.stop) + FIO_LOG_ERROR("waitpid failed, worker re-spawning might fail."); + if (!WIFEXITED(status) || WEXITSTATUS(status)) { + FIO_LOG_WARNING("abnormal worker exit detected"); + fio_state_callback_force(FIO_CALL_ON_CHILD_CRUSH); + } + if (!FIO___IO.stop) { + FIO_ASSERT_DEBUG( + 0, + "DEBUG mode prevents worker re-spawning, now crashing parent."); + fio_state_callback_remove(FIO_CALL_ON_STOP, + fio___io_wait_for_worker, + (void *)thr); + fio_thread_detach(&thr); + fio___io_defer_no_wakeup(fio___io_spawn_worker, (void *)thr, NULL); + } +#else /* Non POSIX? no `fork`? no fio_thread_waitpid? */ + FIO_ASSERT( + 0, + "facil.io doesn't know how to spawn and wait on workers on this system."); +#endif + return NULL; } -FIO_SFUNC void fio___pubsub_protocol_on_close(void *udata) { - fio___pubsub_message_parser_s *p = (fio___pubsub_message_parser_s *)udata; - if (!fio_srv_is_master()) - fio_srv_stop(); - if (!p) + +static void fio___io_spawn_worker(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + fio_thread_t t; + fio_signal_review(); + + if (FIO___IO.stop || !fio_io_is_master()) return; - if (p->uuid[0] || p->uuid[1]) { - // TODO!: fio___pubsub_broadcast_hello(fio_s *io) ? - fio___pubsub_broadcast_connected_remove( - &FIO___PUBSUB_POSTOFFICE.remote_uuids, - p->uuid[0], - p->uuid[1], - NULL); - FIO_LOG_INFO("(cluster) lost peer connection (%zu connections)", - fio___pubsub_broadcast_connected_count( - &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + if (fio_atomic_or_fetch(&FIO___IO.stop, 2) != 2) + return; + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_stop(q); } - fio___pubsub_message_parser_free(p); -} + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + fio_state_callback_force(FIO_CALL_BEFORE_FORK); + /* do not allow master tasks to run in worker */ + fio_queue_perform_all(&FIO___IO.queue); + /* perform actual fork */ + fio_thread_pid_t pid = fio_thread_fork(); + FIO_ASSERT(pid != (fio_thread_pid_t)-1, "system call `fork` failed."); + if (!pid) + goto is_worker_process; + fio_state_callback_force(FIO_CALL_AFTER_FORK); + fio_state_callback_force(FIO_CALL_IN_MASTER); + if (fio_thread_create(&t, fio___io_worker_sentinel, (void *)(uintptr_t)pid)) { + FIO_LOG_FATAL( + "sentinel thread creation failed, no worker will be spawned."); + fio_io_stop(); + } + if (!fio_atomic_xor_fetch(&FIO___IO.stop, 2)) + fio___io_defer_no_wakeup(fio___io_work_task, NULL, NULL); + return; -static void fio___pubsub_protocol_on_timeout(fio_s *io) { - static const uint8_t ping_msg[FIO___PUBSUB_MESSAGE_OVERHEAD] = { - [23] = FIO___PUBSUB_PING}; - fio_write2(io, .buf = (void *)ping_msg, .len = FIO___PUBSUB_MESSAGE_OVERHEAD); +is_worker_process: + FIO___IO.pid = fio_thread_getpid(); + FIO___IO.is_worker = 1; + FIO_LOG_INFO("(%d) worker starting up.", fio_io_pid()); + fio_state_callback_force(FIO_CALL_AFTER_FORK); + fio_state_callback_force(FIO_CALL_IN_CHILD); + if (!fio_atomic_xor_fetch(&FIO___IO.stop, 2)) + fio___io_work(1); + FIO_LOG_INFO("(%d) worker exiting.", fio_io_pid()); + exit(0); } /* ***************************************************************************** -Pub/Sub Engine Support Implementation +Starting / Stopping the IO Reactor ***************************************************************************** */ -static void fio___pubsub_mock_detached(const fio_pubsub_engine_s *eng) { - (void)eng; -} -static void fio___pubsub_mock_sub_unsub(const fio_pubsub_engine_s *eng, - fio_buf_info_s channel, - int16_t filter) { - (void)eng, (void)channel, (void)filter; -} -static void fio___pubsub_mock_publish(const fio_pubsub_engine_s *eng, - fio_msg_s *msg) { - (void)eng, (void)msg; /* TODO:? sensible default? publish to cluster? */ +/** Adds `workers` amount of workers to the root IO reactor process. */ +SFUNC void fio_io_add_workers(int workers) { + if (!workers || !fio_io_is_master()) + return; + FIO_LOG_INFO("(%d) spawning %d workers.", fio_io_pid(), workers); + for (int i = 0; i < workers; ++i) + fio___io_defer_no_wakeup(fio___io_spawn_worker, NULL, NULL); } -static void fio___pubsub_attach_task(void *engine_, void *ignr_) { - (void)ignr_; - fio_pubsub_engine_s *engine = (fio_pubsub_engine_s *)engine_; - if (!engine->detached) - engine->detached = fio___pubsub_mock_detached; - if (!engine->subscribe) - engine->subscribe = fio___pubsub_mock_sub_unsub; - if (!engine->unsubscribe) - engine->unsubscribe = fio___pubsub_mock_sub_unsub; - if (!engine->psubscribe) - engine->psubscribe = fio___pubsub_mock_sub_unsub; - if (!engine->punsubscribe) - engine->punsubscribe = fio___pubsub_mock_sub_unsub; - if (!engine->publish) - engine->publish = fio___pubsub_mock_publish; - fio___pubsub_engines_set(&FIO___PUBSUB_POSTOFFICE.engines, engine); - FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.channels, i) { - engine->subscribe(engine, - FIO_BUF_INFO2(i.key.buf, i.key.len), - (i.key.capa >> 16)); - } - FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.patterns, i) { - engine->psubscribe(engine, - FIO_BUF_INFO2(i.key.buf, i.key.len), - (i.key.capa >> 16)); - } -} +/** Starts the IO reactor, using optional `workers` processes. Will BLOCK! */ +SFUNC void fio_io_start(int workers) { + FIO___IO.stop = 0; + FIO___IO.workers = fio_io_workers(workers); + workers = (int)FIO___IO.workers; + FIO___IO.is_worker = !workers; + fio_sock_maximize_limits(0); -FIO_SFUNC void fio___pubsub_detach_task(void *engine, void *ignr_) { - (void)ignr_; - fio_pubsub_engine_s *e = (fio_pubsub_engine_s *)engine; - fio___pubsub_engines_remove(&FIO___PUBSUB_POSTOFFICE.engines, e, NULL); -} + FIO_LIST_EACH(fio_io_async_s, node, &FIO___IO.async, q) { + fio___io_async_start(q); + } -/** Attaches an engine, so it's callback can be called by facil.io. */ -SFUNC void fio_pubsub_attach(fio_pubsub_engine_s *engine) { - if (!engine) - return; - fio_srv_defer(fio___pubsub_attach_task, engine, NULL); + fio_state_callback_force(FIO_CALL_PRE_START); + fio_queue_perform_all(&FIO___IO.queue); + fio_signal_monitor(SIGINT, fio___io_signal_handle, (void *)&FIO___IO.stop); + fio_signal_monitor(SIGTERM, fio___io_signal_handle, (void *)&FIO___IO.stop); +#ifdef SIGPIPE + fio_signal_monitor(SIGPIPE, NULL, NULL); +#endif + FIO___IO.tick = FIO___IO_GET_TIME_MILLI(); + if (workers) { + FIO_LOG_INFO("(%d) spawning %d workers.", fio_io_root_pid(), workers); + for (int i = 0; i < workers; ++i) { + fio___io_spawn_worker(NULL, NULL); + } + } else { + FIO_LOG_DEBUG2("(%d) starting facil.io IO reactor in single process mode.", + fio_io_root_pid()); + } + fio___io_work(!workers); + fio_signal_forget(SIGINT); + fio_signal_forget(SIGTERM); +#ifdef SIGPIPE + fio_signal_forget(SIGPIPE); +#endif + fio_queue_perform_all(&FIO___IO.queue); } -/** Schedules an engine for Detachment, so it could be safely destroyed. */ -SFUNC void fio_pubsub_detach(fio_pubsub_engine_s *engine) { - fio_queue_push(fio_srv_queue(), fio___pubsub_detach_task, engine, NULL); +/** Returns the number or workers the IO reactor will actually run. */ +SFUNC uint16_t fio_io_workers(int workers) { + if (workers < 0) { + long cores = -1; +#ifdef _SC_NPROCESSORS_ONLN + cores = sysconf(_SC_NPROCESSORS_ONLN); +#endif /* _SC_NPROCESSORS_ONLN */ + if (cores == -1L) { + cores = 8; + FIO_LOG_WARNING( + "fio_io_start / fio_io_workers called with negative value for worker " + "count, but auto-detect failed, assuming %d CPU cores", + cores); + } + workers = (int)(cores / (0 - workers)); + workers += !workers; + } + return (uint16_t)workers; } /* ***************************************************************************** -Channel Creation / Destruction Callback (notifying engines) +Listening to Incoming Connections ***************************************************************************** */ -/** Callback for when a channel is created. */ -FIO_IFUNC void fio___channel_on_create(fio_channel_s *ch) { - fio_buf_info_s name = FIO_BUF_INFO2(ch->name, ch->name_len); - FIO_LOG_DDEBUG2("(%d) pub/sub %s created, filter %d, length %zu bytes: %s", - fio_srv_pid(), - (ch->is_pattern ? "pattern" : "channel"), - (int)ch->filter, - (size_t)ch->name_len, - name.buf); - FIO_MAP_EACH(fio___pubsub_engines, &FIO___PUBSUB_POSTOFFICE.engines, i) { - (&i.key->subscribe + ch->is_pattern)[0](i.key, name, ch->filter); - } - if (!FIO___PUBSUB_POSTOFFICE.filter.remote) { /* inform root process */ - FIO_LOG_DDEBUG2("informing root process of new channel."); - fio___pubsub_message_s *m = - fio___pubsub_message_author((fio_publish_args_s){ - .id = (uint64_t)(ch->is_pattern + 1), - .channel = FIO_BUF_INFO2(ch->name, ch->name_len), - .filter = ch->filter, - .is_json = FIO___PUBSUB_SUB, - }); - if (m) { - fio_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, - fio___pubsub_message_write2io, - m); - fio___pubsub_message_free(m); - } - } +typedef struct { + fio_io_protocol_s *protocol; + void *udata; + void *tls_ctx; + fio_io_async_s *queue_for_accept; + fio_queue_s *queue; + fio_io_s *io; + void (*on_start)(fio_io_protocol_s *protocol, void *udata); + void (*on_stop)(fio_io_protocol_s *protocol, void *udata); + int owner; + int fd; + size_t ref_count; + size_t url_len; + uint8_t hide_from_log; + char url[]; +} fio___io_listen_s; + +FIO_LEAK_COUNTER_DEF(fio_io_listen) + +static fio___io_listen_s *fio___io_listen_dup(fio___io_listen_s *l) { + fio_atomic_add(&l->ref_count, 1); + return l; } -/** Callback for when a channel is destroy. */ -FIO_IFUNC void fio___channel_on_destroy(fio_channel_s *ch) { - fio_buf_info_s name = FIO_BUF_INFO2(ch->name, ch->name_len); - FIO_MAP_EACH(fio___pubsub_engines, &FIO___PUBSUB_POSTOFFICE.engines, i) { - (&i.key->unsubscribe + ch->is_pattern)[0](i.key, name, ch->filter); - } +static void fio___io_listen_free(void *l_) { + fio___io_listen_s *l = (fio___io_listen_s *)l_; + if (l->io) + fio_io_close(l->io); + if (fio_atomic_sub(&l->ref_count, 1)) + return; - if (!FIO___PUBSUB_POSTOFFICE.filter.remote) { /* inform root process */ - fio___pubsub_message_s *m = - fio___pubsub_message_author((fio_publish_args_s){ - .id = (uint64_t)(ch->is_pattern + 1), - .channel = FIO_BUF_INFO2(ch->name, ch->name_len), - .filter = ch->filter, - .is_json = FIO___PUBSUB_UNSUB, - }); - if (m) { - fio_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, - fio___pubsub_message_write2io, - m); - fio___pubsub_message_free(m); - } + fio_state_callback_remove(FIO_CALL_AT_EXIT, fio___io_listen_free, (void *)l); + fio_state_callback_remove(FIO_CALL_ON_START, fio___io_listen_free, (void *)l); + fio_state_callback_remove(FIO_CALL_PRE_START, + fio___io_listen_free, + (void *)l); + fio___io_func_free_context_caller(l->protocol->io_functions.free_context, + l->tls_ctx); + fio_sock_close(l->fd); + +#ifdef AF_UNIX + /* delete the unix socket file, if any. */ + fio_url_s u = fio_url_parse(l->url, FIO_STRLEN(l->url)); + if (FIO___IO.pid == l->owner && !u.host.buf && !u.port.buf && u.path.buf) { + unlink(u.path.buf); } +#endif - FIO_LOG_DDEBUG2("(%d) pub/sub %s destroyed, filter %d, length %zu bytes: %s", - fio_srv_pid(), - (ch->is_pattern ? "pattern" : "channel"), - (int)ch->filter, - (size_t)ch->name_len, - name.buf); + if (l->on_stop) + l->on_stop(l->protocol, l->udata); + + if (l->hide_from_log) + FIO_LOG_DEBUG2("(%d) stopped listening @ %.*s", + getpid(), + (int)l->url_len, + l->url); + else + FIO_LOG_INFO("(%d) stopped listening @ %.*s", + getpid(), + (int)l->url_len, + l->url); + fio_queue_perform_all(&FIO___IO.queue); + FIO_LEAK_COUNTER_ON_FREE(fio_io_listen); + FIO_MEM_FREE_(l, sizeof(*l) + l->url_len + 1); } -/* ***************************************************************************** -Broadcasting for remote connections -***************************************************************************** */ +SFUNC void fio_io_listen_stop(void *listener) { + if (listener) + fio___io_listen_free(listener); +} -FIO_IFUNC fio_u512 fio___pubsub_broadcast_compose(uint64_t tick) { - /* [0-1] Sender's 128 bit UUID - * [2] Random nonce - * [3] Timestamp in milliseconds - * [4-5] MAC - */ - fio_u512 u = {0}; - uint64_t hello_rand = fio_rand64(); - const void *k = fio___pubsub_secret_key(hello_rand); - u.u64[0] = FIO___PUBSUB_POSTOFFICE.uuid.u64[0]; - u.u64[1] = FIO___PUBSUB_POSTOFFICE.uuid.u64[1]; - u.u64[2] = fio_ltole64(hello_rand); /* persistent endienes required for k */ - u.u64[3] = fio_ltole64(tick); - fio_poly1305_auth(u.u64 + 4, k, NULL, 0, u.u64, 32); - return u; +/** Returns the URL on which the listener is listening. */ +SFUNC fio_buf_info_s fio_io_listener_url(void *listener) { + fio___io_listen_s *l = (fio___io_listen_s *)listener; + return FIO_BUF_INFO2(l->url, l->url_len); } -FIO_SFUNC void fio___pubsub_broadcast_hello(fio_s *io) { +/** Returns true if the listener protocol has an attached TLS context. */ +SFUNC int fio_io_listener_is_tls(void *listener) { + fio___io_listen_s *l = (fio___io_listen_s *)listener; + return !!l->tls_ctx; +} - if (!fio_srv_is_running() || !(io = FIO___PUBSUB_POSTOFFICE.broadcaster)) - return; - static int64_t last_hello = 0; - int64_t this_hello = fio_srv_last_tick(); - if (last_hello == this_hello) +static void fio___io_listen_on_data_task(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + int fd; + fio___io_listen_s *l = (fio___io_listen_s *)fio_io_udata(io); + fio_io_unsuspend(io); + while (FIO_SOCK_FD_ISVALID(fd = fio_sock_accept(fio_io_fd(io), NULL, NULL))) { + FIO_LOG_DDEBUG2("(%d) accepted new connection with fd %d", + fio_io_pid(), + fd); + fio_io_attach_fd(fd, l->protocol, l->udata, l->tls_ctx); + } + fio___io_free2(io); +} +static void fio___io_listen_on_data_task_reschd(void *io_, void *ignr_) { + fio_io_defer(fio___io_listen_on_data_task, io_, ignr_); +} +static void fio___io_listen_on_attach(fio_io_s *io) { + fio___io_listen_s *l = (fio___io_listen_s *)(io->udata); + l->queue = (l->queue_for_accept && l->queue_for_accept->q != &FIO___IO.queue) + ? l->queue_for_accept->q + : NULL; + if (l->on_start) + l->on_start(l->protocol, l->udata); + if (l->hide_from_log) + FIO_LOG_DEBUG2("(%d) started listening @ %s", fio_io_pid(), l->url); + else + FIO_LOG_INFO("(%d) started listening @ %s", fio_io_pid(), l->url); +} +static void fio___io_listen_on_shutdown(fio_io_s *io) { + fio___io_listen_s *l = (fio___io_listen_s *)(io->udata); + l->queue = fio_io_queue(); +} +static void fio___io_listen_on_data(fio_io_s *io) { + fio___io_listen_s *l = (fio___io_listen_s *)(io->udata); + if (l->queue) { + fio_io_suspend(io); + fio_queue_push(l->queue, + fio___io_listen_on_data_task_reschd, + fio___io_dup2(io)); return; - fio_u512 u = fio___pubsub_broadcast_compose((last_hello = this_hello)); - struct sockaddr_in addr = (struct sockaddr_in){ - .sin_family = AF_INET, - .sin_port = fio_lton16((uint16_t)(uintptr_t)fio_udata_get(io)), - .sin_addr.s_addr = INADDR_BROADCAST, // inet_addr("255.255.255.255"), - }; - FIO_LOG_DEBUG2("(%d) pub/sub sending broadcast.", fio_srv_pid()); - sendto(fio_fd_get(io), - (const char *)u.u8, - 48, - 0, - (struct sockaddr *)&addr, - sizeof(addr)); + } + fio___io_listen_on_data_task(fio___io_dup2(io), NULL); +} +static void fio___io_listen_on_close(void *buffer, void *l) { + ((fio___io_listen_s *)l)->io = NULL; + fio___io_listen_free(l); + (void)buffer; } -FIO_SFUNC int fio___pubsub_broadcast_hello_task(void *io_, void *ignr_) { +static fio_io_protocol_s FIO___IO_LISTEN_PROTOCOL = { + .on_attach = fio___io_listen_on_attach, + .on_data = fio___io_listen_on_data, + .on_close = fio___io_listen_on_close, + .on_timeout = fio_io_touch, + .on_shutdown = fio___io_listen_on_shutdown, +}; + +FIO_SFUNC void fio___io_listen_attach_task_deferred(void *l_, void *ignr_) { + fio___io_listen_s *l = (fio___io_listen_s *)l_; + l = fio___io_listen_dup(l); + int fd = fio_sock_dup(l->fd); + FIO_ASSERT(fd != -1, "listening socket failed to `dup`"); + FIO_LOG_DEBUG2("(%d) Called dup(%d) to attach %d as a listening socket.", + (int)fio_io_pid(), + l->fd, + fd); + l->io = fio_io_attach_fd(fd, &FIO___IO_LISTEN_PROTOCOL, l, NULL); (void)ignr_; - fio_s *io = (fio_s *)io_; - fio___pubsub_broadcast_hello(io); - fio_undup(io); - return 0; } -FIO_SFUNC int fio___pubsub_broadcast_hello_validate(uint64_t *hello) { - uint64_t mac[2] = {0}; - /* test server UUID (ignore self generated messages) */ - if (hello[0] == FIO___PUBSUB_POSTOFFICE.uuid.u64[0] && - hello[1] == FIO___PUBSUB_POSTOFFICE.uuid.u64[1]) - return -1; - /* test time window */ - mac[0] = fio_srv_last_tick(); - if (mac[0] > fio_ltole64(hello[3]) + 8192 || - mac[0] + 8192 < fio_ltole64(hello[3])) { - FIO_LOG_SECURITY( - "(%d) pub/sub-broadcast timing error - possible replay attack?", - fio_srv_pid()); - return -1; +FIO_SFUNC void fio___io_listen_attach_task(void *l_) { + /* make sure to run in server thread */ + fio_io_defer(fio___io_listen_attach_task_deferred, l_, NULL); +} + +int fio_io_listen___(void); /* IDE marker */ +/** + * Sets up a network service on a listening socket. + * + * Returns 0 on success or -1 on error. + * + * See the `fio_listen` Macro for details. + */ +SFUNC void *fio_io_listen FIO_NOOP(struct fio_io_listen_args args) { + fio___io_listen_s *l = NULL; + void *built_tls = NULL; + int should_free_tls = !args.tls; + FIO_STR_INFO_TMP_VAR(url_alt, 2048); + if (!args.protocol) { + FIO_LOG_ERROR("fio_io_listen requires a protocol to be assigned."); + return l; } - /* test for duplicate connections */ - if (fio___pubsub_broadcast_connected_get( - &FIO___PUBSUB_POSTOFFICE.remote_uuids, - hello[0], - hello[1])) { - FIO_LOG_DEBUG2("(%d) pub/sub-broadcast Prevented duplicate connection!", - fio_srv_pid()); - return -1; + if (args.on_root && !fio_io_is_master()) { + FIO_LOG_ERROR("fio_io_listen called with `on_root` by a non-root worker."); + return l; } - /* test MAC */ - const void *k = fio___pubsub_secret_key(fio_ltole64(hello[2])); - fio_poly1305_auth(mac, k, NULL, 0, hello, 32); - if (mac[0] != hello[4] || mac[1] != hello[5]) { - FIO_LOG_SECURITY("(%d) pub/sub-broadcast MAC failure - under attack?", - fio_srv_pid()); - return -1; + if (!args.url) { + args.url = getenv("ADDRESS"); + if (!args.url) + args.url = "0.0.0.0"; } - return 0; -} -/* ***************************************************************************** -Letter Listening to Remote Connections - TODO! -***************************************************************************** -*/ -FIO_SFUNC void fio___pubsub_broadcast_on_attach(fio_s *io) { - fio___pubsub_broadcast_hello((FIO___PUBSUB_POSTOFFICE.broadcaster = io)); - fio_srv_run_every(.fn = fio___pubsub_broadcast_hello_task, - .udata1 = fio_dup(io), - .every = (uint32_t)(1024 | - (1023 & - FIO___PUBSUB_POSTOFFICE.uuid.u64[0])), - .repetitions = 2); -} -FIO_SFUNC void fio___pubsub_broadcast_on_close(void *ignr_) { - FIO___PUBSUB_POSTOFFICE.broadcaster = NULL; - (void)ignr_; + url_alt.len = strlen(args.url); + if (url_alt.len > 2024) { + FIO_LOG_ERROR("binding address / url too long."); + args.url = NULL; + } + fio_url_s url = fio_url_parse(args.url, url_alt.len); + if (url.scheme.buf && + (url.scheme.len > 2 && url.scheme.len < 5 && + (url.scheme.buf[0] | (char)0x20) == 't' && + (url.scheme.buf[1] | (char)0x20) == 'c') && + (url.scheme.buf[2] | (char)0x20) == 'p') + url.scheme = FIO_BUF_INFO0; + if (!url.port.buf && !url.scheme.buf) { + static size_t port_counter = 3000; + size_t port = fio_atomic_add(&port_counter, 1); + if (port == 3000 && getenv("PORT")) { + char *port_env = getenv("PORT"); + port = fio_atol10(&port_env); + if (!port | (port > 65535ULL)) + port = 3000; + } + url_alt.len = 0; + fio_string_write2(&url_alt, + NULL, + FIO_STRING_WRITE_STR2(url.scheme.buf, url.scheme.len), + (url.scheme.len ? FIO_STRING_WRITE_STR2("://", 3) + : FIO_STRING_WRITE_STR2(NULL, 0)), + FIO_STRING_WRITE_STR2(url.host.buf, url.host.len), + FIO_STRING_WRITE_STR2(":", 1), + FIO_STRING_WRITE_NUM(port)); + args.url = url_alt.buf; + url = fio_url_parse(args.url, url_alt.len); + } + + args.tls = fio_io_tls_from_url(args.tls, url); + fio___io_init_protocol_test(args.protocol, !!args.tls); + built_tls = args.protocol->io_functions.build_context(args.tls, 0); + fio_buf_info_s url_buf = FIO_BUF_INFO2((char *)args.url, url_alt.len); + /* remove query details from URL */ + if (url.query.len) + url_buf.len = url.query.buf - (url_buf.buf + 1); + else if (url.target.len) + url_buf.len = url.target.buf - (url_buf.buf + 1); + l = (fio___io_listen_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*l) + url_buf.len + 1, 0); + FIO_ASSERT_ALLOC(l); + FIO_LEAK_COUNTER_ON_ALLOC(fio_io_listen); + *l = (fio___io_listen_s){ + .protocol = args.protocol, + .udata = args.udata, + .tls_ctx = built_tls, + .queue_for_accept = args.queue_for_accept, + .on_start = args.on_start, + .on_stop = args.on_stop, + .owner = FIO___IO.pid, + .url_len = url_buf.len, + .hide_from_log = args.hide_from_log, + }; + FIO_MEMCPY(l->url, url_buf.buf, url_buf.len); + l->url[l->url_len] = 0; + if (should_free_tls) + fio_io_tls_free(args.tls); + + l->fd = fio_sock_open2(l->url, FIO_SOCK_SERVER | FIO_SOCK_TCP); + if (l->fd == -1) { + fio___io_listen_free(l); + return (l = NULL); + } + if (fio_io_is_running()) { + fio_io_defer(fio___io_listen_attach_task_deferred, l, NULL); + } else { + fio_state_callback_add( + (args.on_root ? FIO_CALL_PRE_START : FIO_CALL_ON_START), + fio___io_listen_attach_task, + (void *)l); + } + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___io_listen_free, l); + return l; } -FIO_SFUNC void fio___pubsub_broadcast_on_data(fio_s *io) { - uint64_t buf[16]; - struct sockaddr from[2]; - socklen_t from_len = sizeof(from); - ssize_t len; - int should_say_hello = 0; - fio___pubsub_message_s *m = fio___pubsub_message_author( - (fio_publish_args_s){.id = FIO___PUBSUB_POSTOFFICE.uuid.u64[0], - .published = FIO___PUBSUB_POSTOFFICE.uuid.u64[1], - .is_json = FIO___PUBSUB_IDENTIFY}); +/* ***************************************************************************** +Connecting as a Client +***************************************************************************** */ - while ( - (len = recvfrom(fio_fd_get(io), (char *)buf, 128, 0, from, &from_len)) > - 0) { - if (len != 48) { - FIO_LOG_WARNING( - "(%d) pub/sub peer detection received invalid packet (%zu bytes)!", - fio_srv_pid(), - len); - continue; - } - if (fio___pubsub_broadcast_hello_validate(buf)) { - FIO_LOG_WARNING( - "(%d) pub/sub peer detection received invalid packet payload!", - fio_srv_pid()); - continue; - } - if (fio___pubsub_broadcast_connected_get( - &FIO___PUBSUB_POSTOFFICE.remote_uuids, - buf[0], - buf[1]) == buf[1]) { - FIO_LOG_DDEBUG2("skipping peer connection - already exists"); - continue; /* skip connection, already exists. */ - } - should_say_hello |= 1; - FIO_LOG_DDEBUG2("detected peer, should now connect"); +typedef struct { + fio_io_protocol_s protocol; + fio_io_protocol_s *upr; + void (*on_failed)(fio_io_protocol_s *protocol, void *udata); + void *udata; + void *tls_ctx; + size_t url_len; + char url[]; +} fio___io_connecting_s; - /* TODO: fixme! */ - char addr_buf[128]; - if (getnameinfo(from, - from_len, - addr_buf, - 64, - addr_buf + 64, - 64, - (NI_NUMERICHOST | NI_NUMERICHOST))) { - FIO_LOG_ERROR("(%d) couldn't resolve peer address", fio_srv_pid()); - continue; - } - int fd = fio_sock_open(addr_buf, - addr_buf + 64, - FIO_SOCK_NONBLOCK | FIO_SOCK_CLIENT | FIO_SOCK_TCP); - if (fd == -1) { - FIO_LOG_ERROR("couldn't connect to cluster peer: %s", strerror(errno)); - continue; - } - fio___pubsub_broadcast_connected_set(&FIO___PUBSUB_POSTOFFICE.remote_uuids, - addr_buf[0], - addr_buf[1]); - fio_s *peer = fio_srv_attach_fd(fd, - &FIO___PUBSUB_POSTOFFICE.protocol.remote, - NULL, - NULL); - fio___pubsub_message_write2io(peer, m); - FIO_LOG_INFO("(%d) pub/sub-cluster connecting to peer (%zu connections).", - fio_srv_pid(), - fio___pubsub_broadcast_connected_count( - &FIO___PUBSUB_POSTOFFICE.remote_uuids)); - } - fio___pubsub_message_free(m); - if (should_say_hello) - fio_srv_run_every(.fn = fio___pubsub_broadcast_hello_task, - .udata1 = fio_dup(io), - .every = - (uint32_t)(1024 | - (1023 & - FIO___PUBSUB_POSTOFFICE.uuid.u64[0]))); +FIO_SFUNC void fio___connecting_cleanup(fio___io_connecting_s *c) { + fio___io_func_free_context_caller(c->protocol.io_functions.free_context, + c->tls_ctx); + FIO_MEM_FREE_(c, sizeof(*c) + c->url_len + 1); } -FIO_SFUNC void fio___pubsub_broadcast_on_incoming(fio_s *io) { - int fd; - while ((fd = accept(fio_fd_get(io), NULL, NULL)) != -1) { - FIO_LOG_DDEBUG2("accepting a cluster peer connection"); - fio_srv_attach_fd(fd, &FIO___PUBSUB_POSTOFFICE.protocol.remote, NULL, NULL); - } - FIO_LOG_INFO("(cluster) accepted new peer(s) (%zu connections).", - fio___pubsub_broadcast_connected_count( - &FIO___PUBSUB_POSTOFFICE.remote_uuids)); +FIO_SFUNC void fio___connecting_on_close(void *buffer, void *udata) { + fio___io_connecting_s *c = (fio___io_connecting_s *)udata; + if (c->on_failed) + c->on_failed(c->upr, c->udata); + fio___connecting_cleanup(c); + (void)buffer; } -SFUNC void fio___pubsub_broadcast_on_port(void *port_) { - int16_t port = (int16_t)(uintptr_t)port_; - static fio_protocol_s broadcast = { - .on_attach = fio___pubsub_broadcast_on_attach, - .on_data = fio___pubsub_broadcast_on_data, - .on_close = fio___pubsub_broadcast_on_close, - .on_timeout = fio_touch, - }; - static fio_protocol_s accept_remote = { - .on_data = fio___pubsub_broadcast_on_incoming, - .on_timeout = fio_touch, - }; - if (FIO___PUBSUB_POSTOFFICE.secret_is_random) { - FIO_LOG_ERROR( - "Listening to cluster peer connections failed!" - "\n\tUsing a random (non-shared) secret, cannot validate peers."); +FIO_SFUNC void fio___connecting_on_ready(fio_io_s *io) { + if (!fio_io_is_open(io)) return; - } - if (!port || port < 0) - port = 3333; - FIO_STR_INFO_TMP_VAR(url, 32); - url.buf[0] = ':'; - url.len = 1; - fio_string_write_u(&url, NULL, (uint64_t)port); - - int fd_udp = - fio_sock_open(NULL, - url.buf + 1, - FIO_SOCK_UDP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER); - FIO_ASSERT(fd_udp != -1, "couldn't open broadcast socket!"); - int fd_tcp = - fio_sock_open(NULL, - url.buf + 1, - FIO_SOCK_TCP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER); - FIO_ASSERT(fd_tcp != -1, "couldn't open cluster-peer listening socket!"); - { -#if FIO_OS_WIN - char enabled = 1; -#else - int enabled = 1; -#endif - setsockopt(fd_udp, SOL_SOCKET, SO_BROADCAST, &enabled, sizeof(enabled)); - enabled = 1; - setsockopt(fd_udp, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)); - } - fio_srv_attach_fd(fd_udp, &broadcast, port_, NULL); - fio_srv_attach_fd(fd_tcp, &accept_remote, NULL, NULL); - return; + fio___io_connecting_s *c = (fio___io_connecting_s *)fio_io_udata(io); + FIO_LOG_DEBUG2("(%d) established client connection to %s", + fio_io_pid(), + c->url); + fio_io_udata_set(io, c->udata); + fio_io_protocol_set(io, c->upr); + c->on_failed = NULL; + fio___io_defer_no_wakeup(fio___connecting_on_close, NULL, (void *)c); } -/** Auto-peer detection and pub/sub multi-machine clustering using `port`. */ -SFUNC void fio_pubsub_broadcast_on_port(int16_t port) { - fio_state_callback_add(FIO_CALL_PRE_START, - fio___pubsub_broadcast_on_port, - (void *)(uintptr_t)port); +void fio_io_connect___(void); /* IDE Marker */ +SFUNC fio_io_s *fio_io_connect FIO_NOOP(fio_io_connect_args_s args) { + int should_free_tls = !args.tls; + if (!args.protocol) + return NULL; + if (!args.url) { + if (args.on_failed) + args.on_failed(args.protocol, args.udata); + return NULL; + } + if (!args.timeout) + args.timeout = 30000; + + size_t url_len = strlen(args.url); + fio_url_s url = fio_url_parse(args.url, url_len); + args.tls = fio_io_tls_from_url(args.tls, url); + fio___io_init_protocol(args.protocol, !!args.tls); + if (url.query.len) + url_len = url.query.buf - (args.url + 1); + else if (url.target.len) + url_len = url.target.buf - (args.url + 1); + fio___io_connecting_s *c = (fio___io_connecting_s *) + FIO_MEM_REALLOC_(NULL, 0, sizeof(*c) + url_len + 1, 0); + FIO_ASSERT_ALLOC(c); + *c = (fio___io_connecting_s){ + .protocol = + { + .on_ready = fio___connecting_on_ready, + .on_close = fio___connecting_on_close, + .io_functions = args.protocol->io_functions, + .timeout = args.timeout, + .buffer_size = args.protocol->buffer_size, + }, + .upr = args.protocol, + .on_failed = args.on_failed, + .udata = args.udata, + .tls_ctx = args.protocol->io_functions.build_context(args.tls, 1), + }; + FIO_MEMCPY(c->url, args.url, url_len); + c->url[url_len] = 0; + fio_io_s *io = fio_io_attach_fd( + fio_sock_open2(c->url, FIO_SOCK_CLIENT | FIO_SOCK_NONBLOCK), + &c->protocol, + c, + c->tls_ctx); + if (should_free_tls) + fio_io_tls_free(args.tls); + return io; } - /* ***************************************************************************** -Pub/Sub Cleanup +IO Reactor Finish ***************************************************************************** */ - -#endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_PUBSUB -#endif /* FIO_PUBSUB */ +#endif /* FIO_IO */ /* ************************************************************************* */ #if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ #define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_HTTP_HANDLE /* Development inclusion - ignore line */ -#define FIO_STR /* Development inclusion - ignore line */ +#define FIO_IO /* Development inclusion - ignore line */ #include "./include.h" /* Development inclusion - ignore line */ #endif /* Development inclusion - ignore line */ /* ***************************************************************************** @@ -37682,7001 +37331,7402 @@ Pub/Sub Cleanup - An HTTP connection Handle helper + OpenSSL Implementation for IO Functions -See also: -https://www.rfc-editor.org/rfc/rfc9110.html Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ -#if defined(FIO_HTTP_HANDLE) && !defined(H___FIO_HTTP_HANDLE___H) && \ - !defined(FIO___RECURSIVE_INCLUDE) -#define H___FIO_HTTP_HANDLE___H +#if defined(H___FIO_SERVER___H) && \ + (HAVE_OPENSSL || __has_include("openssl/ssl.h")) && \ + !defined(H___FIO_OPENSSL___H) && !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_OPENSSL___H 1 +/* ***************************************************************************** +OpenSSL IO Function Getter +***************************************************************************** */ + +/* Returns the OpenSSL IO functions. */ +SFUNC fio_io_functions_s fio_openssl_io_functions(void); /* ***************************************************************************** -HTTP Handle Settings +OpenSSL Helpers Implementation ***************************************************************************** */ -#ifndef FIO_HTTP_EXACT_LOGGING -/** - * By default, facil.io logs the HTTP request cycle using a fuzzy starting and - * ending point for the time stamp. - * - * The fuzzy timestamp includes delays that aren't related to the HTTP request - * and may ignore time passed due to timestamp caching. - * - * On the other hand, `FIO_HTTP_EXACT_LOGGING` collects exact time stamps to - * measure the time it took to process the HTTP request (excluding time spent - * reading / writing the data from the network). - * - * Due to the preference to err on the side of higher performance, fuzzy - * time-stamping is the default. - */ -#define FIO_HTTP_EXACT_LOGGING 0 -#ifndef H___FIO_SERVER___H -#undef FIO_HTTP_EXACT_LOGGING -#define FIO_HTTP_EXACT_LOGGING 1 -#endif -#endif +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -#ifndef FIO_HTTP_BODY_RAM_LIMIT -/** - * The HTTP handle automatically switches between RAM storage and file storage - * once the HTTP body (payload) reaches a certain size. This control this point - * of transition - */ -#define FIO_HTTP_BODY_RAM_LIMIT (1 << 17) -#endif +#include +#include -#ifndef FIO_HTTP_CACHE_LIMIT -/** Each of the HTTP String Caches will be limited to this String count. */ -#define FIO_HTTP_CACHE_LIMIT 0 /* ((1UL << 6) + (1UL << 5)) */ -#endif +FIO_ASSERT_STATIC(OPENSSL_VERSION_MAJOR > 2, "OpenSSL version mismatch"); -#ifndef FIO_HTTP_CACHE_STR_MAX_LEN -/** The HTTP handle will avoid caching strings longer than this value. */ -#define FIO_HTTP_CACHE_STR_MAX_LEN (1 << 12) -#endif +/* ***************************************************************************** +Self-Signed Certificates - TODO: change to ECDSA +***************************************************************************** */ +static EVP_PKEY *fio___openssl_pkey = NULL; +static void fio___openssl_clear_root_key(void *key) { + EVP_PKEY_free(((EVP_PKEY *)key)); + fio___openssl_pkey = NULL; +} -#ifndef FIO_HTTP_CACHE_USES_MUTEX -/** The HTTP cache will use a mutex to allow headers to be set concurrently. */ -#define FIO_HTTP_CACHE_USES_MUTEX 1 -#endif +static void fio___openssl_make_root_key(void) { + static fio_lock_i lock = FIO_LOCK_INIT; + fio_lock(&lock); + if (!fio___openssl_pkey) { + /* create private key, free it at exit */ + FIO_LOG_DEBUG2("calculating a new TLS private key... might take a while."); + fio___openssl_pkey = EVP_RSA_gen(2048); + FIO_ASSERT(fio___openssl_pkey, "OpenSSL failed to create private key."); + fio_state_callback_add(FIO_CALL_AT_EXIT, + fio___openssl_clear_root_key, + fio___openssl_pkey); + } + fio_unlock(&lock); +} -#ifndef FIO_HTTP_CACHE_STATIC_HEADERS -/** Adds a static cache for common HTTP header names. */ -#define FIO_HTTP_CACHE_STATIC_HEADERS 1 -#endif +static X509 *fio_io_tls_create_self_signed(const char *server_name) { + X509 *cert = X509_new(); + static uint32_t counter = 0; + FIO_ASSERT(cert, + "OpenSSL failed to allocate memory for self-signed certificate."); + fio___openssl_make_root_key(); -#ifndef FIO_HTTP_DEFAULT_INDEX_FILENAME -/** The default file name when a static file response points to a folder. */ -#define FIO_HTTP_DEFAULT_INDEX_FILENAME "index" -#endif + /* serial number */ + fio_atomic_add(&counter, 1); + ASN1_INTEGER_set(X509_get_serialNumber(cert), counter); -#ifndef FIO_HTTP_STATIC_FILE_COMPLETION -/** Attempts to auto-complete static file paths with missing extensions. */ -#define FIO_HTTP_STATIC_FILE_COMPLETION 1 -#endif + /* validity (180 days) */ + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), 15552000L); -#ifndef FIO_HTTP_LOG_X_REQUEST_START -#define FIO_HTTP_LOG_X_REQUEST_START 1 -#endif + /* set (public) key */ + X509_set_pubkey(cert, fio___openssl_pkey); -#ifndef FIO_HTTP_ENFORCE_LOWERCASE_HEADERS -/** If true, the HTTP handle will copy input header names to lower case. */ -#define FIO_HTTP_ENFORCE_LOWERCASE_HEADERS 0 -#endif + /* set identity details */ + X509_NAME *s = X509_get_subject_name(cert); + size_t srv_name_len = FIO_STRLEN(server_name); + FIO_ASSERT(srv_name_len < (size_t)((int)0 - 1), + "fio_io_tls_create_self_signed server_name too long"); + X509_NAME_add_entry_by_txt(s, + "O", + MBSTRING_ASC, + (unsigned char *)server_name, + (int)srv_name_len, + -1, + 0); + X509_NAME_add_entry_by_txt(s, + "CN", + MBSTRING_ASC, + (unsigned char *)server_name, + (int)srv_name_len, + -1, + 0); + X509_NAME_add_entry_by_txt(s, + "CA", + MBSTRING_ASC, + (unsigned char *)server_name, + (int)srv_name_len, + -1, + 0); + X509_set_issuer_name(cert, s); + + /* sign certificate */ + FIO_ASSERT(X509_sign(cert, fio___openssl_pkey, EVP_sha512()), + "OpenSSL failed to signed self-signed certificate"); + return cert; +} /* ***************************************************************************** -HTTP Handle Type +OpenSSL Context type wrappers ***************************************************************************** */ -/** - * The HTTP Handle type. - * - * Note that the type is NOT designed to be thread-safe. - */ -typedef struct fio_http_s fio_http_s; +/* Context for all future connections */ +typedef struct { + SSL_CTX *ctx; + fio_io_tls_s *tls; +} fio___openssl_context_s; -/** - * The HTTP Controller points to all the callbacks required by the HTTP Handler. - * - * This allows the HTTP Handler to be somewhat protocol agnostic. - * - * Note: if the controller callbacks aren't thread-safe, than the `http_write` - * function MUST NOT be called from any thread except the thread that the - * controller is expecting. - */ -typedef struct fio_http_controller_s fio_http_controller_s; +FIO_LEAK_COUNTER_DEF(fio___openssl_context_s) /* ***************************************************************************** -Constructor / Destructor +OpenSSL Callbacks ***************************************************************************** */ -/** Create a new fio_http_s handle. */ -SFUNC fio_http_s *fio_http_new(void); +FIO_SFUNC int fio___openssl_pem_password_cb(char *buf, + int size, + int rwflag, + void *u) { + const char *password = (const char *)u; + size_t password_len = FIO_STRLEN(password); + if (password_len > (size_t)size) + return -1; + FIO_MEMCPY(buf, password, password_len); + return (int)password_len; + (void)rwflag; +} -/** Creates a copy of an existing handle, copying only its request data. */ -SFUNC fio_http_s *fio_http_new_copy_request(fio_http_s *old); +FIO_SFUNC int fio___openssl_alpn_selector_cb(SSL *ssl, + const unsigned char **out, + unsigned char *outlen, + const unsigned char *in, + unsigned int inlen, + void *tls_) { + fio_io_s *io = (fio_io_s *)SSL_get_ex_data(ssl, 0); + fio___openssl_context_s *ctx = (fio___openssl_context_s *)tls_; -/** Reduces an fio_http_s handle's reference count or frees it. */ -SFUNC void fio_http_free(fio_http_s *); + const unsigned char *end = in + inlen; + char buf[256]; + for (;;) { + uint8_t len = in[0]; + FIO_MEMCPY(buf, in + 1, len); + buf[len] = 0; + if (fio_io_tls_alpn_select(ctx->tls, buf, (size_t)len, io)) { + in += len + 1; + if (in < end) + continue; + FIO_LOG_DDEBUG2("(%d) ALPN Failed! No protocol name match for %p", + (int)fio_thread_getpid(), + io); + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + *out = in + 1; + *outlen = len; + FIO_LOG_DDEBUG2("(%d) TLS ALPN set to: %s for %p", + (int)fio_thread_getpid(), + buf, + io); + return SSL_TLSEXT_ERR_OK; + (void)tls_; + } +} -/** Increases an fio_http_s handle's reference count. */ -SFUNC fio_http_s *fio_http_dup(fio_http_s *); +/* ***************************************************************************** +Public Context Builder +***************************************************************************** */ -/** Destroyed the HTTP handle object, freeing all allocated resources. */ -SFUNC fio_http_s *fio_http_destroy(fio_http_s *h); +FIO_SFUNC int fio___openssl_each_cert(struct fio_io_tls_each_s *e, + const char *server_name, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password) { + fio___openssl_context_s *s = (fio___openssl_context_s *)e->udata; + if (public_cert_file && private_key_file) { /* load certificate */ + SSL_CTX_set_default_passwd_cb(s->ctx, fio___openssl_pem_password_cb); + SSL_CTX_set_default_passwd_cb_userdata(s->ctx, (void *)pk_password); + FIO_LOG_DDEBUG2("loading TLS certificates: %s & %s", + public_cert_file, + private_key_file); + /* Set the key and cert */ + if (SSL_CTX_use_certificate_chain_file(s->ctx, public_cert_file) <= 0) { + ERR_print_errors_fp(stderr); + FIO_ASSERT(0, + "OpenSSL couldn't open PEM file for certificate: %s", + public_cert_file); + } -/** Collects an updated timestamp for logging purposes. */ -SFUNC void fio_http_start_time_set(fio_http_s *); + if (SSL_CTX_use_PrivateKey_file(s->ctx, + private_key_file, + SSL_FILETYPE_PEM) <= 0) { + ERR_print_errors_fp(stderr); + FIO_ASSERT(0, + "OpenSSL couldn't open PEM file for private key: %s", + private_key_file); + } + SSL_CTX_set_default_passwd_cb(s->ctx, NULL); + SSL_CTX_set_default_passwd_cb_userdata(s->ctx, NULL); + } else { /* self signed */ + if (!server_name || !strlen(server_name)) + server_name = (const char *)"localhost"; + X509 *cert = fio_io_tls_create_self_signed(server_name); + SSL_CTX_use_certificate(s->ctx, cert); + SSL_CTX_use_PrivateKey(s->ctx, fio___openssl_pkey); + } + return 0; +} -/** Clears any response data. */ -SFUNC fio_http_s *fio_http_clear_response(fio_http_s *h, bool clear_body); +FIO_SFUNC int fio___openssl_each_alpn(struct fio_io_tls_each_s *e, + const char *protocol_name, + void (*on_selected)(fio_io_s *)) { + fio_str_info_s *str = (fio_str_info_s *)e->udata2; + size_t len = FIO_STRLEN(protocol_name); + if (len > 255 || (len + str->len >= str->capa)) { + FIO_LOG_ERROR("ALPN protocol name/list overflow."); + return -1; + } + str->buf[str->len++] = (len & 0xFF); + FIO_MEMCPY(str->buf + str->len, protocol_name, len); + str->len += len; + return 0; + (void)on_selected; +} + +FIO_SFUNC int fio___openssl_each_trust(struct fio_io_tls_each_s *e, + const char *public_cert_file) { + X509_STORE *store = (X509_STORE *)e->udata2; + if (public_cert_file) /* trust specific certificate */ + X509_STORE_load_file(store, public_cert_file); + else { /* trust system's trust */ + const char *path = getenv(X509_get_default_cert_dir_env()); + if (!path) + path = X509_get_default_cert_dir(); + if (path) + X509_STORE_load_path(store, path); + } + return 0; +} + +/** Helper that converts a `fio_io_tls_s` into the implementation's context. */ +FIO_SFUNC void *fio___openssl_build_context(fio_io_tls_s *tls, + uint8_t is_client) { + fio___openssl_context_s *ctx = + (fio___openssl_context_s *)FIO_MEM_REALLOC(NULL, 0, sizeof(*ctx), 0); + FIO_ASSERT_ALLOC(ctx); + FIO_LEAK_COUNTER_ON_ALLOC(fio___openssl_context_s); + *ctx = (fio___openssl_context_s){ + .ctx = SSL_CTX_new((is_client ? TLS_client_method : TLS_server_method)()), + .tls = fio_io_tls_dup(tls), + }; + + SSL_CTX_set_mode(ctx->ctx, SSL_MODE_ENABLE_PARTIAL_WRITE); + SSL_CTX_set_mode(ctx->ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + SSL_CTX_clear_mode(ctx->ctx, SSL_MODE_AUTO_RETRY); + + X509_STORE *store = NULL; + if (fio_io_tls_trust_count(tls)) { + SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_PEER, NULL); + store = X509_STORE_new(); + SSL_CTX_set_cert_store(ctx->ctx, store); + } else { + SSL_CTX_set_verify(ctx->ctx, SSL_VERIFY_NONE, NULL); + } + if (!fio_io_tls_cert_count(tls) && !is_client) { + /* add self-signed certificate to context */ + X509 *cert = fio_io_tls_create_self_signed("localhost"); + SSL_CTX_use_certificate(ctx->ctx, cert); + SSL_CTX_use_PrivateKey(ctx->ctx, fio___openssl_pkey); + } + fio_io_tls_each(tls, + .udata = ctx, + .udata2 = store, + .each_cert = fio___openssl_each_cert, + .each_trust = fio___openssl_each_trust); + + if (fio_io_tls_alpn_count(tls)) { + FIO_STR_INFO_TMP_VAR(alpn_list, 1023); + fio_io_tls_each(tls, + .udata = ctx, + .udata2 = &alpn_list, + .each_alpn = fio___openssl_each_alpn); + if (SSL_CTX_set_alpn_protos(ctx->ctx, + (const unsigned char *)alpn_list.buf, + (unsigned int)alpn_list.len)) { + FIO_LOG_ERROR("SSL_CTX_set_alpn_protos failed."); + } else { + SSL_CTX_set_alpn_select_cb(ctx->ctx, fio___openssl_alpn_selector_cb, ctx); + SSL_CTX_set_next_proto_select_cb( + ctx->ctx, + (int (*)(SSL *, + unsigned char **, + unsigned char *, + const unsigned char *, + unsigned int, + void *))fio___openssl_alpn_selector_cb, + (void *)ctx); + } + } + return ctx; +} /* ***************************************************************************** -Opaque User and Controller Data +IO functions ***************************************************************************** */ -/** Gets the opaque user pointer associated with the HTTP handle. */ -FIO_IFUNC void *fio_http_udata(fio_http_s *); +/** Called to perform a non-blocking `read`, same as the system call. */ +FIO_SFUNC ssize_t fio___openssl_read(int fd, + void *buf, + size_t len, + void *tls_ctx) { + ssize_t r; + SSL *ssl = (SSL *)tls_ctx; + errno = 0; + if (len > INT_MAX) + len = INT_MAX; + r = SSL_read(ssl, buf, (int)len); + if (r > 0) + return r; + if (errno == EWOULDBLOCK || errno == EAGAIN) + return (ssize_t)-1; -/** Sets the opaque user pointer associated with the HTTP handle. */ -FIO_IFUNC void *fio_http_udata_set(fio_http_s *, void *); + switch ((r = (ssize_t)SSL_get_error(ssl, (int)r))) { + case SSL_ERROR_SSL: /* fall through */ + case SSL_ERROR_SYSCALL: /* fall through */ + case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ + case SSL_ERROR_NONE: /* fall through */ + case SSL_ERROR_WANT_CONNECT: /* fall through */ + case SSL_ERROR_WANT_ACCEPT: /* fall through */ + case SSL_ERROR_WANT_WRITE: /* fall through */ + r = SSL_write_ex(ssl, (void *)&r, 0, (size_t *)&r); /* fall through */ + case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ + case SSL_ERROR_WANT_READ: /* fall through */ +#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ + case SSL_ERROR_WANT_ASYNC: /* fall through */ +#endif + default: errno = EWOULDBLOCK; return (r = -1); + } + (void)fd; +} -/** Gets the second opaque user pointer associated with the HTTP handle. */ -FIO_IFUNC void *fio_http_udata2(fio_http_s *); +/** Sends any unsent internal data. Returns 0 only if all data was sent. */ +FIO_SFUNC int fio___openssl_flush(int fd, void *tls_ctx) { + return 0; + (void)fd, (void)tls_ctx; +#if 0 /* no flushing necessary? */ + int r; + char buf[8] = {0}; + size_t count = 0; + SSL *ssl = (SSL *)tls_ctx; + r = SSL_write_ex(ssl, buf, 0, &count); + if (count) + return 1; + if (r > 0) + return 0; + switch ((r = SSL_get_error(ssl, r))) { + case SSL_ERROR_SSL: /* fall through */ + case SSL_ERROR_SYSCALL: /* fall through */ + case SSL_ERROR_NONE: /* fall through */ + case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ + case SSL_ERROR_WANT_CONNECT: /* fall through */ + case SSL_ERROR_WANT_ACCEPT: /* fall through */ + case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ + case SSL_ERROR_WANT_READ: /* fall through */ + SSL_read_ex(ssl, buf, 0, &count); /* fall through */ + case SSL_ERROR_WANT_WRITE: /* fall through */ +#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ + case SSL_ERROR_WANT_ASYNC: /* fall through */ +#endif + default: errno = EWOULDBLOCK; return -1; + } +#endif +} -/** Sets a second opaque user pointer associated with the HTTP handle. */ -FIO_IFUNC void *fio_http_udata2_set(fio_http_s *, void *); +/** Called to perform a non-blocking `write`, same as the system call. */ +FIO_SFUNC ssize_t fio___openssl_write(int fd, + const void *buf, + size_t len, + void *tls_ctx) { + ssize_t r = -1; + if (!buf || !len || !tls_ctx) + return r; + SSL *ssl = (SSL *)tls_ctx; + errno = 0; + if (len > INT_MAX) + len = INT_MAX; + r = SSL_write(ssl, buf, (int)len); + if (r > 0) + return r; + if (errno == EWOULDBLOCK || errno == EAGAIN) + return -1; -/** Gets the HTTP Controller associated with the HTTP handle. */ -FIO_IFUNC fio_http_controller_s *fio_http_controller(fio_http_s *h); + switch ((r = (ssize_t)SSL_get_error(ssl, (int)r))) { + case SSL_ERROR_SSL: /* fall through */ + case SSL_ERROR_SYSCALL: /* fall through */ + case SSL_ERROR_ZERO_RETURN: return (r = 0); /* EOF */ + case SSL_ERROR_NONE: /* fall through */ + case SSL_ERROR_WANT_CONNECT: /* fall through */ + case SSL_ERROR_WANT_ACCEPT: /* fall through */ + case SSL_ERROR_WANT_X509_LOOKUP: /* fall through */ + case SSL_ERROR_WANT_WRITE: /* fall through */ + case SSL_ERROR_WANT_READ: /* fall through */ +#ifdef SSL_ERROR_WANT_ASYNC /* fall through */ + case SSL_ERROR_WANT_ASYNC: /* fall through */ +#endif + default: errno = EWOULDBLOCK; return (r = -1); + } + (void)fd; +} -/** Gets the HTTP Controller associated with the HTTP handle. */ -FIO_IFUNC fio_http_controller_s *fio_http_controller_set( - fio_http_s *h, - fio_http_controller_s *controller); +/* ***************************************************************************** +Per-Connection Builder +***************************************************************************** */ -/** Returns the existing controller data (`void *` pointer). */ -FIO_IFUNC void *fio_http_cdata(fio_http_s *h); +FIO_LEAK_COUNTER_DEF(fio___SSL) + +/** called once the IO was attached and the TLS object was set. */ +FIO_SFUNC void fio___openssl_start(fio_io_s *io) { + fio___openssl_context_s *ctx_parent = (fio___openssl_context_s *)fio_tls(io); + FIO_ASSERT_DEBUG(ctx_parent, "OpenSSL Context missing!"); + + SSL *ssl = SSL_new(ctx_parent->ctx); + FIO_LEAK_COUNTER_ON_ALLOC(fio___SSL); + fio_io_tls_set(io, (void *)ssl); -/** Sets a new controller data (`void *` pointer). */ -FIO_IFUNC void *fio_http_cdata_set(fio_http_s *h, void *cdata); + /* attach socket */ + FIO_LOG_DDEBUG2("(%d) allocated new TLS context for %p.", + (int)fio_thread_getpid(), + (void *)io); + BIO *bio = BIO_new_socket(fio_fd(io), 0); + SSL_set_bio(ssl, bio, bio); + SSL_set_ex_data(ssl, 0, (void *)io); + if (SSL_is_server(ssl)) + SSL_accept(ssl); + else + SSL_connect(ssl); +} /* ***************************************************************************** -Data associated with the Request (usually set by the HTTP protocol) +Closing Connections ***************************************************************************** */ -/** Gets the status associated with the HTTP handle (response). */ -SFUNC size_t fio_http_status(fio_http_s *); - -/** Sets the status associated with the HTTP handle (response). */ -SFUNC size_t fio_http_status_set(fio_http_s *, size_t status); - -/** Gets the method information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_method(fio_http_s *); - -/** Sets the method information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_method_set(fio_http_s *, fio_str_info_s); +/** Decreases a fio_io_tls_s object's reference count, or frees the object. */ +FIO_SFUNC void fio___openssl_finish(int fd, void *tls_ctx) { + SSL *ssl = (SSL *)tls_ctx; + SSL_shutdown(ssl); + (void)fd; +} -/** Gets the path information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_path(fio_http_s *); +/* ***************************************************************************** +Per-Connection Cleanup +***************************************************************************** */ -/** Sets the path information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_path_set(fio_http_s *, fio_str_info_s); +/** Decreases a fio_io_tls_s object's reference count, or frees the object. */ +FIO_SFUNC void fio___openssl_cleanup(void *tls_ctx) { + SSL *ssl = (SSL *)tls_ctx; + SSL_shutdown(ssl); + FIO_LEAK_COUNTER_ON_FREE(fio___SSL); + SSL_free(ssl); +} -/** Gets the query information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_query(fio_http_s *); +/* ***************************************************************************** +Context Cleanup +***************************************************************************** */ -/** Sets the query information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_query_set(fio_http_s *, fio_str_info_s); +static void fio___openssl_free_context_task(void *tls_ctx, void *ignr_) { + fio___openssl_context_s *ctx = (fio___openssl_context_s *)tls_ctx; + FIO_LEAK_COUNTER_ON_FREE(fio___openssl_context_s); + SSL_CTX_free(ctx->ctx); + fio_io_tls_free(ctx->tls); + FIO_MEM_FREE(ctx, sizeof(*ctx)); + (void)ignr_; +} -/** Gets the version information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_version(fio_http_s *); +/** Builds a local TLS context out of the fio_io_tls_s object. */ +static void fio___openssl_free_context(void *tls_ctx) { + fio_srv_defer(fio___openssl_free_context_task, tls_ctx, NULL); +} +/* ***************************************************************************** +IO Functions Structure +***************************************************************************** */ -/** Sets the version information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_version_set(fio_http_s *, fio_str_info_s); +/* Returns the OpenSSL IO functions (implementation) */ +SFUNC fio_io_functions_s fio_openssl_io_functions(void) { + return (fio_io_functions_s){ + .build_context = fio___openssl_build_context, + .free_context = fio___openssl_free_context, + .start = fio___openssl_start, + .read = fio___openssl_read, + .write = fio___openssl_write, + .flush = fio___openssl_flush, + .cleanup = fio___openssl_cleanup, + }; +} -/** - * Gets the header information associated with the HTTP handle. - * - * Since more than a single value may be associated with a header name, the - * index may be used to collect subsequent values. - * - * An empty value is returned if no header value is available (or index is - * exceeded). - */ -SFUNC fio_str_info_s fio_http_request_header(fio_http_s *, - fio_str_info_s name, - size_t index); +/* Setup OpenSSL as TLS IO default */ +FIO_CONSTRUCTOR(fio___openssl_setup_default) { + static fio_io_functions_s FIO___OPENSSL_IO_FUNCS; + FIO___OPENSSL_IO_FUNCS = fio_openssl_io_functions(); + fio_io_tls_default_io_functions(&FIO___OPENSSL_IO_FUNCS); +#ifdef SIGPIPE + fio_signal_monitor(SIGPIPE, NULL, NULL); /* avoid OpenSSL issue... */ +#endif +} -/** - * Returns the number of headers named `name` that were received. - * - * If `name` buffer is `NULL`, returns the number of unique headers (not the - * number of unique values). - */ -SFUNC size_t fio_http_request_header_count(fio_http_s *, fio_str_info_s name); +/* ***************************************************************************** +OpenSSL Helpers Cleanup +***************************************************************************** */ -/** Sets the header information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_request_header_set(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value); +#endif /* FIO_EXTERN_COMPLETE */ +#endif /* HAVE_OPENSSL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_PUBSUB /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -/** Sets the header information associated with the HTTP handle. */ -SFUNC fio_str_info_s -fio_http_request_header_set_if_missing(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value); -/** Adds to the header information associated with the HTTP handle. */ -SFUNC fio_str_info_s fio_http_request_header_add(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value); -/** - * Iterates through all request headers (except cookies!). - * - * A non-zero return will stop iteration. - * - * Returns the number of iterations performed. If `callback` is `NULL`, returns - * the number of headers available (multi-value headers are counted as 1). - * */ -SFUNC size_t fio_http_request_header_each(fio_http_s *, - int (*callback)(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value, - void *udata), - void *udata); -/** Gets the body (payload) length associated with the HTTP handle. */ -SFUNC size_t fio_http_body_length(fio_http_s *); + Pub/Sub Services for IPC / Server applications -/** Adjusts the body's reading position. Negative values start at the end. */ -SFUNC size_t fio_http_body_seek(fio_http_s *, ssize_t pos); -/** Reads up to `length` of data from the body, returns nothing on EOF. */ -SFUNC fio_str_info_s fio_http_body_read(fio_http_s *, size_t length); -/** - * Reads from the body until finding `token`, reaching `limit` or EOF. - * - * Note: `limit` is ignored if zero or if the remaining data is lower than - * limit. - */ -SFUNC fio_str_info_s fio_http_body_read_until(fio_http_s *, - char token, - size_t limit); -/** Allocates a body (payload) of (at least) the `expected_length`. */ -SFUNC void fio_http_body_expect(fio_http_s *, size_t expected_length); +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_PUBSUB) && !defined(H___FIO_PUBSUB___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_PUBSUB___H -/** Writes `data` to the body (payload) associated with the HTTP handle. */ -SFUNC void fio_http_body_write(fio_http_s *, const void *data, size_t len); +/* ***************************************************************************** +Pub/Sub - message format +***************************************************************************** */ -/** - * If the body is stored in a temporary file, returns the file's handle. - * - * Otherwise returns -1. - */ -SFUNC int fio_http_body_fd(fio_http_s *); +/** Message structure, as received by the `on_message` subscription callback. */ +struct fio_msg_s { + /** A connection (if any) to which the subscription belongs. */ + fio_io_s *io; + /** The `udata` argument associated with the subscription. */ + void *udata; + /** Message ID. */ + uint64_t id; + /** Milliseconds since epoch. */ + uint64_t published; + /** + * A channel name, allowing for pub/sub patterns. + * + * NOTE: this is a shared copy - do NOT mutate the channel name string. + */ + fio_buf_info_s channel; + /** + * The actual message. + * + * NOTE: this is a shared copy - do NOT mutate the message payload string. + **/ + fio_buf_info_s message; + /** Channel name namespace. Negative values are reserved. */ + int16_t filter; + /** flag indicating if the message is JSON data or binary/text. */ + uint8_t is_json; +}; /* ***************************************************************************** -Cookies +Pub/Sub - Subscribe / Unsubscribe ***************************************************************************** */ -/** - * Possible values for the `same_site` property in the cookie settings. - * - * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie - */ -typedef enum fio_http_cookie_same_site_e { - /** allow the browser to dictate this property */ - FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT = 0, - /** The browser sends the cookie with cross-site and same-site requests. */ - FIO_HTTP_COOKIE_SAME_SITE_NONE, +/** Possible arguments for the fio_subscribe method. */ +typedef struct { /** - * The cookie is withheld on cross-site sub-requests. + * The subscription owner - if none, the subscription is owned by the system. * - * The cookie is sent when a user navigates to the URL from an external - * site. + * Note: + * + * Both the system and the `io` objects each manage channel listing + * which allows only a single subscription to the same channel. + * + * This means a single subscription per channel per IO and a single + * subscription per channel for the global system unless managing the + * subscription handle manually. */ - FIO_HTTP_COOKIE_SAME_SITE_LAX, - /** The browser sends the cookie only for same-site requests. */ - FIO_HTTP_COOKIE_SAME_SITE_STRICT, -} fio_http_cookie_same_site_e; - -/** - * This is a helper for setting cookie data. - * - * This struct is used together with the `fio_http_cookie_set` macro. i.e.: - * - * fio_http_set_cookie(h, - * .name = FIO_STR_INFO1("my_cookie"), - * .value = FIO_STR_INFO1("data")); - * - */ -typedef struct fio_http_cookie_args_s { - /** The cookie's name. */ - fio_str_info_s name; - /** The cookie's value (leave blank to delete cookie). */ - fio_str_info_s value; - /** The cookie's domain (optional). */ - fio_str_info_s domain; - /** The cookie's path (optional). */ - fio_str_info_s path; - /** Max Age (how long should the cookie persist), in seconds (0 == session).*/ - int max_age; - /** SameSite value. */ - fio_http_cookie_same_site_e same_site; - /** Limit cookie to secure connections.*/ - unsigned secure : 1; - /** Limit cookie to HTTP (intended to prevent JavaScript access/hijacking).*/ - unsigned http_only : 1; + fio_io_s *io; /** - * Set the Partitioned (third party) cookie flag: - * https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies + * A named `channel` to which the message was sent. + * + * Subscriptions require a match by both channel name and namespace filter. */ - unsigned partitioned : 1; -} fio_http_cookie_args_s; + fio_buf_info_s channel; + /** + * The callback to be called for each message forwarded to the subscription. + */ + void (*on_message)(fio_msg_s *msg); + /** An optional callback for when a subscription is canceled. */ + void (*on_unsubscribe)(void *udata); + /** The opaque udata value is ignored and made available to the callbacks. */ + void *udata; + /** Replay cached messages (if any) since supplied time in milliseconds. */ + uint64_t replay_since; + /** + * OPTIONAL: subscription handle return value - should be NULL when using + * automatic memory management with the IO or global environment. + * + * When set, the `io` pointer will be ignored and the subscription object + * handle will be written to the `subscription_handle_ptr` which MUST be + * used when unsubscribing. + * + * NOTE: this could cause subscriptions and memory leaks unless properly + * handled. + */ + uintptr_t *subscription_handle_ptr; + /** + * A numerical namespace `filter` subscribers need to match. + * + * Negative values are reserved for facil.io framework extensions. + * + * Filer channels are bound to the processes and workers, they are NOT + * forwarded to engines and can be used for inter process communication (IPC). + */ + int16_t filter; + /** If set, pattern matching will be used (name is a pattern). */ + uint8_t is_pattern; + /** If set, subscription will be limited to the root / master process. */ + uint8_t master_only; +} fio_subscribe_args_s; /** - * Sets a response cookie. - * - * Returns -1 on error and 0 on success. + * Subscribes to a channel / filter pair. * - * Note: Long cookie names and long cookie values will be considered a security - * violation and an error will be returned. Many browsers and proxies impose - * limits on headers and cookies, cookies often limited to 4Kb in total for both - * name and value. + * The on_unsubscribe callback will be called on failure. */ -SFUNC int fio_http_cookie_set(fio_http_s *h, fio_http_cookie_args_s); - -/** Named arguments helper. See fio_http_cookie_args_s for details. */ -#define fio_http_cookie_set(http___handle, ...) \ - fio_http_cookie_set((http___handle), (fio_http_cookie_args_s){__VA_ARGS__}) - -/** Returns a cookie value (either received of newly set), if any. */ -SFUNC fio_str_info_s fio_http_cookie(fio_http_s *, - const char *name, - size_t name_len); - -/** Iterates through all cookies. A non-zero return will stop iteration. */ -SFUNC size_t fio_http_cookie_each(fio_http_s *, - int (*callback)(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value, - void *udata), - void *udata); +SFUNC void fio_subscribe(fio_subscribe_args_s args); /** - * Iterates through all response set cookies. + * Subscribes to a channel / filter pair. * - * A non-zero return value from the callback will stop iteration. + * See `fio_subscribe_args_s` for details. */ -SFUNC size_t -fio_http_set_cookie_each(fio_http_s *h, - int (*callback)(fio_http_s *, - fio_str_info_s set_cookie_header, - fio_str_info_s value, - void *udata), - void *udata); - -/* ***************************************************************************** -Responding to an HTTP event. -***************************************************************************** */ - -/** Returns true if no HTTP headers / data was sent (a clean slate). */ -SFUNC int fio_http_is_clean(fio_http_s *); - -/** Returns true if the HTTP handle's response was sent. */ -SFUNC int fio_http_is_finished(fio_http_s *); - -/** Returns true if the HTTP handle's response is streaming. */ -SFUNC int fio_http_is_streaming(fio_http_s *); - -/** Returns true if the HTTP connection was (or should have been) upgraded. */ -SFUNC int fio_http_is_upgraded(fio_http_s *h); - -/** Returns true if the HTTP handle refers to a WebSocket connection. */ -SFUNC int fio_http_is_websocket(fio_http_s *); - -/** Returns true if the HTTP handle refers to an EventSource connection. */ -SFUNC int fio_http_is_sse(fio_http_s *); - -/** Returns true if handle is in the process of freeing itself. */ -SFUNC int fio_http_is_freeing(fio_http_s *); +#define fio_subscribe(...) fio_subscribe((fio_subscribe_args_s){__VA_ARGS__}) /** - * Gets the header information associated with the HTTP handle. - * - * Since more than a single value may be associated with a header name, the - * index may be used to collect subsequent values. - * - * An empty value is returned if no header value is available (or index is - * exceeded). - * - * If the response headers were already sent, the returned value is always - * empty. - */ -SFUNC fio_str_info_s fio_http_response_header(fio_http_s *, - fio_str_info_s name, - size_t index); -/** - * Returns the number of headers named `name` in the response. + * Cancels an existing subscriptions. * - * If `name` buffer is `NULL`, returns the number of unique headers (not the - * number of unique values). - */ -SFUNC size_t fio_http_response_header_count(fio_http_s *, fio_str_info_s name); - -/** - * Sets the header information associated with the HTTP handle. + * Accepts the same arguments as `fio_subscribe`, except the `udata` and + * callback details are ignored (no need to provide `udata` or callback + * details). * - * If the response headers were already sent, the returned value is always - * empty. - */ -SFUNC fio_str_info_s fio_http_response_header_set(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value); -/** - * Sets the header information associated with the HTTP handle. + * If a `subscription_handle_ptr` was provided it should contain the value of + * the subscription handle returned. * - * If the response headers were already sent, the returned value is always - * empty. + * Returns -1 if the subscription could not be found. Otherwise returns 0. */ -SFUNC fio_str_info_s -fio_http_response_header_set_if_missing(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value); +SFUNC int fio_unsubscribe(fio_subscribe_args_s args); /** - * Adds to the header information associated with the HTTP handle. + * Cancels an existing subscriptions. * - * If the response headers were already sent, the returned value is always - * empty. - */ -SFUNC fio_str_info_s fio_http_response_header_add(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value); - -/** - * Iterates through all response headers (except cookies!). + * Accepts the same arguments as `fio_subscribe`, except the `udata` and + * callback details are ignored (no need to provide `udata` or callback + * details). * - * A non-zero return will stop iteration. - * */ -SFUNC size_t fio_http_response_header_each(fio_http_s *, - int (*callback)(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value, - void *udata), - void *udata); - -/** Arguments for the fio_http_write function. */ -typedef struct fio_http_write_args_s { - /** The data to be written. */ - const void *buf; - /** The length of the data to be written. */ - size_t len; - /** The offset at which writing should begin. */ - size_t offset; - /** If streaming a file, set this value. The file is always closed. */ - int fd; - /** If the data is a buffer, this callback may be set to free it once sent. */ - void (*dealloc)(void *); - /** If the data is a buffer / a file - should it be copied? */ - int copy; - /** - * If `finish` is set, this data marks the end of the response. - * - * Otherwise the response will stream the data. - */ - int finish; -} fio_http_write_args_s; - -/** - * Writes `data` to the response body associated with the HTTP handle after - * sending all headers (no further headers may be sent). + * Returns -1 if the subscription could not be found. Otherwise returns 0. */ -SFUNC void fio_http_write(fio_http_s *, fio_http_write_args_s args); - -/** Named arguments helper. See fio_http_write and fio_http_write_args_s. */ -#define fio_http_write(http_handle, ...) \ - fio_http_write(http_handle, (fio_http_write_args_s){__VA_ARGS__}) -#define fio_http_finish(http_handle) fio_http_write(http_handle, .finish = 1) - -/** Closes a persistent HTTP connection (i.e., if upgraded). */ -SFUNC void fio_http_close(fio_http_s *h); - -/* ***************************************************************************** -WebSocket / SSE Helpers -***************************************************************************** */ - -/** Returns non-zero if request headers ask for a WebSockets Upgrade.*/ -SFUNC int fio_http_websocket_requested(fio_http_s *); - -/** Returns non-zero if the response accepts a WebSocket upgrade request. */ -SFUNC int fio_http_websocket_accepted(fio_http_s *h); - -/** Sets response data to agree to a WebSockets Upgrade.*/ -SFUNC void fio_http_upgrade_websocket(fio_http_s *); - -/** Sets request data to request a WebSockets Upgrade.*/ -SFUNC void fio_http_websocket_set_request(fio_http_s *); - -/** Returns non-zero if request headers ask for an EventSource (SSE) Upgrade.*/ -SFUNC int fio_http_sse_requested(fio_http_s *); - -/** Returns non-zero if the response accepts an SSE request. */ -SFUNC int fio_http_sse_accepted(fio_http_s *h); - -/** Sets response data to agree to an EventSource (SSE) Upgrade.*/ -SFUNC void fio_http_upgrade_sse(fio_http_s *); - -/** Sets request data to request an EventSource (SSE) Upgrade.*/ -SFUNC void fio_http_sse_set_request(fio_http_s *); - -/* ***************************************************************************** -MIME File Type Helpers - NOT thread safe! -***************************************************************************** */ - -/** Registers a Mime-Type to be associated with the file extension. */ -SFUNC int fio_http_mimetype_register(char *file_ext, - size_t file_ext_len, - fio_str_info_s mime_type); +#define fio_unsubscribe(...) \ + fio_unsubscribe((fio_subscribe_args_s){__VA_ARGS__}) -/** Finds the Mime-Type associated with the file extension (if registered). */ -SFUNC fio_str_info_s fio_http_mimetype(char *file_ext, size_t file_ext_len); +/* A callback for IO subscriptions - sends raw message data. */ +FIO_SFUNC void FIO_ON_MESSAGE_SEND_MESSAGE(fio_msg_s *msg); /* ***************************************************************************** -HTTP Body Parsing Helpers (TODO!) +Pub/Sub - Publish ***************************************************************************** */ -/* ***************************************************************************** -Header Parsing Helpers -***************************************************************************** */ +/** A pub/sub engine data structure. See details later on. */ +typedef struct fio_pubsub_engine_s fio_pubsub_engine_s; + +/** Publishing and on_message callback arguments. */ +typedef struct fio_publish_args_s { + /** The pub/sub engine that should be used to forward this message. */ + fio_pubsub_engine_s const *engine; + /** If `from` is specified, it will be skipped (won't receive message) + * UNLESS a non-native `engine` is specified. */ + fio_io_s *from; + /** Message ID (if missing, a random ID will be generated). */ + uint64_t id; + /** Milliseconds since epoch (if missing, defaults to "now"). */ + uint64_t published; + /** The target named channel. */ + fio_buf_info_s channel; + /** The message body / content. */ + fio_buf_info_s message; + /** A numeral namespace for channel names. Negative values are reserved. */ + int16_t filter; + /** A flag indicating if the message is JSON data or not. */ + uint8_t is_json; +} fio_publish_args_s; /** - * Copies all header data, from possibly an array of identical response headers, - * resulting in a parsed format outputted to `buf_parsed`. + * Publishes a message to the relevant subscribers (if any). * - * Returns 0 on success or -1 on error (i.e., `buf_parsed.capa` wasn't enough - * for the parsed output). + * By default the message is sent using the `FIO_PUBSUB_DEFAULT` engine (set by + * default to `FIO_PUBSUB_LOCAL` which publishes to all processes, including the + * calling process). * - * Note that the parsed output isn't readable as a string, but is designed to - * work with the `FIO_HTTP_PARSED_HEADER_EACH` and - * `FIO_HTTP_HEADER_VALUE_EACH_PROPERTY` property. + * To limit the message only to other processes (exclude the calling process), + * use the `FIO_PUBSUB_SIBLINGS` engine. * - * See also `fio_http_response_header_parse`. + * To limit the message only to the calling process, use the + * `FIO_PUBSUB_PROCESS` engine. + * + * To limit the message only to the root process, use the `FIO_PUBSUB_ROOT` + * engine. */ -SFUNC int fio_http_response_header_parse(fio_http_s *h, - fio_str_info_s *buf_parsed, - fio_str_info_s header_name); - +SFUNC void fio_publish(fio_publish_args_s args); /** - * Copies all header data, from possibly an array of identical response headers, - * resulting in a parsed format outputted to `buf_parsed`. + * Publishes a message to the relevant subscribers (if any). * - * Returns 0 on success or -1 on error (i.e., `buf_parsed.capa` wasn't enough - * for the parsed output). + * By default the message is sent using the `FIO_PUBSUB_DEFAULT` engine (set by + * default to `FIO_PUBSUB_LOCAL` which publishes to all processes, including the + * calling process). * - * Note that the parsed output isn't readable as a string, but is designed to - * work with the `FIO_HTTP_PARSED_HEADER_EACH` and - * `FIO_HTTP_HEADER_VALUE_EACH_PROPERTY` property. + * To limit the message only to other processes (exclude the calling process), + * use the `FIO_PUBSUB_SIBLINGS` engine. * - * i.e.: + * To limit the message only to the calling process, use the + * `FIO_PUBSUB_PROCESS` engine. * - * ```c - * FIO_STR_INFO_TMP_VAR(buf, 1023); // tmp buffer for the parsed output - * fio_http_s *h = fio_http_new(); // using a mock HTTP handle - * fio_http_request_header_add( - * h, - * FIO_STR_INFO2("accept", 6), - * FIO_STR_INFO1("text/html, application/json;q=0.9; d=500, image/png")); - * fio_http_request_header_add(h, - * FIO_STR_INFO2("accept", 6), - * FIO_STR_INFO1("text/yaml")); - * FIO_ASSERT( // in production do NOT assert, but route to error instead! - * !fio_http_request_header_parse(h, &buf, FIO_STR_INFO2("accept", 6)), - * "parse returned error!"); - * FIO_HTTP_PARSED_HEADER_EACH(buf, value) { - * printf("* processing value (%zu bytes): %s\n", value.len, value.buf); - * FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(value, prop) { - * printf("* for value %s: (%zu,%zu bytes) %s = %s\n", - * value.buf, - * prop.name.len, - * prop.value.len, - * prop.name.buf, - * prop.value.buf); - * } - * } - * ``` + * To limit the message only to the root process, use the `FIO_PUBSUB_ROOT` + * engine. */ -SFUNC int fio_http_request_header_parse(fio_http_s *h, - fio_str_info_s *buf_parsed, - fio_str_info_s header_name); +#define fio_publish(...) fio_publish((fio_publish_args_s){__VA_ARGS__}) /** - * Parses header for multiple values and properties and iterates over all - * values. - * - * This MACRO will allocate 2048 bytes on the stack for parsing the header - * values and properties, if more space is necessary dig deeper. + * Defers the current callback, so it will be called again for the message. * - * Use FIO_HTTP_HEADER_VALUE_EACH_PROPERTY to iterate over a value's properties. + * After calling this function, the `msg` object must NOT be accessed again. */ -#define FIO_HTTP_HEADER_EACH_VALUE(/* fio_http_s */ http_handle, \ - /* int / bool */ is_request, \ - /* fio_str_info_s */ header_name, \ - /* chosen var named */ value) \ - for (char fio___buf__##value##__[2048], /* allocate buffer on stack */ \ - *fio___buf__##value##_ptr = NULL; \ - !fio___buf__##value##_ptr; \ - fio___buf__##value##_ptr = fio___buf__##value##__) \ - for (fio_str_info_s fio___buf__##value##__str = /* declare buffer var */ \ - FIO_STR_INFO3(fio___buf__##value##__, 0, 2048); \ - fio___buf__##value##__str.buf == fio___buf__##value##__; \ - fio___buf__##value##__str.buf = fio___buf__##value##__ + 1) \ - if (!((is_request ? fio_http_request_header_parse \ - : fio_http_response_header_parse)( \ - http_handle, /* parse headers */ \ - &fio___buf__##value##__str, \ - header_name))) \ - FIO_HTTP_PARSED_HEADER_EACH(fio___buf__##value##__str, value) /* loop \ - */ +SFUNC void fio_pubsub_message_defer(fio_msg_s *msg); -/** Iterated through the properties associated with a parsed header values. */ -#define FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(/* fio_str_info_s */ value, \ - /* chosen var named */ property) +/* ***************************************************************************** +Pub/Sub - History and Event Replay - TODO!!! +***************************************************************************** */ -/** Used internally to iterate over a parsed header buffer. */ -#define FIO_HTTP_PARSED_HEADER_EACH(/* fio_str_info_s */ buf_parsed, \ - /* chosen var named */ value) +/** Sets the maximum number of messages to be stored in the history store. */ +// SFUNC void fio_pubsub_store_limit(size_t messages); /* ***************************************************************************** -General Helpers +Pub/Sub - defaults and builtin pub/sub engines ***************************************************************************** */ -/** Sends the requested error message and finishes the response. */ -SFUNC int fio_http_send_error_response(fio_http_s *h, size_t status); +/** Flag bits for internal usage (message exchange network format). */ +typedef enum { + /* pub/sub messages */ + FIO___PUBSUB_JSON = 1, + FIO___PUBSUB_PROCESS = 2, + FIO___PUBSUB_ROOT = 4, + FIO___PUBSUB_SIBLINGS = 8, + FIO___PUBSUB_WORKERS = (8 | 2), + FIO___PUBSUB_LOCAL = (8 | 4 | 2), + FIO___PUBSUB_REMOTE = 16, + FIO___PUBSUB_CLUSTER = (16 | 8 | 4 | 2), + FIO___PUBSUB_REPLAY = 32, /* history replay message */ -/** Returns true (1) if the ETag response matches an if-none-match request. */ -SFUNC int fio_http_etag_is_match(fio_http_s *h); + /* internal messages */ + FIO___PUBSUB_SPECIAL = 128, + FIO___PUBSUB_SUB = (128 | 1), + FIO___PUBSUB_UNSUB = (128 | 2), + FIO___PUBSUB_IDENTIFY = (128 | 4), /* identify remote connection */ + FIO___PUBSUB_FORWARDER = (128 | 8), /* forward to external engine */ + FIO___PUBSUB_PING = (128 | 16), -/** - * Attempts to send a static file from the `root` folder. On success the - * response is complete and 0 is returned. Otherwise returns -1. - */ -SFUNC int fio_http_static_file_response(fio_http_s *h, - fio_str_info_s root_folder, - fio_str_info_s file_name, - size_t max_age); + FIO___PUBSUB_HISTORY_START = (128 | 32), + FIO___PUBSUB_HISTORY_END = (128 | 64), +} fio___pubsub_msg_flags_e; -/** Returns a human readable string related to the HTTP status number. */ -SFUNC fio_str_info_s fio_http_status2str(size_t status); +/** Used to publish the message exclusively to the root / master process. */ +#define FIO_PUBSUB_ROOT ((fio_pubsub_engine_s *)FIO___PUBSUB_ROOT) +/** Used to publish the message only within the current process. */ +#define FIO_PUBSUB_PROCESS ((fio_pubsub_engine_s *)FIO___PUBSUB_PROCESS) +/** Used to publish the message except within the current process. */ +#define FIO_PUBSUB_SIBLINGS ((fio_pubsub_engine_s *)FIO___PUBSUB_SIBLINGS) +/** Used to publish the message for this process, its siblings and root. */ +#define FIO_PUBSUB_LOCAL ((fio_pubsub_engine_s *)FIO___PUBSUB_LOCAL) +/** Used to publish the message to any possible publishers. */ +#define FIO_PUBSUB_CLUSTER ((fio_pubsub_engine_s *)FIO___PUBSUB_CLUSTER) -/** Logs an HTTP (response) to STDOUT. */ -SFUNC void fio_http_write_log(fio_http_s *h); +#if defined(FIO_EXTERN) /* static definitions can't be easily repeated. */ +/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ +SFUNC const fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT; /** - * Writes peer address to `dest` starting with the `forwarded` header, with a - * fallback to actual socket address and a final fallback to `"[unknown]"`. + * The pattern matching callback used for pattern matching. * - * If `unknown` is returned, the function returns -1. if `dest` capacity is too - * small, the number of bytes required will be returned. + * Returns 1 on a match or 0 if the string does not match the pattern. * - * If all goes well, this function returns 0. + * By default, the value is set to `fio_glob_match` (see facil.io's C STL). */ -SFUNC int fio_http_from(fio_str_info_s *dest, const fio_http_s *h); +SFUNC uint8_t (*FIO_PUBSUB_PATTERN_MATCH)(fio_str_info_s pattern, + fio_str_info_s channel); +#endif /* ***************************************************************************** -The HTTP Controller +Message metadata (advance usage API) ***************************************************************************** */ /** - * The HTTP Controller manages all the callbacks required by the HTTP Handler in - * order for HTTP responses and requests to be sent. + * The number of different metadata callbacks that can be attached. + * + * Effects performance. + * + * The default value should be enough for the following metadata objects: + * - WebSocket server headers. + * - WebSocket client (header + masked message copy). + * - EventSource (SSE) encoded named channel and message. */ -struct fio_http_controller_s { - /* MUST be initialized to zero, used internally by the HTTP Handle. */ - uintptr_t private_flags; - /** Called when an HTTP handle is freed. */ - void (*on_destroyed)(fio_http_s *h); - /** Informs the controller that request / response headers must be sent. */ - void (*send_headers)(fio_http_s *h); - /** called by the HTTP handle for each body chunk (or to finish a response. */ - void (*write_body)(fio_http_s *h, fio_http_write_args_s args); - /** called once a request / response had finished */ - void (*on_finish)(fio_http_s *h); - /** called to close an HTTP connection */ - void (*close_io)(fio_http_s *h); - /** called when the file descriptor is directly required */ - int (*get_fd)(fio_http_s *h); -}; - -/* ***************************************************************************** -HTTP Handle Implementation - inlined static functions -***************************************************************************** */ - -#define FIO___HTTP_GETSET_PTR(type, name, index_, pre_set_code) \ - /** Used internally to set / get the propecrty at its known pointer index. \ - */ \ - FIO_IFUNC type *fio_http_##name(fio_http_s *h) { \ - return ((type **)h)[index_]; \ - } \ - /** Used internally to set / get the propercty at its known pointer index. \ - */ \ - FIO_IFUNC type *fio_http_##name##_set(fio_http_s *h, type *ptr) { \ - pre_set_code; \ - return (((type **)h)[index_] = ptr); \ - } - -SFUNC fio_http_controller_s *fio___http_controller_validate( - fio_http_controller_s *c); +#ifndef FIO___PUBSUB_METADATA_STORE_LIMIT +#define FIO___PUBSUB_METADATA_STORE_LIMIT 4 +#endif -/* Create fio_http_udata_(get|set) functions */ -FIO___HTTP_GETSET_PTR(void, udata, 0, (void)0) -/* Create fio_http_pdata_(get|set) functions */ -FIO___HTTP_GETSET_PTR(void, udata2, 1, (void)0) -/* Create fio_http_cdata_(get|set) functions */ -FIO___HTTP_GETSET_PTR(void, cdata, 2, (void)0) -/* Create fio_http_controller_(get|set) functions */ -FIO___HTTP_GETSET_PTR(fio_http_controller_s, - controller, - 3, - ptr = fio___http_controller_validate(ptr)) +/** Pub/Sub Metadata callback type. */ +typedef void *(*fio_msg_metadata_fn)(fio_msg_s *); -#undef FIO___HTTP_GETSET_PTR -/* -REMEMBER: -======== +/** + * It's possible to attach metadata to facil.io pub/sub messages before they are + * published. + * + * This allows, for example, messages to be encoded as network packets for + * outgoing protocols (i.e., encoding for WebSocket transmissions), improving + * performance in large network based broadcasting. + * + * Up to `FIO___PUBSUB_METADATA_STORE_LIMIT` metadata callbacks can be attached. + * + * The callback should return a `void *` pointer. + * + * To remove a callback, call `fio_message_metadata_remove` with the returned + * value. + * + * The cluster messaging system allows some messages to be flagged as JSON and + * this flag is available to the metadata callback. + * + * Returns zero (0) on success or -1 on failure. + * + * Multiple `fio_message_metadata_add` calls increase a reference count and + * should be matched by the same number of `fio_message_metadata_remove`. + */ +SFUNC int fio_message_metadata_add(fio_msg_metadata_fn metadata_func, + void (*cleanup)(void *)); -All memory allocations should use: -* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) -* FIO_MEM_FREE_(ptr, size) +/** + * Removed the metadata callback. + * + * Removal might be delayed if live metatdata exists. + */ +SFUNC void fio_message_metadata_remove(fio_msg_metadata_fn metadata_func); -*/ +/** Finds the message's metadata, returning the data or NULL. */ +SFUNC void *fio_message_metadata(fio_msg_s *msg, + fio_msg_metadata_fn metadata_func); /* ***************************************************************************** -Header Parsing Helpers - inlined helpers +Pub/Sub Middleware and Extensions ("Engines") ***************************************************************************** */ -#define FIO___HTTP_PARSED_HEADER_VALUE 0 -#define FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN 1 -#define FIO___HTTP_PARSED_HEADER_PROPERTY_DATA 2 - -typedef struct { - fio_str_info_s name; - fio_str_info_s value; -} fio___http_header_property_s; - /** - * Assumes a Buffer of bytes containing length info and string data as such: - * [ NUL byte - 1 byte at head of format ] - * repeat - * [ 2 byte info: (type | (len << 2)) ] - * [ Optional 2 byte info: (len << 2) (if type was 1)] - * [ String of `len` bytes][ NUL byte (not counted in `len`)] + * facil.io can be linked with external Pub/Sub services using "engines". + * + * Engines MUST provide the listed function pointers and should be attached + * using the `fio_pubsub_attach` function. + * + * Engines that were connected / attached using `fio_pubsub_attach` MUST + * disconnect / detach, before being destroyed, by using the `fio_pubsub_detach` + * function. + * + * When an engine received a message to publish, it should call the + * `fio_publish` function with the engine to which the message is forwarded. + * i.e.: + * + * fio_publish( + * .engine = FIO_PUBSUB_LOCAL, + * .channel = channel_name, + * .message = msg_body); + * + * IMPORTANT: The callbacks will be called by the main IO thread, so they should + * never block. Long tasks should copy the data and scheduling an external task + * (i.e., using `fio_io_defer`). */ +struct fio_pubsub_engine_s { + /** Called after the engine was detached, may be used for cleanup. */ + void (*detached)(const fio_pubsub_engine_s *eng); + /** Subscribes to a channel. Called ONLY in the Root (master) process. */ + void (*subscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Subscribes to a pattern. Called ONLY in the Root (master) process. */ + void (*psubscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Unsubscribes to a channel. Called ONLY in the Root (master) process. */ + void (*unsubscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Unsubscribe to a pattern. Called ONLY in the Root (master) process. */ + void (*punsubscribe)(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter); + /** Publishes a message through the engine. Called by any worker / thread. */ + void (*publish)(const fio_pubsub_engine_s *eng, fio_msg_s *msg); +}; -FIO_IFUNC fio_str_info_s fio___http_parsed_headers_next(fio_str_info_s value) { - for (;;) { - const size_t coded = (size_t)fio_buf2u16u(value.buf + value.len + 1U); - if (!coded) - return (value = (fio_str_info_s){0}); - const size_t block_len = coded >> 2; - value.buf += value.len + 3; - value.len = block_len; - if (!(coded & 3)) - return value; - value.buf -= 3; /* reposition to read NUL + value rather than text start */ - } -} - -FIO_IFUNC fio___http_header_property_s -fio___http_parsed_property_next(fio___http_header_property_s property) { - for (;;) { - size_t coded = - (size_t)fio_buf2u16u(property.value.buf + property.value.len + 1); - if (!(coded & 3)) - return (property = (fio___http_header_property_s){{0}, {0}}); - if ((coded & 3) == FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN) { - property.value.buf += 2; - coded = (size_t)fio_buf2u16u(property.value.buf + property.value.len + 1); - } - if ((coded & 3) != 2) - return (property = (fio___http_header_property_s){{0}, {0}}); - coded >>= 2; - property.name.buf = property.value.buf + property.value.len + 3; - property.name.len = coded; - coded = (size_t)fio_buf2u16u(property.name.buf + property.name.len + 1); - FIO_ASSERT_DEBUG((coded & 3) == 2, - "header property value parsing format error"); - property.value.buf = property.name.buf + property.name.len + 3; - property.value.len = coded >> 2; - return property; - } -} - -#undef FIO_HTTP_PARSED_HEADER_EACH -#define FIO_HTTP_PARSED_HEADER_EACH(buf_parsed, value) \ - for (fio_str_info_s value = \ - fio___http_parsed_headers_next(FIO_STR_INFO2(buf_parsed.buf, 0)); \ - value.len; \ - value = fio___http_parsed_headers_next(value)) - -#undef FIO_HTTP_HEADER_VALUE_EACH_PROPERTY -#define FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(value_, property) \ - for (fio___http_header_property_s property = \ - fio___http_parsed_property_next( \ - (fio___http_header_property_s){.value = value_}); \ - property.name.len; \ - property = fio___http_parsed_property_next(property)) +/** + * Attaches an engine, so it's callback can be called by facil.io. + * + * The `(p)subscribe` callback will be called for every existing channel. + * + * This can be called multiple times resulting in re-running the `(p)subscribe` + * callbacks. + * + * NOTE: engines are automatically detached from child processes but can be + * safely used even so - messages are always forwarded to the engine attached to + * the root (master) process. + * + * NOTE: engines should publish events to `FIO_PUBSUB_LOCAL`. + */ +SFUNC void fio_pubsub_attach(fio_pubsub_engine_s *engine); -/* ***************************************************************************** -HTTP Handle Implementation - possibly externed functions. -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) +/** Schedules an engine for Detachment, so it could be safely destroyed. */ +SFUNC void fio_pubsub_detach(fio_pubsub_engine_s *engine); /* ***************************************************************************** -Helpers - memory allocation & logging time collection +Pub/Sub Clustering and Security ***************************************************************************** */ -FIO_LEAK_COUNTER_DEF(http___keystr_allocator) - -FIO_SFUNC void fio___http_keystr_free(void *ptr, size_t len) { - if (!ptr) - return; - FIO_LEAK_COUNTER_ON_FREE(http___keystr_allocator); - FIO_MEM_FREE_(ptr, len); - (void)len; /* if unused */ -} -FIO_SFUNC void *fio___http_keystr_alloc(size_t capa) { - FIO_LEAK_COUNTER_ON_ALLOC(http___keystr_allocator); - return FIO_MEM_REALLOC_(NULL, 0, capa, 0); -} +/** Sets the current IPC socket address (can't be changed while running). */ +SFUNC int fio_pubsub_ipc_url_set(char *str, size_t len); -#if FIO_HTTP_EXACT_LOGGING -#define FIO___HTTP_TIME_DIV 1000000 -#define FIO___HTTP_TIME_UNIT "us" -FIO_IFUNC int64_t fio_http_get_timestump(void) { - return fio_time2micro(fio_time_real()); -} -#else -#define FIO___HTTP_TIME_DIV 1000 -#define FIO___HTTP_TIME_UNIT "ms" -int64_t fio_srv_last_tick(void); -FIO_IFUNC int64_t fio_http_get_timestump(void) { - return (int64_t)fio_srv_last_tick(); -} -#endif +/** Returns a pointer to the current IPC socket address. */ +SFUNC const char *fio_pubsub_ipc_url(void); -/* date/time string caching for HTTP date header */ -FIO_SFUNC fio_str_info_s fio_http_date(uint64_t now_in_seconds) { - static char date_buf[128]; - static size_t date_len; - static uint64_t date_buf_val; - if (date_buf_val == now_in_seconds) - return FIO_STR_INFO2(date_buf, date_len); - date_len = fio_time2rfc7231(date_buf, now_in_seconds); - date_buf[date_len] = 0; - date_buf_val = now_in_seconds; - return FIO_STR_INFO2(date_buf, date_len); -} +/** + * Sets a (possibly shared) secret for securing pub/sub communication. + * + * If `secret` is `NULL`, the environment variable `"SECRET"` will be used or. + * + * If secret is never set, a random secret will be generated. + * + * NOTE: secrets produce a SHA-512 Hash that is used to produce 256 bit keys. + */ +SFUNC void fio_pubsub_secret_set(char *secret, size_t len); -/* date/time string caching for HTTP logging */ -FIO_SFUNC fio_str_info_s fio_http_log_time(uint64_t now_in_seconds) { - static char date_buf[128]; - static size_t date_len; - static uint64_t date_buf_val; - if (date_buf_val == now_in_seconds) - return FIO_STR_INFO2(date_buf, date_len); - date_len = fio_time2log(date_buf, now_in_seconds); - date_buf[date_len] = 0; - date_buf_val = now_in_seconds; - return FIO_STR_INFO2(date_buf, date_len); -} +/** Auto-peer detection and pub/sub multi-machine clustering using `port`. */ +SFUNC void fio_pubsub_broadcast_on_port(int16_t port); -#define FIO___RECURSIVE_INCLUDE 1 /* ***************************************************************************** -String Cache -***************************************************************************** */ -#define FIO_MAP_NAME fio___http_str_cache -#define FIO_MAP_LRU FIO_HTTP_CACHE_LIMIT -#define FIO_MAP_KEY_BSTR -#include FIO_INCLUDE_FILE -static struct { - fio___http_str_cache_s cache; - FIO___LOCK_TYPE lock; -} FIO___HTTP_STRING_CACHE[2] = {{.lock = FIO___LOCK_INIT}, - {.lock = FIO___LOCK_INIT}}; -#define FIO___HTTP_STR_CACHE_COOKIE 0 -#define FIO___HTTP_STR_CACHE_VALUE 1 -#if FIO_HTTP_CACHE_STATIC_HEADERS +Pub/Sub Implementation + -#define FIO___HTTP_STATIC_CACHE_MASK 127 -#define FIO___HTTP_STATIC_CACHE_FOLD 56 -#define FIO___HTTP_STATIC_CACHE_STEP 1 -#define FIO___HTTP_STATIC_CACHE_STEP_LIMIT 2 -#define FIO___HTTP_STATIC_CACHE_MASK_INV \ - (~(uint16_t)FIO___HTTP_STATIC_CACHE_MASK) -static struct { - fio___bstr_meta_s meta; - char str[32]; -} FIO___HTTP_STATIC_CACHE[] = { -#define FIO___HTTP_STATIC_CACHE_SET(s) \ - { .meta = {.len = (uint32_t)(sizeof(s) - 1), .ref = 3}, .str = s } - FIO___HTTP_STATIC_CACHE_SET("a-im"), - FIO___HTTP_STATIC_CACHE_SET("accept"), - FIO___HTTP_STATIC_CACHE_SET("accept-charset"), - FIO___HTTP_STATIC_CACHE_SET("accept-datetime"), - FIO___HTTP_STATIC_CACHE_SET("accept-encoding"), - FIO___HTTP_STATIC_CACHE_SET("accept-language"), - FIO___HTTP_STATIC_CACHE_SET("accept-ranges"), - FIO___HTTP_STATIC_CACHE_SET("access-control-allow-origin"), - FIO___HTTP_STATIC_CACHE_SET("access-control-request-headers"), - FIO___HTTP_STATIC_CACHE_SET("access-control-request-method"), - FIO___HTTP_STATIC_CACHE_SET("age"), - FIO___HTTP_STATIC_CACHE_SET("allow"), - FIO___HTTP_STATIC_CACHE_SET("authorization"), - FIO___HTTP_STATIC_CACHE_SET("cache-control"), - FIO___HTTP_STATIC_CACHE_SET("connection"), - FIO___HTTP_STATIC_CACHE_SET("content-disposition"), - FIO___HTTP_STATIC_CACHE_SET("content-encoding"), - FIO___HTTP_STATIC_CACHE_SET("content-language"), - FIO___HTTP_STATIC_CACHE_SET("content-length"), - FIO___HTTP_STATIC_CACHE_SET("content-location"), - FIO___HTTP_STATIC_CACHE_SET("content-range"), - FIO___HTTP_STATIC_CACHE_SET("content-type"), - FIO___HTTP_STATIC_CACHE_SET("cookie"), - FIO___HTTP_STATIC_CACHE_SET("date"), - FIO___HTTP_STATIC_CACHE_SET("dnt"), - FIO___HTTP_STATIC_CACHE_SET("etag"), - FIO___HTTP_STATIC_CACHE_SET("expect"), - FIO___HTTP_STATIC_CACHE_SET("expires"), - FIO___HTTP_STATIC_CACHE_SET("forwarded"), - FIO___HTTP_STATIC_CACHE_SET("from"), - FIO___HTTP_STATIC_CACHE_SET("host"), - FIO___HTTP_STATIC_CACHE_SET("if-match"), - FIO___HTTP_STATIC_CACHE_SET("if-modified-since"), - FIO___HTTP_STATIC_CACHE_SET("if-none-match"), - FIO___HTTP_STATIC_CACHE_SET("if-range"), - FIO___HTTP_STATIC_CACHE_SET("if-unmodified-since"), - FIO___HTTP_STATIC_CACHE_SET("last-modified"), - FIO___HTTP_STATIC_CACHE_SET("link"), - FIO___HTTP_STATIC_CACHE_SET("location"), - FIO___HTTP_STATIC_CACHE_SET("max-forwards"), - FIO___HTTP_STATIC_CACHE_SET("origin"), - FIO___HTTP_STATIC_CACHE_SET("pragma"), - FIO___HTTP_STATIC_CACHE_SET("proxy-authenticate"), - FIO___HTTP_STATIC_CACHE_SET("proxy-authorization"), - FIO___HTTP_STATIC_CACHE_SET("range"), - FIO___HTTP_STATIC_CACHE_SET("referer"), - FIO___HTTP_STATIC_CACHE_SET("refresh"), - FIO___HTTP_STATIC_CACHE_SET("retry-after"), - FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua"), - FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua-mobile"), - FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua-platform"), - FIO___HTTP_STATIC_CACHE_SET("sec-fetch-dest"), - FIO___HTTP_STATIC_CACHE_SET("sec-fetch-mode"), - FIO___HTTP_STATIC_CACHE_SET("sec-fetch-site"), - FIO___HTTP_STATIC_CACHE_SET("sec-fetch-user"), - FIO___HTTP_STATIC_CACHE_SET("server"), - FIO___HTTP_STATIC_CACHE_SET("set-cookie"), - FIO___HTTP_STATIC_CACHE_SET("strict-transport-security"), - FIO___HTTP_STATIC_CACHE_SET("te"), - FIO___HTTP_STATIC_CACHE_SET("transfer-encoding"), - FIO___HTTP_STATIC_CACHE_SET("upgrade"), - FIO___HTTP_STATIC_CACHE_SET("upgrade-insecure-requests"), - FIO___HTTP_STATIC_CACHE_SET("user-agent"), - FIO___HTTP_STATIC_CACHE_SET("vary"), - FIO___HTTP_STATIC_CACHE_SET("via"), - FIO___HTTP_STATIC_CACHE_SET("warning"), - FIO___HTTP_STATIC_CACHE_SET("www-authenticate"), - FIO___HTTP_STATIC_CACHE_SET("x-csrf-token"), - FIO___HTTP_STATIC_CACHE_SET("x-forwarded-for"), - FIO___HTTP_STATIC_CACHE_SET("x-forwarded-host"), - FIO___HTTP_STATIC_CACHE_SET("x-forwarded-proto"), - FIO___HTTP_STATIC_CACHE_SET("x-requested-with"), - {{0}}}; +The implementation has a big number of interconnected modules: +- Distribution Channels (`fio_channel_s` and `FIO___PUBSUB_POSTOFFICE`) +- Subscriptions (`fio_subscription_s`) +- Metadata Management. +- External Distribution Engines (`fio_pubsub_engine_s`) +- Message format and their network exchange protocols (`fio___pubsub_message_s`) -static uint16_t FIO___HTTP_STATIC_CACHE_IMAP[FIO___HTTP_STATIC_CACHE_MASK + 1] = - {0}; +Message wire format (as 64 bit numerals in little endien encoding): +[0] Message ID +[1] Publication time (milliseconds since epoch) +[2] 16 bit filter | 16 bit channel len | 24 bit message len | 8 bit flags +| --- encryption starts --- | +| X bytes - (channel name, + 1 NUL terminator) | +| Y bytes - (message data, + 1 NUL terminator) | +| --- encryption ends --- | +| 16 bytes - (optional) message MAC | +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -static void fio___http_str_cached_init(void) { - FIO_MEMSET(FIO___HTTP_STATIC_CACHE_IMAP, - 0, - (FIO___HTTP_STATIC_CACHE_MASK + 1) * - sizeof(FIO___HTTP_STATIC_CACHE_IMAP[0])); +#undef FIO___PUBSUB_MESSAGE_HEADER +#define FIO___PUBSUB_MESSAGE_HEADER 24 +/* header + 2 NUL bytes (message + channel) + 16 byte MAC */ +#undef FIO___PUBSUB_MESSAGE_OVERHEAD_NET +#define FIO___PUBSUB_MESSAGE_OVERHEAD_NET (FIO___PUBSUB_MESSAGE_HEADER + 18) +/* extra 2 NUL bytes (after message & channel name) */ +#undef FIO___PUBSUB_MESSAGE_OVERHEAD +#define FIO___PUBSUB_MESSAGE_OVERHEAD (FIO___PUBSUB_MESSAGE_OVERHEAD_NET + 2) - for (size_t i = 0; FIO___HTTP_STATIC_CACHE[i].meta.ref; ++i) { - uint64_t hash = fio_stable_hash(FIO___HTTP_STATIC_CACHE[i].str, - FIO___HTTP_STATIC_CACHE[i].meta.len, - 0); /* use stable hash (change resilient) */ - hash ^= hash >> FIO___HTTP_STATIC_CACHE_FOLD; - size_t protection = 0; - while (FIO___HTTP_STATIC_CACHE_IMAP[hash & FIO___HTTP_STATIC_CACHE_MASK]) { - FIO_ASSERT( - (FIO___HTTP_STATIC_CACHE_IMAP[hash & FIO___HTTP_STATIC_CACHE_MASK] & - FIO___HTTP_STATIC_CACHE_MASK_INV) != - (hash & FIO___HTTP_STATIC_CACHE_MASK_INV), - "full collision for HTTP static hash (%zu == %zu!", - (size_t)(hash & FIO___HTTP_STATIC_CACHE_MASK), - i); - hash += hash >> FIO___HTTP_STATIC_CACHE_STEP; - FIO_ASSERT((protection++) < FIO___HTTP_STATIC_CACHE_STEP_LIMIT, - "HTTP static cache collision overflow @ %zu (%s)", - i, - FIO___HTTP_STATIC_CACHE[i].str); - } - FIO___HTTP_STATIC_CACHE_IMAP[hash & FIO___HTTP_STATIC_CACHE_MASK] = - (hash & FIO___HTTP_STATIC_CACHE_MASK_INV) | i; - } -} +/* ***************************************************************************** +Pub/Sub - defaults and builtin pub/sub engines +***************************************************************************** */ -static char *fio___http_str_cached_static(char *str, size_t len) { - uint64_t hash = - fio_stable_hash(str, len, 0); /* use stable hash (change resilient) */ - hash ^= hash >> FIO___HTTP_STATIC_CACHE_FOLD; - for (size_t attempts = 0; attempts < FIO___HTTP_STATIC_CACHE_STEP_LIMIT; - ++attempts) { - if ((FIO___HTTP_STATIC_CACHE_IMAP[hash & FIO___HTTP_STATIC_CACHE_MASK] & - FIO___HTTP_STATIC_CACHE_MASK_INV) == - (hash & FIO___HTTP_STATIC_CACHE_MASK_INV)) - break; - hash += hash >> FIO___HTTP_STATIC_CACHE_STEP; - } - size_t pos = - FIO___HTTP_STATIC_CACHE_IMAP[hash & FIO___HTTP_STATIC_CACHE_MASK] & - FIO___HTTP_STATIC_CACHE_MASK; - if (FIO___HTTP_STATIC_CACHE[pos].meta.len == len && - !FIO_MEMCMP(str, FIO___HTTP_STATIC_CACHE[pos].str, len)) { - return FIO___HTTP_STATIC_CACHE[pos].str; - } - return NULL; -} +/** The default engine (settable). Initial default is FIO_PUBSUB_CLUSTER. */ +SFUNC const fio_pubsub_engine_s *FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; -#undef FIO___HTTP_STATIC_CACHE_FOLD -#undef FIO___HTTP_STATIC_CACHE_MASK -#undef FIO___HTTP_STATIC_CACHE_MASK_INV -#undef FIO___HTTP_STATIC_CACHE_STEP -#undef FIO___HTTP_STATIC_CACHE_STEP_LIMIT -#else -#define fio___http_str_cached_init() (void)0 -#endif /* FIO_HTTP_CACHE_STATIC_HEADERS */ +/** + * The pattern matching callback used for pattern matching. + * + * Returns 1 on a match or 0 if the string does not match the pattern. + * + * By default, the value is set to `fio_glob_match` (see facil.io's C STL). + */ +SFUNC uint8_t (*FIO_PUBSUB_PATTERN_MATCH)(fio_str_info_s, + fio_str_info_s) = fio_glob_match; -FIO_IFUNC char *fio___http_str_cached_inner(size_t group, - uint64_t hash, - fio_str_info_s s) { -#if !FIO_HTTP_CACHE_LIMIT - return fio_bstr_write(NULL, s.buf, s.len); -#endif - fio_str_info_s cached; - hash ^= (uint64_t)(uintptr_t)fio_http_new; -#if FIO_HTTP_CACHE_USES_MUTEX - FIO___LOCK_LOCK(FIO___HTTP_STRING_CACHE[group].lock); -#endif - cached = - fio___http_str_cache_set_if_missing(&FIO___HTTP_STRING_CACHE[group].cache, - hash, - s); -#if FIO_HTTP_CACHE_USES_MUTEX - FIO___LOCK_UNLOCK(FIO___HTTP_STRING_CACHE[group].lock); -#endif - return fio_bstr_copy(cached.buf); -} -FIO_IFUNC char *fio___http_str_cached(size_t group, fio_str_info_s s) { -#if !FIO_HTTP_CACHE_LIMIT - return fio_bstr_write(NULL, s.buf, s.len); -#endif - if (!s.len) - return NULL; - if (s.len > FIO_HTTP_CACHE_STR_MAX_LEN) - goto avoid_caching; - return fio___http_str_cached_inner(group, fio_risky_hash(s.buf, s.len, 0), s); -avoid_caching: - return fio_bstr_write(NULL, s.buf, s.len); -} +/* a mock callback for subscriptions */ +FIO_SFUNC void fio___subscription_mock_cb(fio_msg_s *msg) { (void)msg; } -FIO_IFUNC char *fio___http_str_cached_with_static(fio_str_info_s s) { -#if FIO_HTTP_CACHE_STATIC_HEADERS - char *tmp; - if (!s.len) - return NULL; - if (s.len > FIO_HTTP_CACHE_STR_MAX_LEN) - goto skip_cache_test; - tmp = fio___http_str_cached_static(s.buf, s.len); - if (tmp) - return fio_bstr_copy(tmp); -skip_cache_test: -#endif /* FIO_HTTP_CACHE_STATIC_HEADERS */ - return fio_bstr_write(NULL, s.buf, s.len); +/* A callback for IO subscriptions. */ +FIO_SFUNC void fio___subscription_call_protocol(fio_msg_s *msg) { + if (!msg->io) + return; + fio_io_protocol_s *p = fio_io_protocol(msg->io); + FIO_ASSERT_DEBUG(p, "every IO object should have a protocol, always"); + p->on_pubsub(msg); } +#ifndef FIO___PUBSUB_CLUSTER_BACKLOG +#define FIO___PUBSUB_CLUSTER_BACKLOG (1UL << 12) +#endif + /* ***************************************************************************** -Headers Maps +PostOffice Distribution types - Channel and Subscription Core Types ***************************************************************************** */ -#define FIO_ARRAY_NAME fio___http_sary -#define FIO_ARRAY_TYPE char * -#define FIO_ARRAY_TYPE_DESTROY(obj) fio_bstr_free(obj) +/** The Distribution Channel: manages subscriptions to named channels. */ +typedef struct fio_channel_s { + FIO_LIST_HEAD subscriptions; + FIO_LIST_HEAD history; + uint32_t name_len; + int16_t filter; + uint8_t is_pattern; + char name[]; +} fio_channel_s; -#define FIO_MAP_NAME fio___http_hmap -#define FIO_MAP_KEY fio_str_info_s -#define FIO_MAP_KEY_INTERNAL char * -#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) -#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) -#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) -#define FIO_MAP_KEY_COPY(dest, src) \ - (dest) = fio___http_str_cached_with_static((src)) -#define FIO_MAP_KEY_DISCARD(key) -#define FIO_MAP_VALUE fio___http_sary_s -#define FIO_MAP_VALUE_COPY(a, b) \ - do { \ - (a) = (fio___http_sary_s)FIO_ARRAY_INIT; \ - (void)(b); \ - } while (0) /*no-op*/ -#define FIO_MAP_VALUE_DESTROY(o) fio___http_sary_destroy(&(o)) -#define FIO_MAP_HASH_FN(k) \ - fio_risky_hash((k).buf, (k).len, (uint64_t)(uintptr_t)fio___http_sary_destroy) +/** The Channel Map: maps named channels. */ +FIO_SFUNC void fio___channel_on_create(fio_channel_s *ch); +FIO_SFUNC void fio___channel_on_destroy(fio_channel_s *ch); + +/** + * Reference counting: `fio_channel_dup(ch)` / `fio_channel_free(ch)` + */ +#define FIO_REF_NAME fio_channel +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_DESTROY(ch) fio___channel_on_destroy(&(ch)) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO___RECURSIVE_INCLUDE 1 #include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -#if FIO_HTTP_ENFORCE_LOWERCASE_HEADERS -#define FIO___HTTP_ENFORCE_LOWERCASE(var_name, inpute_var) \ - FIO_STR_INFO_TMP_VAR(var_name, 4096); \ - fio___http_hmap_key_to_lower(&var_name, &inpute_var); +/** The Subscription: contains subscriber data. */ +typedef struct fio_subscription_s { + FIO_LIST_NODE node; + FIO_LIST_NODE history; + FIO_LIST_NODE history_active; + uint64_t replay_since; + fio_io_s *io; + fio_channel_s *channel; + void (*on_message)(fio_msg_s *msg); + void (*on_unsubscribe)(void *udata); + void *udata; +} fio_subscription_s; -/** Converts a Header key to lower-case */ -FIO_IFUNC void fio___http_hmap_key_to_lower(fio_str_info_s *t, - fio_str_info_s *k) { - if (k->len >= t->capa) - goto too_big; - for (size_t i = 0; i < k->len; ++i) { - uint8_t c = (uint8_t)k->buf[i]; - c |= (uint8_t)(c >= 'A' || c <= 'Z') << 5; - t->buf[i] = c; - } - t->len = k->len; - return; -too_big: - *t = *k; -} +/** + * Reference counting: `fio_subscription_dup(sb)` / `fio_subscription_free(sb)` + */ +FIO_SFUNC void fio___pubsub_subscription_on_destroy(fio_subscription_s *sub); +#define FIO_REF_NAME fio_subscription +#define FIO_REF_DESTROY(obj) fio___pubsub_subscription_on_destroy(&(obj)) +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -#else -#define FIO___HTTP_ENFORCE_LOWERCASE(var_name, inpute_var) \ - fio_str_info_s var_name = inpute_var; -#endif +/** The Message Container */ +typedef struct { + fio_msg_s data; + void *metadata[FIO___PUBSUB_METADATA_STORE_LIMIT]; + uint8_t metadata_is_initialized; /* to compact this we need to change all? */ + char buf[]; +} fio___pubsub_message_s; -/** set `add` to positive to add multiple values or negative to overwrite. */ -FIO_IFUNC fio_str_info_s fio___http_hmap_set2(fio___http_hmap_s *map, - fio_str_info_s key_input, - fio_str_info_s val, - int add) { - fio_str_info_s r = {0}; - if (!key_input.buf || !key_input.len || !map) - return r; - /* make sure key is all lower-case? */ - FIO___HTTP_ENFORCE_LOWERCASE(key, key_input); - fio___http_sary_s *o; - if (!val.buf || !val.len) - goto remove_key; - o = fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); - if (!o) { - fio___http_sary_s va = {0}; - o = fio___http_hmap_node2val_ptr( - fio___http_hmap_set_ptr(map, key, va, NULL, 1)); - add = 1; - } - if (FIO_UNLIKELY(!o)) { - FIO_LOG_ERROR("Couldn't add value to header: %.*s:%.*s", - (int)key.len, - key.buf, - (int)val.len, - val.buf); - return r; - } - if (add) { - if (add < 0) { - fio___http_sary_destroy(o); - } - r = fio_bstr_info(fio___http_str_cached(FIO___HTTP_STR_CACHE_VALUE, val)); - fio___http_sary_push(o, r.buf); - return r; - } - r = fio_bstr_info(fio___http_sary_get(o, -1)); - return r; +/* returns the internal message object. */ +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_msg2internal(fio_msg_s *msg); -remove_key: - if (add < 1) - fio___http_hmap_remove(map, key, NULL); - return r; +/** Callback called when a message is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_on_destroy(fio___pubsub_message_s *m); + +/* Message reference counting */ +#define FIO_REF_NAME fio___pubsub_message +#define FIO_REF_DESTROY(obj) fio___pubsub_message_on_destroy(&(obj)) +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO___RECURSIVE_INCLUDE 1 +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + +typedef struct { + size_t len; + uint64_t uuid[2]; + fio___pubsub_message_s *msg; + char buf[FIO___PUBSUB_MESSAGE_OVERHEAD_NET]; +} fio___pubsub_message_parser_s; + +FIO_LEAK_COUNTER_DEF(fio___pubsub_message_parser_s) + +FIO_IFUNC fio___pubsub_message_parser_s *fio___pubsub_message_parser( + fio_io_s *io) { + return (fio___pubsub_message_parser_s *)fio_io_buffer(io); } -FIO_IFUNC fio_str_info_s fio___http_hmap_get2(fio___http_hmap_s *map, - fio_str_info_s key_input, - int32_t index) { - fio_str_info_s r = {0}; - FIO___HTTP_ENFORCE_LOWERCASE(key, key_input); - fio___http_sary_s *a = - fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); - if (!a) - return r; - const uint32_t count = fio___http_sary_count(a); - if (!count) - return r; - if (index < 0) { - index += count; - if (index < 0) - index = 0; - } - if ((uint32_t)index >= count) - return r; - r = fio_bstr_info(fio___http_sary_get(a, index)); - return r; +FIO_SFUNC void fio___pubsub_message_parser_init( + fio___pubsub_message_parser_s *p) { + FIO_LEAK_COUNTER_ON_ALLOC(fio___pubsub_message_parser_s); + p->len = 0; + p->uuid[0] = p->uuid[1] = 0; + p->msg = NULL; } -FIO_IFUNC size_t fio___http_hmap_count2(fio___http_hmap_s *map, - fio_str_info_s key) { - size_t r = 0; - fio___http_sary_s *a = - fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); - if (!a) - return r; - r = fio___http_sary_count(a); - return r; +FIO_SFUNC void fio___pubsub_message_parser_destroy( + fio___pubsub_message_parser_s *p) { + if (!p) + return; + fio___pubsub_message_free(p->msg); + FIO_LEAK_COUNTER_ON_FREE(fio___pubsub_message_parser_s); } /* ***************************************************************************** -Header iteration Task +PostOffice Distribution types - The Distribution Channel Map ***************************************************************************** */ -typedef struct { - fio_http_s *h; - int (*callback)(fio_http_s *, fio_str_info_s, fio_str_info_s, void *); - void *udata; -} fio___http_hmap_each_info_s; +#define FIO___PUBSUB_CHANNEL_ENCODE_CAPA(filter_, is_pattern_) \ + (((size_t)(is_pattern_) << 16) | (size_t)(uint16_t)(filter_)) -FIO_SFUNC int fio___http_h_each_task_wrapper(fio___http_hmap_each_s *e) { - fio___http_hmap_each_info_s *data = (fio___http_hmap_each_info_s *)(e->udata); - FIO_ARRAY_EACH(fio___http_sary, &e->value, pos) { - if (data->callback(data->h, e->key, fio_bstr_info(*pos), data->udata) == -1) - return -1; - } - return 0; +#define FIO___PUBSUB_CHANNEL2STR(ch) \ + FIO_STR_INFO3(ch->name, \ + ch->name_len, \ + FIO___PUBSUB_CHANNEL_ENCODE_CAPA(ch->filter, ch->is_pattern)) + +FIO_IFUNC int fio___channel_cmp(fio_channel_s *ch, fio_str_info_s s) { + fio_str_info_s c = FIO___PUBSUB_CHANNEL2STR(ch); + return FIO_STR_INFO_IS_EQ(c, s); } -/* ***************************************************************************** -Cookie Maps -***************************************************************************** */ +FIO_IFUNC fio_channel_s *fio___channel_new_for_map(fio_str_info_s s) { + fio_channel_s *ch = fio_channel_new(s.len + 1); + FIO_ASSERT_ALLOC(ch); + *ch = (fio_channel_s){ + .subscriptions = FIO_LIST_INIT(ch->subscriptions), + .history = FIO_LIST_INIT(ch->history), + .name_len = (uint32_t)s.len, + .filter = (int16_t)(s.capa & 0xFFFFUL), + .is_pattern = (uint8_t)(s.capa >> 16), + }; + FIO_MEMCPY(ch->name, s.buf, s.len); + ch->name[s.len] = 0; + fio___channel_on_create(ch); + return ch; +} -#define FIO_MAP_NAME fio___http_cmap /* cached names */ -#define FIO_MAP_KEY fio_str_info_s -#define FIO_MAP_KEY_INTERNAL char * -#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) -#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) -#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) -#define FIO_MAP_KEY_COPY(dest, src) \ - (dest) = fio___http_str_cached(FIO___HTTP_STR_CACHE_COOKIE, (src)) +#define FIO_MAP_NAME fio___channel_map +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL fio_channel_s * +#define FIO_MAP_KEY_FROM_INTERNAL(k_) FIO___PUBSUB_CHANNEL2STR(k_) +#define FIO_MAP_KEY_COPY(dest, src) ((dest) = fio___channel_new_for_map((src))) +#define FIO_MAP_KEY_CMP(a, b) fio___channel_cmp((a), (b)) +#define FIO_MAP_HASH_FN(str) fio_risky_hash(str.buf, str.len, str.capa) +#define FIO_MAP_KEY_DESTROY(key) fio_channel_free((key)) #define FIO_MAP_KEY_DISCARD(key) - -#define FIO_MAP_VALUE_BSTR /* not cached */ -#define FIO_MAP_HASH_FN(k) \ - fio_risky_hash((k).buf, (k).len, (uint64_t)(uintptr_t)fio___http_cmap_destroy) +#define FIO___RECURSIVE_INCLUDE 1 #include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE /* ***************************************************************************** -Controller Validation +Pub/Sub Subscription destruction ***************************************************************************** */ -FIO_SFUNC int fio___mock_controller_get_fd_cb(fio_http_s *h) { - return -1; - (void)h; -} -FIO_SFUNC void fio___mock_controller_cb(fio_http_s *h) { (void)h; } -FIO_SFUNC void fio___mock_c_write_body(fio_http_s *h, - fio_http_write_args_s args) { - if (args.buf) { - if (args.dealloc) - args.dealloc((void *)args.buf); - } else if ((unsigned)(args.fd + 1) > 1U && !args.copy && - args.fd != fio_http_body_fd(h)) { - close(args.fd); - } - (void)h; -} - -static fio_http_controller_s FIO___MOCK_CONTROLLER = { - .on_destroyed = fio___mock_controller_cb, - .send_headers = fio___mock_controller_cb, - .write_body = fio___mock_c_write_body, - .on_finish = fio___mock_controller_cb, - .close_io = fio___mock_controller_cb, - .get_fd = fio___mock_controller_get_fd_cb, -}; - -SFUNC fio_http_controller_s *fio___http_controller_validate( - fio_http_controller_s *c) { - if (!c) - c = &FIO___MOCK_CONTROLLER; - if (c->private_flags) - return c; - if (!c->on_destroyed) - c->on_destroyed = fio___mock_controller_cb; - if (!c->send_headers) - c->send_headers = fio___mock_controller_cb; - if (!c->write_body) - c->write_body = fio___mock_c_write_body; - if (!c->on_finish) - c->on_finish = fio___mock_controller_cb; - if (!c->close_io) - c->close_io = fio___mock_controller_cb; - if (!c->get_fd) - c->get_fd = fio___mock_controller_get_fd_cb; - return c; +/* calls the on_unsubscribe callback. */ +FIO_SFUNC void fio___pubsub_subscription_on_destroy__task(void *fnp, + void *udata) { + union { + void *p; + void (*fn)(void *udata); + } u = {.p = fnp}; + u.fn(udata); } +FIO_SFUNC void fio___pubsub_subscription_on_destroy(fio_subscription_s *s) { + if (s->on_unsubscribe) { + union { + void *p; + void (*fn)(void *udata); + } u = {.fn = s->on_unsubscribe}; + fio_queue_push(fio_io_queue(), + fio___pubsub_subscription_on_destroy__task, + u.p, + s->udata); + } +} /* ***************************************************************************** -HTTP Handle Type +Pub/Sub Subscription map (for mapping Master only subscriptions) ***************************************************************************** */ -#define FIO_HTTP_STATE_STREAMING 1 -#define FIO_HTTP_STATE_FINISHED 2 -#define FIO_HTTP_STATE_UPGRADED 4 -#define FIO_HTTP_STATE_WEBSOCKET 8 -#define FIO_HTTP_STATE_SSE 16 -#define FIO_HTTP_STATE_COOKIES_PARSED 32 -#define FIO_HTTP_STATE_FREEING 64 +/** Performs Housekeeping and defers the on_unsubscribe callback. */ +FIO_IFUNC void fio___pubsub_subscription_unsubscribe(fio_subscription_s *s); -FIO_SFUNC int fio____http_write_start(fio_http_s *, fio_http_write_args_s *); -FIO_SFUNC int fio____http_write_cont(fio_http_s *, fio_http_write_args_s *); +/* define a helper map to manage master only subscription. */ +#define FIO_MAP_KEY_KSTR +#define FIO_MAP_NAME fio___postoffice_msmap +#define FIO_MAP_VALUE fio_subscription_s * +#define FIO_MAP_VALUE_DESTROY(s) fio___pubsub_subscription_unsubscribe(s) +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -struct fio_http_s { - void *udata; - void *udata2; - void *cdata; - fio_http_controller_s *controller; - int (*writer)(fio_http_s *, fio_http_write_args_s *); - int64_t received_at; - size_t sent; - uint32_t state; - uint32_t status; - fio_keystr_s method; - fio_keystr_s path; - fio_keystr_s query; - fio_keystr_s version; - fio___http_hmap_s headers[2]; /* request, response */ - fio___http_cmap_s cookies[2]; /* read, write */ - struct { - char *buf; - size_t len; - size_t pos; - int fd; - } body; -}; +/* ***************************************************************************** +Pub/Sub Remote Connection Uniqueness +***************************************************************************** */ -#define HTTP_HDR_REQUEST(h) (h->headers + 0) -#define HTTP_HDR_RESPONSE(h) (h->headers + 1) +/* Managing Remote Connection Uniqueness */ +#define FIO_MAP_NAME fio___pubsub_broadcast_connected +#define FIO_MAP_KEY uint64_t +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -#define FIO_REF_NAME fio_http -#define FIO_REF_INIT(h) \ - h = (fio_http_s) { \ - .controller = &FIO___MOCK_CONTROLLER, .writer = fio____http_write_start, \ - .received_at = fio_http_get_timestump(), .body.fd = -1 \ - } -#define FIO_REF_DESTROY(h) fio_http_destroy(&(h)) -SFUNC fio_http_s *fio_http_destroy(fio_http_s *h) { - if (!h) - return h; - h->state |= FIO_HTTP_STATE_FREEING; - h->controller->on_destroyed(h); +/* ***************************************************************************** +Pub/Sub Engine Map +***************************************************************************** */ - fio_keystr_destroy(&h->method, fio___http_keystr_free); - fio_keystr_destroy(&h->path, fio___http_keystr_free); - fio_keystr_destroy(&h->query, fio___http_keystr_free); - fio_keystr_destroy(&h->version, fio___http_keystr_free); - fio___http_hmap_destroy(h->headers); - fio___http_hmap_destroy(h->headers + 1); - fio___http_cmap_destroy(h->cookies); - fio___http_cmap_destroy(h->cookies + 1); - fio_bstr_free(h->body.buf); - if (h->body.fd != -1) - close(h->body.fd); - FIO_REF_INIT(*h); - return h; -} +/* Managing Remote Connection Uniqueness */ +#define FIO_MAP_NAME fio___pubsub_engines +#define FIO_MAP_KEY fio_pubsub_engine_s * +#define FIO_MAP_HASH_FN(e) fio_risky_ptr(e) +#define FIO_MAP_RECALC_HASH 1 +#define FIO_MAP_KEY_DESTROY(e) \ + do { \ + e->detached(e); \ + e = NULL; \ + } while (0) +#define FIO_MAP_KEY_DISCARD(e) +#define FIO___RECURSIVE_INCLUDE #include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -/** Clears any response data. */ -SFUNC fio_http_s *fio_http_clear_response(fio_http_s *h, bool clear_body) { - fio___http_hmap_destroy(HTTP_HDR_RESPONSE(h)); - h->state = 0; - h->writer = fio____http_write_start; - h->received_at = fio_http_get_timestump(); - h->status = 0; - if (!clear_body) - return h; - fio_bstr_free(h->body.buf); - if (h->body.fd != -1) - close(h->body.fd); - h->body.buf = NULL; - h->body.len = h->body.pos = 0; - h->body.fd = -1; - return h; -} +/* ***************************************************************************** +Message Uniqueness Map for filtering remote connection broadcasts +***************************************************************************** */ -/** Create a new http_s handle. */ -SFUNC fio_http_s *fio_http_new(void) { return fio_http_new2(); } +/* Managing Remote Connection Uniqueness */ +#define FIO_MAP_NAME fio___pubsub_message_map +#define FIO_MAP_KEY fio___pubsub_message_s * +#define FIO_MAP_KEY_COPY(d_, e_) (d_ = fio___pubsub_message_dup(e_)) +#define FIO_MAP_KEY_CMP(a, b) \ + (a->data.id == b->data.id && a->data.published == b->data.published) +#define FIO_MAP_KEY_DESTROY(e) fio___pubsub_message_free(e) +#define FIO_MAP_HASH_FN(m) fio_risky_num(m->data.id, m->data.published) +#define FIO_MAP_RECALC_HASH 1 +#define FIO_MAP_LRU FIO___PUBSUB_CLUSTER_BACKLOG +#define FIO_MAP_KEY_DISCARD(e) +#define FIO___RECURSIVE_INCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE -/** Reduces an http_s handle's reference count or frees it. */ -SFUNC void fio_http_free(fio_http_s *h) { fio_http_free2(h); } +/* ***************************************************************************** +Pub/Sub Post Office State +***************************************************************************** */ +#ifndef FIO___IPC_LEN +#define FIO___IPC_LEN 256 +#endif -/** Increases an http_s handle's reference count. */ -SFUNC fio_http_s *fio_http_dup(fio_http_s *h) { return fio_http_dup2(h); } +FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_close(void *buffer, void *udata); +FIO_SFUNC void fio___pubsub_protocol_on_timeout(fio_io_s *io); -/** Collects an updated timestamp for logging purposes. */ -SFUNC void fio_http_start_time_set(fio_http_s *h) { - h->received_at = fio_http_get_timestump(); +static struct FIO___PUBSUB_POSTOFFICE { + fio_u128 uuid; + fio_u512 secret; + fio___channel_map_s channels; + fio___channel_map_s patterns; + struct { + uint8_t publish; + uint8_t local; + uint8_t remote; + } filter; + uint8_t secret_is_random; + FIO___LOCK_TYPE lock; + fio___pubsub_engines_s engines; + FIO_LIST_NODE history_active; + FIO_LIST_NODE history_waiting; + fio___postoffice_msmap_s master_subscriptions; + fio___postoffice_msmap_s global_subscriptions; + fio___pubsub_broadcast_connected_s remote_uuids; + fio___pubsub_message_map_s remote_messages; + fio___pubsub_message_map_s history_messages; + struct { + fio_io_protocol_s ipc; + fio_io_protocol_s remote; + } protocol; + fio_io_s *broadcaster; + struct { + fio_msg_metadata_fn build; + void (*cleanup)(void *); + size_t ref; + } metadata[FIO___PUBSUB_METADATA_STORE_LIMIT]; + char ipc_url[FIO___IPC_LEN]; +} FIO___PUBSUB_POSTOFFICE = { + .filter = + { + .publish = (FIO___PUBSUB_PROCESS | FIO___PUBSUB_ROOT), + .local = (FIO___PUBSUB_SIBLINGS), + .remote = FIO___PUBSUB_REMOTE, + }, + .lock = FIO___LOCK_INIT, + .protocol = + { + .ipc = + { + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_master, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio_io_touch, + .buffer_size = sizeof(fio___pubsub_message_parser_s), + }, + .remote = + { + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_remote, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio___pubsub_protocol_on_timeout, + .buffer_size = sizeof(fio___pubsub_message_parser_s), + }, + }, +}; + +/** Returns the secret key for a message with stated `rndm` value. */ +FIO_IFUNC const void *fio___pubsub_secret_key(uint64_t rndm) { + return (void *)&FIO___PUBSUB_POSTOFFICE.secret.u8[rndm & 15]; } -/** Closes a persistent HTTP connection (i.e., if upgraded). */ -SFUNC void fio_http_close(fio_http_s *h) { h->controller->close_io(h); } +/* ***************************************************************************** +PostOffice Helpers +***************************************************************************** */ -/** Creates a copy of an existing handle, copying only its request data. */ -SFUNC fio_http_s *fio_http_new_copy_request(fio_http_s *o) { - fio_http_s *h = fio_http_new(); - FIO_ASSERT_ALLOC(h); - fio_http_path_set(h, fio_http_path(o)); - fio_http_method_set(h, fio_http_method(o)); - fio_http_query_set(h, fio_http_query(o)); - fio_http_version_set(h, fio_http_version(o)); - /* copy headers */ - fio___http_hmap_reserve(h->headers, fio___http_hmap_count(o->headers)); - FIO_MAP_EACH(fio___http_hmap, o->headers, i) { - fio___http_sary_s *a = fio___http_hmap_node2val_ptr( - fio___http_hmap_set_ptr(h->headers, - i.key, - (fio___http_sary_s){0}, - NULL, - 0)); - FIO_ARRAY_EACH(fio___http_sary, &i.value, v) { - fio___http_sary_push(a, fio_bstr_copy(*v)); +/** Sets the current IPC socket address (shouldn't be changed while running). */ +SFUNC int fio_pubsub_ipc_url_set(char *str, size_t len) { + if (fio_io_is_running() || len >= FIO___IPC_LEN) + return -1; + fio_str_info_s url = + FIO_STR_INFO3(FIO___PUBSUB_POSTOFFICE.ipc_url, 0, FIO___IPC_LEN); + fio_string_write2(&url, NULL, FIO_STRING_WRITE_STR2(str, len)); + return 0; +} +/** Returns the current IPC socket address (shouldn't be changed). */ +SFUNC const char *fio_pubsub_ipc_url(void) { + return FIO___PUBSUB_POSTOFFICE.ipc_url; +} + +/** Sets a (possibly shared) secret for securing pub/sub communication. */ +SFUNC void fio_pubsub_secret_set(char *str, size_t len) { + FIO___PUBSUB_POSTOFFICE.secret_is_random = 0; + uint64_t fallback_secret = 0; + if (!str || !len) { + if ((str = getenv("SECRET"))) { + const char *secret_length = getenv("SECRET_LENGTH"); + len = secret_length ? fio_atol((char **)&secret_length) : 0; + if (!len) + len = strlen(str); + } else { + fallback_secret = fio_rand64(); + str = (char *)&fallback_secret; + len = sizeof(fallback_secret); + FIO___PUBSUB_POSTOFFICE.secret_is_random = 1; } } - /* copy cookies */ - FIO_MAP_EACH(fio___http_cmap, o->cookies, i) { - fio___http_cmap_set(h->cookies, i.key, i.value, NULL); - } - return h; + FIO___PUBSUB_POSTOFFICE.secret = fio_sha512(str, len); } -#undef FIO___RECURSIVE_INCLUDE /* ***************************************************************************** -Simple Property Set / Get +Postoffice History Control ***************************************************************************** */ -#define HTTP___MAKE_GET_SET(property) \ - fio_str_info_s fio_http_##property(fio_http_s *h) { \ - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); \ - return fio_keystr_info(&h->property); \ - } \ - \ - fio_str_info_s fio_http_##property##_set(fio_http_s *h, \ - fio_str_info_s value) { \ - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); \ - fio_keystr_destroy(&h->property, fio___http_keystr_free); \ - h->property = fio_keystr_init(value, fio___http_keystr_alloc); \ - return fio_keystr_info(&h->property); \ +FIO_SFUNC void fio___pubub_on_history_start(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + if (!FIO_LIST_IS_EMPTY(&FIO___PUBSUB_POSTOFFICE.history_active)) + return; + FIO_LIST_EACH(fio_subscription_s, + history_active, + &FIO___PUBSUB_POSTOFFICE.history_active, + s) { + FIO_LIST_REMOVE(&s->history); + FIO_LIST_REMOVE(&s->history_active); + FIO_LIST_PUSH(&s->channel->history, &s->history); + FIO_LIST_PUSH(&FIO___PUBSUB_POSTOFFICE.history_active, &s->history_active); } - -HTTP___MAKE_GET_SET(method) -HTTP___MAKE_GET_SET(path) -HTTP___MAKE_GET_SET(query) -HTTP___MAKE_GET_SET(version) - -#undef HTTP___MAKE_GET_SET - -/** Gets the status associated with the HTTP handle (response). */ -SFUNC size_t fio_http_status(fio_http_s *h) { return (size_t)(h->status); } - -/** Sets the status associated with the HTTP handle (response). */ -SFUNC size_t fio_http_status_set(fio_http_s *h, size_t status) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - if (status > 1023) - status = 500; - if (!status) - status = 200; - return (h->status = (uint32_t)status); } -/* ***************************************************************************** -Handler State -***************************************************************************** */ -SFUNC int fio_http_is_clean(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return !h->state; +FIO_SFUNC void fio___pubub_on_history_end(void *ignr_1, void *ignr_2) { + (void)ignr_1, (void)ignr_2; + FIO_LIST_EACH(fio_subscription_s, + history_active, + &FIO___PUBSUB_POSTOFFICE.history_active, + s) { + FIO_LIST_REMOVE(&s->history); + FIO_LIST_REMOVE(&s->history_active); + } } -/** Returns true if the HTTP handle's response was sent. */ -SFUNC int fio_http_is_finished(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return (!!(h->state & FIO_HTTP_STATE_FINISHED)); -} +/* ***************************************************************************** +Postoffice Metadata Control +***************************************************************************** */ -/** Returns true if handle is in the process of freeing itself. */ -SFUNC int fio_http_is_freeing(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return (!!(h->state & FIO_HTTP_STATE_FREEING)); +/* Returns zero (0) on success or -1 on failure. */ +SFUNC int fio_message_metadata_add(fio_msg_metadata_fn metadata_func, + void (*cleanup)(void *)) { + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* test existing */ + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1) && + metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) + return 0; + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* insert if available */ + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + continue; + } + FIO___PUBSUB_POSTOFFICE.metadata[i].build = metadata_func; + FIO___PUBSUB_POSTOFFICE.metadata[i].cleanup = cleanup; + return 0; + } + return -1; } -/** Returns true if the HTTP handle's response is streaming. */ -SFUNC int fio_http_is_streaming(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return (!!(h->state & FIO_HTTP_STATE_STREAMING)); +/** + * Removed the metadata callback. + * + * Removal might be delayed if live metatdata + * exists. + */ +SFUNC void fio_message_metadata_remove(fio_msg_metadata_fn metadata_func) { + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* test existing */ + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1) && + metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) { + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } } -/** Returns true if the HTTP connection was (or should have been) upgraded. */ -SFUNC int fio_http_is_upgraded(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return (!!(h->state & FIO_HTTP_STATE_UPGRADED)); +/** Finds the message's metadata, returning the data or NULL. */ +SFUNC void *fio_message_metadata(fio_msg_s *msg, + fio_msg_metadata_fn metadata_func) { + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; + ++i) { /* test existing */ + if (FIO___PUBSUB_POSTOFFICE.metadata[i].ref && + metadata_func == FIO___PUBSUB_POSTOFFICE.metadata[i].build) { + return fio___pubsub_msg2internal(msg)->metadata[i]; + } + } + return NULL; } -/** Returns true if the HTTP handle establishes a WebSocket Upgrade. */ -SFUNC int fio_http_is_websocket(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return (!!(h->state & FIO_HTTP_STATE_WEBSOCKET)); -} +/* ***************************************************************************** +Listening to Local Connections (IPC) +***************************************************************************** */ -/** Returns true if the HTTP handle establishes an EventSource connection. */ -SFUNC int fio_http_is_sse(fio_http_s *h) { - FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); - return (!!(h->state & FIO_HTTP_STATE_SSE)); +#if defined(DEBUG) +#define FIO___PUBSUB_HIDE_FROM_LOG 0 +#else +#define FIO___PUBSUB_HIDE_FROM_LOG 1 +#endif +/** Starts listening to IPC connections on a local socket. */ +FIO_IFUNC void fio___pubsub_ipc_listen(void *ignr_) { + (void)ignr_; + if (fio_io_is_worker()) { + FIO_LOG_DEBUG2("(%d) pub/sub IPC socket skipped - no workers are spawned.", + fio_io_pid()); + return; + } + FIO_ASSERT(fio_io_listen(.url = FIO___PUBSUB_POSTOFFICE.ipc_url, + .protocol = &FIO___PUBSUB_POSTOFFICE.protocol.ipc, + .on_root = 1, + .hide_from_log = FIO___PUBSUB_HIDE_FROM_LOG), + "(%d) pub/sub couldn't open a socket for IPC\n\t\t%s", + fio_io_pid(), + FIO___PUBSUB_POSTOFFICE.ipc_url); } +#undef FIO___PUBSUB_HIDE_FROM_LOG /* ***************************************************************************** -Header Data Management +Postoffice Constructor / Destructor ***************************************************************************** */ -#define FIO___HTTP_HEADER_SET_FN(category, name_, headers, add_val) \ - /** Sets the header information associated with the HTTP handle. */ \ - fio_str_info_s fio_http_##category##_header_##name_(fio_http_s *h, \ - fio_str_info_s name, \ - fio_str_info_s value) { \ - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); \ - return fio___http_hmap_set2(headers(h), name, value, add_val); \ - } -FIO___HTTP_HEADER_SET_FN(request, set, HTTP_HDR_REQUEST, -1) -FIO___HTTP_HEADER_SET_FN(request, set_if_missing, HTTP_HDR_REQUEST, 0) -FIO___HTTP_HEADER_SET_FN(request, add, HTTP_HDR_REQUEST, 1) -FIO___HTTP_HEADER_SET_FN(response, set, HTTP_HDR_RESPONSE, -1) -FIO___HTTP_HEADER_SET_FN(response, set_if_missing, HTTP_HDR_RESPONSE, 0) -FIO___HTTP_HEADER_SET_FN(response, add, HTTP_HDR_RESPONSE, 1) -#undef FIO___HTTP_HEADER_SET_FN +/* listens for IPC connections. */ +FIO_SFUNC void fio___pubsub_ipc_listen(void *); +/* protocol functions. */ +FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_io_s *io); +FIO_SFUNC void fio___pubsub_protocol_on_close(void *buffer, void *udata); -fio_str_info_s fio_http_request_header(fio_http_s *h, - fio_str_info_s name, - size_t index) { - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); - return fio___http_hmap_get2(HTTP_HDR_REQUEST(h), name, (int32_t)index); -} -fio_str_info_s fio_http_response_header(fio_http_s *h, - fio_str_info_s name, - size_t index) { - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); - return fio___http_hmap_get2(HTTP_HDR_RESPONSE(h), name, (int32_t)index); +FIO_SFUNC void fio___pubsub_at_exit(void *ignr_) { + (void)ignr_; + fio_queue_perform_all(fio_io_queue()); + fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.master_subscriptions); + fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.global_subscriptions); + fio___pubsub_broadcast_connected_destroy( + &FIO___PUBSUB_POSTOFFICE.remote_uuids); + fio___pubsub_message_map_destroy(&FIO___PUBSUB_POSTOFFICE.remote_messages); + fio___pubsub_message_map_destroy(&FIO___PUBSUB_POSTOFFICE.history_messages); + fio___pubsub_engines_destroy(&FIO___PUBSUB_POSTOFFICE.engines); + FIO___LOCK_DESTROY(FIO___PUBSUB_POSTOFFICE.lock); + fio_queue_perform_all(fio_io_queue()); } -/** Returns the number of headers named `name` that were received. */ -SFUNC size_t fio_http_request_header_count(fio_http_s *h, fio_str_info_s name) { - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); - if (!name.buf) - return fio___http_hmap_count(HTTP_HDR_REQUEST(h)); - return fio___http_hmap_count2(HTTP_HDR_REQUEST(h), name); -} -/** Returns the number of headers named `name` that were received. */ -SFUNC size_t fio_http_response_header_count(fio_http_s *h, - fio_str_info_s name) { - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); - if (!name.buf) - return fio___http_hmap_count(HTTP_HDR_RESPONSE(h)); - return fio___http_hmap_count2(HTTP_HDR_RESPONSE(h), name); -} +/** Callback called by the letter protocol entering a child processes. */ +FIO_SFUNC void fio___pubsub_on_enter_child(void *ignr_) { + (void)ignr_; + FIO___PUBSUB_POSTOFFICE.protocol.ipc.on_data = + fio___pubsub_protocol_on_data_worker; -/** Iterates through all headers. A non-zero return will stop iteration. */ -size_t fio_http_request_header_each(fio_http_s *h, - int (*callback)(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value, - void *udata), - void *udata) { - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); - if (!callback) - return fio___http_hmap_count(HTTP_HDR_REQUEST(h)); - fio___http_hmap_each_info_s d = {.h = h, - .callback = callback, - .udata = udata}; - return fio___http_hmap_each(HTTP_HDR_REQUEST(h), - fio___http_h_each_task_wrapper, - &d, - 0); + FIO___PUBSUB_POSTOFFICE.filter.publish = FIO___PUBSUB_PROCESS; + FIO___PUBSUB_POSTOFFICE.filter.local = + (FIO___PUBSUB_SIBLINGS | FIO___PUBSUB_ROOT); + FIO___PUBSUB_POSTOFFICE.filter.remote = 0; + fio___postoffice_msmap_destroy(&FIO___PUBSUB_POSTOFFICE.master_subscriptions); + fio___pubsub_engines_destroy(&FIO___PUBSUB_POSTOFFICE.engines); + if (!fio_io_attach_fd(fio_sock_open2(FIO___PUBSUB_POSTOFFICE.ipc_url, + FIO_SOCK_CLIENT | FIO_SOCK_NONBLOCK), + &FIO___PUBSUB_POSTOFFICE.protocol.ipc, + NULL, + NULL)) { + FIO_LOG_FATAL("(%d) couldn't connect to pub/sub socket @ %s", + fio_io_pid(), + FIO___PUBSUB_POSTOFFICE.ipc_url); + fio_thread_kill(fio_io_root_pid(), SIGINT); + FIO_ASSERT(0, "fatal error encountered"); + } } -/** Iterates through all headers. A non-zero return will stop iteration. */ -size_t fio_http_response_header_each( - fio_http_s *h, - int (*callback)(fio_http_s *, fio_str_info_s, fio_str_info_s, void *), - void *udata) { - FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); - if (!callback) - return fio___http_hmap_count(HTTP_HDR_RESPONSE(h)); - fio___http_hmap_each_info_s d = {.h = h, - .callback = callback, - .udata = udata}; - return fio___http_hmap_each(HTTP_HDR_RESPONSE(h), - fio___http_h_each_task_wrapper, - &d, - 0); +FIO_CONSTRUCTOR(fio_postoffice_init) { + FIO___PUBSUB_POSTOFFICE.engines = (fio___pubsub_engines_s)FIO_MAP_INIT; + fio_pubsub_secret_set(NULL, 0); /* allocate a random secret */ + for (size_t i = 0; i < sizeof(FIO___PUBSUB_POSTOFFICE.uuid) / 8; ++i) + FIO___PUBSUB_POSTOFFICE.uuid.u64[i] = fio_rand64(); + fio_str_info_s url = + FIO_STR_INFO3(FIO___PUBSUB_POSTOFFICE.ipc_url, 0, FIO___IPC_LEN); + + fio_string_write2(&url, + NULL, + FIO_STRING_WRITE_STR1((char *)"priv://facil_io_tmp_"), + FIO_STRING_WRITE_HEX(fio_rand64()), + FIO_STRING_WRITE_STR1((char *)".sock")); + fio_state_callback_add(FIO_CALL_PRE_START, fio___pubsub_ipc_listen, NULL); + fio_state_callback_add(FIO_CALL_IN_CHILD, fio___pubsub_on_enter_child, NULL); + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___pubsub_at_exit, NULL); + /* TODO!!! */ + FIO___PUBSUB_POSTOFFICE.protocol.ipc = (fio_io_protocol_s){ + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_master, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio_io_touch, + }; + FIO___PUBSUB_POSTOFFICE.protocol.remote = (fio_io_protocol_s){ + .on_attach = fio___pubsub_protocol_on_attach, + .on_data = fio___pubsub_protocol_on_data_remote, + .on_close = fio___pubsub_protocol_on_close, + .on_timeout = fio_io_touch, + }; } /* ***************************************************************************** -Cookies +Subscription Setup ***************************************************************************** */ -/** (Helper) HTTP Cookie Parser */ -FIO_IFUNC void fio___http_cookie_parse_cookie(fio_http_s *h, fio_str_info_s s) { - /* loop and read Cookie: name=value; name2=value2; name3=value3 */ - while (s.len) { - fio_str_info_s k = {0}, v = {0}; - /* remove white-space */ - while ((s.buf[0] == ' ' || s.buf[0] == '\t') && s.len) { - ++s.buf; - --s.len; - } - if (!s.len) - return; - char *div = (char *)FIO_MEMCHR(s.buf, '=', s.len); - char *end = (char *)FIO_MEMCHR(s.buf, ';', s.len); - if (!end) - end = s.buf + s.len; - v.buf = s.buf; - if (div) { - /* cookie name may be an empty string */ - k.buf = s.buf; - k.len = div - s.buf; - v.buf = div + 1; - } - v.len = end - v.buf; - s.len = (s.buf + s.len) - end; - s.buf = end; - /* skip the ';' if exists (if len is not zero, !!s.len == 1). */ - s.buf += !!s.len; - s.len -= !!s.len; - fio___http_cmap_set_if_missing(h->cookies, k, v); +/** Completes the subscription request. */ +FIO_IFUNC void fio___pubsub_subscribe_task(void *sub_, void *ignr_) { + fio_subscription_s *sub = (fio_subscription_s *)sub_; + union { + FIO_LIST_HEAD *ls; + fio_str_info_s *str; + } uptr = {.ls = &sub->node}; + const fio_str_info_s ch_name = *uptr.str; + fio_channel_s **ch_ptr = + fio___channel_map_node2key_ptr(fio___channel_map_set_ptr( + &FIO___PUBSUB_POSTOFFICE.channels + (ch_name.capa >> 16), + ch_name)); + fio_bstr_free(ch_name.buf); + sub->node = FIO_LIST_INIT(sub->node); + sub->history = FIO_LIST_INIT(sub->history); + sub->history_active = FIO_LIST_INIT(sub->history_active); + if (FIO_UNLIKELY(!ch_ptr)) + goto no_channel; + sub->channel = ch_ptr[0]; + FIO_LIST_PUSH(&(ch_ptr[0]->subscriptions), &sub->node); + if (sub->replay_since) { + FIO_LIST_PUSH(&FIO___PUBSUB_POSTOFFICE.history_waiting, &sub->history); + /* TODO: publish history request event to the cluster. */ } + return; +no_channel: + fio___pubsub_subscription_unsubscribe(sub); + (void)ignr_; } -/** (Helper) HTTP Cookie Parser */ -FIO_IFUNC void fio___http_cookie_parse_set_cookie(fio_http_s *h, - fio_str_info_s s) { - /* TODO! */ - fio_str_info_s k = {0}, v = {0}; - /* remove white-space */ - while ((s.buf[0] == ' ' || s.buf[0] == '\t') && s.len) { - ++s.buf; - --s.len; +/** Unsubscribes a node and destroys the channel if no more subscribers. */ +FIO_IFUNC void fio___pubsub_unsubscribe_task(void *sub_, void *ignr_) { + fio_subscription_s *sub = (fio_subscription_s *)sub_; + fio_channel_s *ch = sub->channel; + fio___channel_map_s *map; + FIO_LIST_REMOVE(&sub->node); + FIO_LIST_REMOVE(&sub->history); + FIO_LIST_REMOVE(&sub->history_active); + if (FIO_UNLIKELY(!ch)) + goto no_channel; + + if (FIO_LIST_IS_EMPTY(&ch->subscriptions)) { + map = &FIO___PUBSUB_POSTOFFICE.channels + ch->is_pattern; + fio___channel_map_remove(map, FIO___PUBSUB_CHANNEL2STR(ch), NULL); + if (!fio___channel_map_count(map)) + fio___channel_map_destroy(map); } - if (!s.len) - return; - char *div = (char *)FIO_MEMCHR(s.buf, '=', s.len); - char *end = (char *)FIO_MEMCHR(s.buf, ';', s.len); - if (div == s.buf || !div) - return; - if (!end) - end = s.buf + s.len; - const uint64_t prefix_secure = fio_buf2u64u("_Secure-"); - const uint32_t prefix_host = fio_buf2u32u("Host"); - uint32_t cont; - k.buf = s.buf; - k.len = div - s.buf; - v.buf = div + 1; - v.len = end - v.buf; - do { /* loop to clear away cookie prefixes in any order */ - cont = 0; - if (k.len > 8 && k.buf[0] == '_' && - fio_buf2u64u(k.buf + 1) == prefix_secure) { - cont = 1; - k.len -= 9; - k.buf += 9; - } - if (k.len > 6 && k.buf[0] == '_' && k.buf[1] == '_' && k.buf[6] == '-' && - fio_buf2u32u(k.buf + 2) == prefix_host) { - cont = 1; - k.len -= 7; - k.buf += 7; - } - } while (cont); - if (k.len) - fio___http_cmap_set_if_missing(h->cookies, k, v); + sub->channel = NULL; + +no_channel: + fio_subscription_free(sub); + return; + (void)ignr_; } -/** (Helper) Parses all HTTP Cookies */ -FIO_SFUNC void fio___http_cookie_collect(fio_http_s *h) { - fio___http_sary_s *header = NULL; - header = fio___http_hmap_node2val_ptr( - fio___http_hmap_get_ptr(h->headers, FIO_STR_INFO1((char *)"cookie"))); - if (header) { - FIO_ARRAY_EACH(fio___http_sary, header, pos) { - fio___http_cookie_parse_cookie(h, fio_bstr_info(*pos)); - } - } - /* if headers were sent, set-cookie data might belong to the handle */ - if (h->writer != fio____http_write_start) - return; - header = fio___http_hmap_node2val_ptr( - fio___http_hmap_get_ptr(h->headers + 1, - FIO_STR_INFO1((char *)"set-cookie"))); - if (!header) +/** Performs Housekeeping and defers the on_unsubscribe callback. */ +FIO_IFUNC void fio___pubsub_subscription_unsubscribe(fio_subscription_s *s) { + if (!s) return; - FIO_ARRAY_EACH(fio___http_sary, header, pos) { - fio___http_cookie_parse_set_cookie(h, fio_bstr_info(*pos)); - } - return; + s->on_message = fio___subscription_mock_cb; + fio_queue_push(fio_io_queue(), + fio___pubsub_unsubscribe_task, + (void *)s, + NULL); } -int fio_http_cookie_set___(void); /* IDE Marker */ -/* Sets a response cookie. */ -SFUNC int fio_http_cookie_set FIO_NOOP(fio_http_s *h, - fio_http_cookie_args_s cookie) { - FIO_ASSERT_DEBUG(h, "Can't set cookie for NULL HTTP handler!"); - if (!h || (h->state & (FIO_HTTP_STATE_FINISHED | FIO_HTTP_STATE_STREAMING))) - return -1; - /* promises that some warnings print only once. */ - static unsigned int warn_illegal = 0; - unsigned int need2warn = 0; +/** Subscribes to a named channel in the numerical filter's namespace. */ +void fio_subscribe___(void); /* sublimetext marker */ +SFUNC void fio_subscribe FIO_NOOP(fio_subscribe_args_s args) { + fio_subscription_s *s = NULL; + union { + FIO_LIST_HEAD *ls; + fio_str_info_s *str; + } uptr; + if (args.channel.len > 0xFFFFUL) + goto sub_error; + s = fio_subscription_new(); + if (!s) + goto sub_error; - /* valid / invalid characters in cookies, create with Ruby using: - a = [] - 256.times {|i| a[i] = 1;} - ('a'.ord..'z'.ord).each {|i| a[i] = 0;} - ('A'.ord..'Z'.ord).each {|i| a[i] = 0;} - ('0'.ord..'9'.ord).each {|i| a[i] = 0;} - "!#$%&'*+-.^_`|~".bytes.each {|i| a[i] = 0;} - p a; nil - "!#$%&'()*+-./:<=>?@[]^_`{|}~".bytes.each {|i| a[i] = 0;} # for values - p a; nil - */ - static const char invalid_cookie_name_char[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - static const char invalid_cookie_value_char[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; - /* write name and value while auto-correcting encoding issues */ - if ((cookie.name.len + cookie.value.len + cookie.domain.len + - cookie.path.len + 128) > 5119) { - FIO_LOG_ERROR("cookie data too long!"); - } - char tmp_buf[5120]; - fio_str_info_s t = FIO_STR_INFO3(tmp_buf, 0, 5119); + *s = (fio_subscription_s){ + .replay_since = args.replay_since, + .io = args.io, + .on_message = + (args.on_message ? args.on_message + : (args.io ? fio___subscription_call_protocol + : fio___subscription_mock_cb)), + .on_unsubscribe = args.on_unsubscribe, + .udata = args.udata, + }; + args.is_pattern = !!args.is_pattern; /* make sure this is either 1 or zero */ + uptr.ls = &s->node; + *uptr.str = FIO_STR_INFO3( + (args.channel.len + ? fio_bstr_write(NULL, args.channel.buf, args.channel.len) + : NULL), + args.channel.len, + FIO___PUBSUB_CHANNEL_ENCODE_CAPA(args.filter, args.is_pattern)); -#define fio___http_h_copy_cookie_ch(ch_var) \ - if (!invalid_cookie_##ch_var##_char[(uint8_t)cookie.ch_var.buf[tmp]]) { \ - t.buf[t.len++] = cookie.ch_var.buf[tmp]; \ - } else { \ - need2warn |= 1; \ - t.buf[t.len++] = '%'; \ - t.buf[t.len++] = fio_i2c(((uint8_t)cookie.ch_var.buf[tmp] >> 4) & 0x0F); \ - t.buf[t.len++] = fio_i2c((uint8_t)cookie.ch_var.buf[tmp] & 0x0F); \ - } \ - tmp += 1; \ - if (t.capa <= t.len + 3) { \ - ((t.buf == tmp_buf) \ - ? FIO_STRING_ALLOC_COPY \ - : FIO_STRING_REALLOC)(&t, fio_string_capa4len(t.len + 3)); \ - } + if (args.subscription_handle_ptr) + goto has_handle; + if (args.master_only) + goto is_master_only; + if (!args.io) + goto is_global; - if (cookie.name.buf) { - size_t tmp = 0; - if (cookie.name.len) { - while (tmp < cookie.name.len) { - fio___http_h_copy_cookie_ch(name); - } - } else { - while (cookie.name.buf[tmp]) { - fio___http_h_copy_cookie_ch(name); - } - } - if (need2warn && !warn_illegal) { - warn_illegal |= 1; - FIO_LOG_WARNING("illegal char 0x%.2x in cookie name (in %s)\n" - " automatic %% encoding applied", - cookie.name.buf[tmp], - cookie.name.buf); - } - } - t.buf[t.len++] = '='; - if (cookie.value.buf) { - size_t tmp = 0; - if (cookie.value.len) { - while (tmp < cookie.value.len) { - fio___http_h_copy_cookie_ch(value); - } - } else { - while (cookie.value.buf[tmp]) { - fio___http_h_copy_cookie_ch(value); - } - } - if (need2warn && !warn_illegal) { - warn_illegal |= 1; - FIO_LOG_WARNING("illegal char 0x%.2x in cookie value (in %s)\n" - " automatic %% encoding applied", - cookie.value.buf[tmp], - cookie.value.buf); - } - } else - cookie.max_age = -1; -#undef fio___http_h_copy_cookie_ch + fio_io_defer(fio___pubsub_subscribe_task, (void *)s, NULL); + fio_io_env_set( + args.io, + .type = (intptr_t)(0LL - (((2ULL | (!!args.is_pattern)) << 16) | + (uint16_t)args.filter)), + .name = args.channel, + .udata = s, + .on_close = (void (*)(void *))fio___pubsub_subscription_unsubscribe); + return; - /* server cookie data */ - t.buf[t.len++] = ';'; - t.buf[t.len++] = ' '; +has_handle: + fio_io_defer(fio___pubsub_subscribe_task, (void *)s, NULL); + *args.subscription_handle_ptr = (uintptr_t)s; + return; - if (cookie.max_age) { - fio_string_write2( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - FIO_STRING_WRITE_STR2((char *)"Max-Age=", 8), - FIO_STRING_WRITE_NUM(cookie.max_age), - FIO_STRING_WRITE_STR2((char *)"; ", 2)); +is_master_only: + if (!fio_io_is_master()) + goto error_not_on_master; +is_global: + if (1) { /* so C++ can jump even though there's a new var here */ + fio_io_defer(fio___pubsub_subscribe_task, (void *)s, NULL); + uint64_t hashed_value = + fio_risky_hash(args.channel.buf, + args.channel.len, + args.filter | ((size_t)args.is_pattern << 20)); + FIO___LOCK_LOCK(FIO___PUBSUB_POSTOFFICE.lock); + fio___postoffice_msmap_set( + &FIO___PUBSUB_POSTOFFICE.master_subscriptions + (!args.master_only), + hashed_value, + FIO_STR_INFO2(args.channel.buf, args.channel.len), + s, + NULL); + FIO___LOCK_UNLOCK(FIO___PUBSUB_POSTOFFICE.lock); } + return; - if (cookie.domain.buf && cookie.domain.len) { - fio_string_write2( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - FIO_STRING_WRITE_STR2((char *)"domain=", 7), - FIO_STRING_WRITE_STR2((char *)cookie.domain.buf, cookie.domain.len), - FIO_STRING_WRITE_STR2((char *)"; ", 2)); - } - if (cookie.path.buf && cookie.path.len) { - fio_string_write2( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - FIO_STRING_WRITE_STR2((char *)"path=", 5), - FIO_STRING_WRITE_STR2((char *)cookie.path.buf, cookie.path.len), - FIO_STRING_WRITE_STR2((char *)"; ", 2)); - } - if (cookie.http_only) { - fio_string_write( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - "HttpOnly; ", - 10); - } - if (cookie.secure) { - fio_string_write( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - "secure; ", - 8); - } - if (cookie.partitioned) { - fio_string_write( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - "partitioned; ", - 13); +error_not_on_master: + fio_bstr_free(uptr.str->buf); + s->node = FIO_LIST_INIT(s->node); + s->history = FIO_LIST_INIT(s->history); + fio_subscription_free(s); + FIO_LOG_WARNING( + "(%d) master-only subscription attempt on a non-master process: %.*s", + fio_io_pid(), + (int)args.channel.len, + args.channel.buf); + return; + +sub_error: + FIO_LOG_ERROR("(%d) pub/sub subscription/channel cannot be created?" + "\n\t%zu bytes long\n\t%.*s...", + fio_io_pid(), + args.channel.len, + (int)(args.channel.len > 10 ? 7 : args.channel.len), + args.channel.buf); + FIO_LOG_ERROR("failed to allocate a new subscription"); + if (args.on_unsubscribe) { + union { + void *p; + void (*fn)(void *udata); + } u = {.fn = args.on_unsubscribe}; + fio_queue_push(fio_io_queue(), + fio___pubsub_subscription_on_destroy__task, + u.p, + args.udata); } - switch (cookie.same_site) { - case FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT: /* fall through */ - default: break; - case FIO_HTTP_COOKIE_SAME_SITE_NONE: - fio_string_write( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - "SameSite=None;", - 14); - break; - case FIO_HTTP_COOKIE_SAME_SITE_LAX: - fio_string_write( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - "SameSite=Lax;", - 13); - break; - case FIO_HTTP_COOKIE_SAME_SITE_STRICT: - fio_string_write( - &t, - ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), - "SameSite=Strict;", - 16); - break; + return; +} + +/** Cancels an existing subscriptions. */ +void fio_unsubscribe___(void); /* sublimetext marker */ +int fio_unsubscribe FIO_NOOP(fio_subscribe_args_s args) { + if (args.subscription_handle_ptr) + goto has_handle; + if (args.master_only || !args.io) + goto is_global; + + return fio_io_env_remove( + args.io, + .type = (intptr_t)(0LL - (((2ULL | (!!args.is_pattern)) << 16) | + (uint16_t)args.filter)), + .name = args.channel); + +has_handle: + fio___pubsub_subscription_unsubscribe( + *(fio_subscription_s **)args.subscription_handle_ptr); + return 0; + +is_global: + if (1) { + int r; + uint64_t hashed_value = + fio_risky_hash(args.channel.buf, + args.channel.len, + args.filter | ((size_t)args.is_pattern << 20)); + FIO___LOCK_LOCK(FIO___PUBSUB_POSTOFFICE.lock); + r = fio___postoffice_msmap_remove( + &FIO___PUBSUB_POSTOFFICE.master_subscriptions + (!args.master_only), + hashed_value, + FIO_STR_INFO3(args.channel.buf, args.channel.len, (size_t)-1), + NULL); + FIO___LOCK_UNLOCK(FIO___PUBSUB_POSTOFFICE.lock); + return r; } - if (t.buf[t.len - 1] == ' ') - --t.len; +} + +/* ***************************************************************************** +Pub/Sub Message Distribution (local process) +***************************************************************************** */ + +/* performs the subscription callback */ +FIO_IFUNC void fio___subscription_on_message_task(void *s_, void *m_) { + fio_subscription_s *s = (fio_subscription_s *)s_; + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + struct { + fio_msg_s msg; + fio___pubsub_message_s *m; + uintptr_t flag; + } container = { + .msg = m->data, + .m = m, + }; + container.msg.io = s->io; + container.msg.udata = s->udata; + container.msg.is_json = !!(container.msg.is_json & FIO___PUBSUB_JSON); + s->on_message(&container.msg); + s->udata = container.msg.udata; + if (container.flag) + goto reschedule; + fio_subscription_free(s); + fio___pubsub_message_free(m); + return; +reschedule: + fio_queue_push(fio_io_queue(), fio___subscription_on_message_task, s_, m_); +} - /* set the "write" cookie store data */ - fio___http_cmap_set(h->cookies + 1, cookie.name, t, NULL); - /* set the "read" cookie store data */ - fio___http_cmap_set(h->cookies, cookie.name, cookie.value, NULL); - if (t.buf != tmp_buf) - FIO_STRING_FREE2(t); - return 0; +/* returns the internal message object. */ +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_msg2internal(fio_msg_s *msg) { + return *(fio___pubsub_message_s **)(msg + 1); } -/** Returns a cookie value (either received of newly set), if any. */ -SFUNC fio_str_info_s fio_http_cookie(fio_http_s *h, - const char *name, - size_t name_len) { - if (!(fio_atomic_or(&h->state, FIO_HTTP_STATE_COOKIES_PARSED) & - FIO_HTTP_STATE_COOKIES_PARSED)) - fio___http_cookie_collect(h); - fio_str_info_s r = - fio___http_cmap_get(h->cookies, FIO_STR_INFO2((char *)name, name_len)); - return r; +/** Defers the current callback, so it will be called again for the message. */ +SFUNC void fio_pubsub_message_defer(fio_msg_s *msg) { + ((uintptr_t *)(msg + 1))[1] = 1; } -/** Iterates through all cookies. A non-zero return will stop iteration. */ -SFUNC size_t fio_http_cookie_each(fio_http_s *h, - int (*callback)(fio_http_s *, - fio_str_info_s name, - fio_str_info_s value, - void *udata), - void *udata) { - if (!(fio_atomic_or(&h->state, FIO_HTTP_STATE_COOKIES_PARSED) & - FIO_HTTP_STATE_COOKIES_PARSED)) - fio___http_cookie_collect(h); - size_t i = 0; - FIO_MAP_EACH(fio___http_cmap, h->cookies, pos) { - ++i; - if (callback(h, pos.key, pos.value, udata)) - return i; +/* distributes a message to all of a channel's subscribers */ +FIO_SFUNC void fio___pubsub_channel_deliver_task(void *ch_, void *m_) { + fio_channel_s *ch = (fio_channel_s *)ch_; + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + FIO_LIST_HEAD *head = (&ch->subscriptions); + _Bool is_history = !!(m->data.is_json & FIO___PUBSUB_REPLAY); + head += is_history; + if (m->data.io) { /* move as many `if` statements as possible out of loops. */ + if (is_history) { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + if (m->data.io != s->io && m->data.published >= s->replay_since) + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } else { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + if (m->data.io != s->io) + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } + } else { + if (is_history) { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + if (m->data.published >= s->replay_since) + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } else { + FIO_LIST_EACH(fio_subscription_s, node, head, s) { + fio_queue_push( + fio_io_queue(), + (void (*)(void *, void *))fio___subscription_on_message_task, + fio_subscription_dup(s), + fio___pubsub_message_dup(m)); + } + } } - return i; + fio___pubsub_message_free(m); + fio_channel_free(ch); } -/** - * Iterates through all response set cookies. - * - * A non-zero return value from the callback will stop iteration. - */ -SFUNC size_t -fio_http_set_cookie_each(fio_http_s *h, - int (*callback)(fio_http_s *h, - fio_str_info_s set_cookie_header, - fio_str_info_s value, - void *udata), - void *udata) { - size_t i = 0; - fio___http_cmap_s *set_cookies = h->cookies + 1; - fio_str_info_s header_name = FIO_STR_INFO2((char *)"set-cookie", 10); - FIO_MAP_EACH(fio___http_cmap, set_cookies, pos) { - ++i; - if (callback(h, header_name, pos.value, udata)) - return i; +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_metadata_init(fio___pubsub_message_s *m); +/** distributes a message to all matching channels */ +FIO_SFUNC void fio___pubsub_message_deliver(fio___pubsub_message_s *m) { + fio___pubsub_message_metadata_init(m); /* metadata initialization */ + fio_str_info_s ch_name = + FIO_STR_INFO3(m->data.channel.buf, + m->data.channel.len, + FIO___PUBSUB_CHANNEL_ENCODE_CAPA(m->data.filter, 0)); + fio_channel_s **ch_ptr = fio___channel_map_node2key_ptr( + fio___channel_map_get_ptr(&FIO___PUBSUB_POSTOFFICE.channels, ch_name)); + if (ch_ptr) + fio_queue_push(fio_io_queue(), + fio___pubsub_channel_deliver_task, + fio_channel_dup(ch_ptr[0]), + fio___pubsub_message_dup(m)); + FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.patterns, i) { + if (i.node->key->filter == m->data.filter && + FIO_PUBSUB_PATTERN_MATCH(i.key, ch_name)) + fio_queue_push(fio_io_queue(), + fio___pubsub_channel_deliver_task, + fio_channel_dup(i.node->key), + fio___pubsub_message_dup(m)); } - return i; +} + +FIO_SFUNC void fio___pubsub_message_deliver_task(void *m_, void *ignr_) { + fio___pubsub_message_deliver((fio___pubsub_message_s *)m_); + fio___pubsub_message_free((fio___pubsub_message_s *)m_); + (void)ignr_; } /* ***************************************************************************** -Peer Address +Pub/Sub Message Type (internal data carrying structure) ***************************************************************************** */ -/** - * Writes peer address to `dest` starting with the `forwarded` header, with a - * fallback to actual socket address and a final fallback to `"[unknown]"`. - * - * If `unknown` is returned, the function returns -1. if `dest` capacity is too - * small, the number of bytes required will be returned. - * - * If all goes well, this function returns 0. - */ -SFUNC int fio_http_from(fio_str_info_s *dest, const fio_http_s *h) { - int r = 0; - /* Guess IP address from headers (forwarded) where possible */ - fio_str_info_s forwarded = - fio_http_request_header((fio_http_s *)h, - FIO_STR_INFO2((char *)"forwarded", 9), - -1); - fio_buf_info_s buf; - char *end; - if (forwarded.len) { - forwarded.len &= 1023; /* limit possible attack surface */ - for (; forwarded.len > 5;) { - if ((forwarded.buf[0] | 32) != 'f' || (forwarded.buf[1] | 32) != 'o' || - (forwarded.buf[2] | 32) != 'r' || forwarded.buf[3] != '=') { - ++forwarded.buf; - --forwarded.len; - continue; - } - forwarded.buf += 4 + (forwarded.buf[4] == '"'); - break; - } - client_address_found: - buf.buf = end = forwarded.buf; - while (*end && *end != '"' && *end != ',' && *end != ' ' && *end != ';' && - (end - forwarded.buf) < 48) - ++end; - buf.len = (size_t)(end - forwarded.buf); - } else { - forwarded = - fio_http_request_header((fio_http_s *)h, - FIO_STR_INFO2((char *)"x-forwarded-for", 15), - -1); - if (forwarded.len) { - forwarded.buf += (forwarded.buf[0] == '"'); - goto client_address_found; +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_metadata_init(fio___pubsub_message_s *m) { + if (fio_atomic_or(&m->metadata_is_initialized, 1)) { + return; + } + fio_msg_s msg = m->data; + msg.is_json &= FIO___PUBSUB_JSON; + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; ++i) { + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { + m->metadata[i] = FIO___PUBSUB_POSTOFFICE.metadata[i].build(&msg); + continue; } -#if defined(H___FIO_SOCK___H) - if (!(buf = fio_sock_peer_addr( - fio_http_controller((fio_http_s *)h)->get_fd((fio_http_s *)h))) - .len) -#endif - buf = FIO_BUF_INFO1((char *)"[unknown]"); - r = -1; + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); } - if (dest->capa > dest->len + buf.len) { /* enough space? */ - FIO_MEMCPY(dest->buf + dest->len, buf.buf, buf.len); - dest->len += buf.len; - dest->buf[dest->len] = 0; - } else - r = (int)buf.len - (!buf.len); - return r; } -/* ***************************************************************************** -Body Management - file descriptor -***************************************************************************** */ - -FIO_SFUNC fio_str_info_s fio___http_body_read_fd(fio_http_s *h, size_t len) { - h->body.buf = fio_bstr_len_set(h->body.buf, 0); - h->body.buf = fio_bstr_readfd(h->body.buf, h->body.fd, h->body.pos, len); - fio_str_info_s r = fio_bstr_info(h->body.buf); - h->body.pos += r.len; - return r; +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_metadata_free(fio___pubsub_message_s *m) { + if (!m->metadata_is_initialized) + return; + for (size_t i = 0; i < FIO___PUBSUB_METADATA_STORE_LIMIT; ++i) { + if (fio_atomic_add(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1)) { + FIO___PUBSUB_POSTOFFICE.metadata[i].cleanup(m->metadata[i]); + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + fio_atomic_sub(&FIO___PUBSUB_POSTOFFICE.metadata[i].ref, 1); + } + m->metadata_is_initialized = 0; } -FIO_SFUNC fio_str_info_s fio___http_body_read_until_fd(fio_http_s *h, - char token, - size_t limit) { - h->body.buf = fio_bstr_len_set(h->body.buf, 0); - h->body.buf = - fio_bstr_getdelim_fd(h->body.buf, h->body.fd, h->body.pos, token, limit); - fio_str_info_s r = fio_bstr_info(h->body.buf); - h->body.pos += r.len; - return r; + +/** Callback called when a letter is destroyed (reference counting). */ +FIO_SFUNC void fio___pubsub_message_on_destroy(fio___pubsub_message_s *m) { + fio___pubsub_message_metadata_free(m); } -FIO_SFUNC void fio___http_body_expect_fd(fio_http_s *h, size_t len) { - (void)h, (void)len; + +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_message_alloc(void *header) { + fio___pubsub_message_s *m; + const size_t channel_len = fio_buf2u16_le((char *)header + 18); + const size_t message_len = fio_buf2u24_le((char *)header + 20); + m = fio___pubsub_message_new(((channel_len + message_len) << 1) + + FIO___PUBSUB_MESSAGE_OVERHEAD); + FIO_ASSERT_ALLOC(m); + m->data = (fio_msg_s){ + .udata = m->buf + channel_len + message_len + 2, + .channel = FIO_BUF_INFO2(m->buf, channel_len), + .message = FIO_BUF_INFO2(m->buf + channel_len + 1, message_len), + }; + return m; } -FIO_SFUNC void fio___http_body_write_fd(fio_http_s *h, - const void *data, - size_t len) { - ssize_t written = fio_fd_write(h->body.fd, data, len); - if (written > 0) - h->body.len += written; + +FIO_IFUNC fio___pubsub_message_s *fio___pubsub_message_author( + fio_publish_args_s args) { + fio___pubsub_message_s *m = + fio___pubsub_message_new(((args.message.len + args.channel.len) << 1) + + FIO___PUBSUB_MESSAGE_OVERHEAD); + FIO_ASSERT_ALLOC(m); + m->data = (fio_msg_s){ + .io = args.from, + .id = args.id ? args.id : fio_rand64(), + .published = args.published ? args.published + : (uint64_t)fio_time2milli(fio_time_real()), + .channel = FIO_BUF_INFO2(m->buf, args.channel.len), + .message = FIO_BUF_INFO2(m->buf + args.channel.len + 1, args.message.len), + .filter = args.filter, + .is_json = args.is_json, + }; + if (args.channel.len) + FIO_MEMCPY(m->data.channel.buf, args.channel.buf, args.channel.len); + m->data.channel.buf[args.channel.len] = 0; + if (args.message.buf) + FIO_MEMCPY(m->data.message.buf, args.message.buf, args.message.len); + m->data.message.buf[args.message.len] = 0; + return m; } -/* ***************************************************************************** -Body Management - buffer -***************************************************************************** */ +FIO_SFUNC void fio___pubsub_message_encrypt(fio___pubsub_message_s *m) { + if (m->data.udata) + return; + const void *k = fio___pubsub_secret_key(m->data.id); + const uint64_t nonce[2] = {fio_risky_num(m->data.id, 0), m->data.published}; + uint8_t *pos = (uint8_t *)(m->data.message.buf + m->data.message.len + 1); + m->data.udata = (void *)pos; + fio_u2buf64_le(pos, m->data.id); + pos += 8; + fio_u2buf64_le(pos, m->data.published); + pos += 8; + fio_u2buf16_le(pos, (uint16_t)m->data.filter); + pos += 2; + fio_u2buf16_le(pos, (uint16_t)m->data.channel.len); + pos += 2; + fio_u2buf24_le(pos, (uint32_t)m->data.message.len); + pos += 3; + *(pos++) = m->data.is_json; + const size_t enc_len = m->data.channel.len + m->data.message.len + 2; + FIO_MEMCPY(pos, m->data.channel.buf, enc_len); + if (enc_len == 2) + return; + pos += enc_len; + fio_chacha20_poly1305_enc( + pos, + (void *)((char *)(m->data.udata) + FIO___PUBSUB_MESSAGE_HEADER), + m->data.channel.len + m->data.message.len + 2, + m->data.udata, + FIO___PUBSUB_MESSAGE_HEADER, + k, + nonce); +} -FIO_SFUNC int fio___http_body___move_buf2fd(fio_http_s *h) { - h->body.fd = fio_filename_tmp(); - if (h->body.fd == -1) { -#if 1 - static int error_printed = 0; - if (!error_printed) { - error_printed = 1; - FIO_LOG_ERROR("fio_http_s couldn't open temporary file! (%d) %s", - errno, - strerror(errno)); - } -#endif - return -1; - } - fio_buf_info_s b = fio_bstr_buf(h->body.buf); - if (!b.len) +FIO_SFUNC int fio___pubsub_message_decrypt(fio___pubsub_message_s *m) { + if (m->data.id) return 0; - ssize_t written = fio_fd_write(h->body.fd, b.buf, b.len); - if (written == (ssize_t)b.len) + if (!m->data.udata) + return -1; + uint8_t *pos = (uint8_t *)(m->data.udata); + m->data.id = fio_buf2u64_le(pos); + pos += 8; + m->data.published = fio_buf2u64_le(pos); + pos += 8; + m->data.filter = fio_buf2u16_le(pos); + pos += 2; + m->data.channel = FIO_BUF_INFO2(m->buf, fio_buf2u16_le(pos)); + pos += 2; + m->data.message = + FIO_BUF_INFO2(m->buf + m->data.channel.len + 1, fio_buf2u24_le(pos)); + pos += 3; + m->data.is_json = *(pos++); + const void *k = fio___pubsub_secret_key(m->data.id); + uint64_t nonce[2] = {fio_risky_num(m->data.id, 0), m->data.published}; + const size_t enc_len = m->data.channel.len + m->data.message.len + 2; + FIO_MEMCPY(m->buf, pos, enc_len); + if (enc_len == 2) return 0; - close(h->body.fd); - FIO_LOG_ERROR("fio_http_s couldn't transfer data to temporary file " - "(transferred %zd / %zu)", - written, - b.len); - return (h->body.fd = -1); + pos += enc_len; + return fio_chacha20_poly1305_dec(pos, + m->buf, + m->data.channel.len + m->data.message.len + + 2, + m->data.udata, + FIO___PUBSUB_MESSAGE_HEADER, + k, + nonce); } -FIO_SFUNC fio_str_info_s fio___http_body_read_buf(fio_http_s *h, size_t len) { - fio_str_info_s r = FIO_STR_INFO2((h->body.buf + h->body.pos), len); - h->body.pos += len; - return r; + +FIO_IFUNC void fio___pubsub_message_is_dirty(fio___pubsub_message_s *m) { + m->data.udata = NULL; } -FIO_SFUNC fio_str_info_s fio___http_body_read_until_buf(fio_http_s *h, - char token, - size_t limit) { - fio_str_info_s r = FIO_STR_INFO2((h->body.buf + h->body.pos), limit); - char *end = (char *)FIO_MEMCHR(r.buf, token, limit); - if (end) { - ++end; - r.len = end - r.buf; - h->body.pos = end - h->body.buf; - } - return r; + +/* ***************************************************************************** +Pub/Sub Message Object - IO helpers +***************************************************************************** */ + +FIO_IFUNC void fio___pubsub_message_write2io(fio_io_s *io, void *m_) { + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + if (io == m->data.io) + return; + FIO_LOG_DDEBUG2("(%d) pub/sub sending IPC/peer message.", fio_io_pid()); + fio___pubsub_message_encrypt(m); + fio_io_write2(io, + .buf = fio___pubsub_message_dup(m), + .len = (m->data.message.len + m->data.channel.len + + FIO___PUBSUB_MESSAGE_OVERHEAD_NET), + .offset = ((uintptr_t)(m->data.udata) - (uintptr_t)(m)), + .dealloc = (void (*)(void *))fio___pubsub_message_free); } -FIO_SFUNC void fio___http_body_expect_buf(fio_http_s *h, size_t len) { - if (len + h->body.len > FIO_HTTP_BODY_RAM_LIMIT) { - fio___http_body___move_buf2fd(h); + +/* A callback for IO subscriptions - sends raw message data. */ +FIO_SFUNC void FIO_ON_MESSAGE_SEND_MESSAGE(fio_msg_s *msg) { + if (!msg || !msg->message.len) return; - } - h->body.buf = fio_bstr_reserve(h->body.buf, len); + fio___pubsub_message_s *m = fio___pubsub_msg2internal(msg); + fio_io_write2(msg->io, + .buf = fio___pubsub_message_dup(m), + .len = msg->message.len, + .offset = (size_t)(msg->message.buf - (char *)m), + .dealloc = (void (*)(void *))fio___pubsub_message_free); } -FIO_SFUNC void fio___http_body_write_buf(fio_http_s *h, - const void *data, - size_t len) { - if (len + h->body.len > FIO_HTTP_BODY_RAM_LIMIT) - goto switch_to_fd; -write_to_buf: - h->body.buf = fio_bstr_write(h->body.buf, data, len); - h->body.len += len; + +/* ***************************************************************************** +Pub/Sub Message Routing +***************************************************************************** */ + +FIO_SFUNC void fio___pubsub_message_route(fio___pubsub_message_s *m) { + fio___pubsub_message_parser_s *p; + unsigned flags = m->data.is_json; + FIO_LOG_DDEBUG2("(%d) pub/sub routing message (%x)", + fio_io_pid(), + (int)m->data.is_json); + + if (flags & FIO___PUBSUB_SPECIAL) + goto is_special_message; + + if ((FIO___PUBSUB_POSTOFFICE.filter.publish & flags)) + fio_queue_push(fio_io_queue(), + fio___pubsub_message_deliver_task, + fio___pubsub_message_dup(m)); + + if ((FIO___PUBSUB_POSTOFFICE.filter.local & flags)) + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + + if ((FIO___PUBSUB_POSTOFFICE.filter.remote & flags)) + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.remote, + fio___pubsub_message_write2io, + m); + return; + +is_special_message: + FIO_LOG_DDEBUG2("(%d) pub/sub internal subscription/ID message received", + fio_io_pid()); + switch (flags) { + case FIO___PUBSUB_SPECIAL: /* TODO: run generic command on root */ break; + case FIO___PUBSUB_SUB: + fio_subscribe(.io = m->data.io, + .channel = m->data.channel, + .on_message = fio___subscription_mock_cb, + .filter = m->data.filter, + .is_pattern = (uint8_t)(m->data.id - 1)); + return; + case FIO___PUBSUB_UNSUB: + fio_unsubscribe(.io = m->data.io, + .channel = m->data.channel, + .on_message = fio___subscription_mock_cb, + .filter = m->data.filter, + .is_pattern = (uint8_t)(m->data.id - 1)); + return; + + case FIO___PUBSUB_IDENTIFY: + p = fio___pubsub_message_parser(m->data.io); + if (p) { + p->uuid[0] = m->data.id; + p->uuid[1] = m->data.published; + fio___pubsub_broadcast_connected_set( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + p->uuid[0], + p->uuid[1]); + } + FIO_LOG_INFO("(cluster) identified new peer (%zu connections)", + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + return; + case FIO___PUBSUB_FORWARDER: /* fall through */ + case (FIO___PUBSUB_FORWARDER | FIO___PUBSUB_JSON): + if (FIO___PUBSUB_POSTOFFICE.filter.remote) { /* root process */ + fio___pubsub_message_is_dirty(m); + m->data.message.len -= 8; + m->data.is_json &= FIO___PUBSUB_JSON; + fio_pubsub_engine_s *e = (fio_pubsub_engine_s *)(uintptr_t)fio_buf2u64u( + m->data.message.buf + m->data.message.len); + m->data.message.buf[m->data.message.len] = 0; + e->publish(e, &m->data); + } else { /* child process */ + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + } + return; + + case FIO___PUBSUB_HISTORY_START: + FIO_LOG_DDEBUG2("(%d) pub/sub internal history start message received", + fio_io_pid()); + /* TODO! */ + return; + case FIO___PUBSUB_HISTORY_END: + FIO_LOG_DDEBUG2("(%d) pub/sub internal history end message received", + fio_io_pid()); + /* TODO! */ + return; + } return; -switch_to_fd: - if (fio___http_body___move_buf2fd(h)) - goto write_to_buf; - fio___http_body_write_fd(h, data, len); } /* ***************************************************************************** -Body Management - Public API +Pub/Sub - Publish ***************************************************************************** */ -/** Gets the body (payload) length associated with the HTTP handle. */ -SFUNC size_t fio_http_body_length(fio_http_s *h) { return h->body.len; } +FIO_SFUNC void fio___publish_message_task(void *m_, void *ignr_) { + (void)ignr_; + fio___pubsub_message_s *m = (fio___pubsub_message_s *)m_; + fio___pubsub_message_route(m); + fio___pubsub_message_free(m); +} -/** - * If the body is stored in a temporary file, returns the file's handle. - * - * Otherwise returns -1. - */ -SFUNC int fio_http_body_fd(fio_http_s *h) { return h->body.fd; } +/** Publishes a message to the relevant subscribers (if any). */ +void fio_publish___(void); /* SublimeText marker*/ +void fio_publish FIO_NOOP(fio_publish_args_s args) { + if (FIO_UNLIKELY(args.channel.len > 0xFFFFUL)) { + FIO_LOG_ERROR("(%d) pub/sub channel name too long (%zu bytes)", + fio_io_pid(), + args.channel.len); + return; + } + if (FIO_UNLIKELY(args.message.len > 0xFFFFFFUL)) { + FIO_LOG_ERROR("(%d) pub/sub message payload too large (%zu bytes)", + fio_io_pid(), + args.message.len); + return; + } + fio___pubsub_message_s *m; + fio_msg_s msg; -/** Adjusts the body's reading position. Negative values start at the end. */ -SFUNC size_t fio_http_body_seek(fio_http_s *h, ssize_t pos) { - if (pos < 0) { - pos += h->body.len; - if (pos < 0) - pos = 0; + if (!args.engine) { + args.engine = FIO_PUBSUB_DEFAULT; + if (!args.engine) + args.engine = FIO_PUBSUB_DEFAULT = FIO_PUBSUB_CLUSTER; + if (args.filter < 0) + args.engine = FIO_PUBSUB_LOCAL; } - if ((size_t)pos >= h->body.len) - pos = h->body.len; - h->body.pos = pos; - return pos; -} + if ((uintptr_t)args.engine > 0xFFUL) + goto external_engine; -/** Reads up to `length` of data from the body, returns nothing on EOF. */ -SFUNC fio_str_info_s fio_http_body_read(fio_http_s *h, size_t length) { - fio_str_info_s r = {0}; - if (h->body.pos == h->body.len) - return r; - if (h->body.pos + length > h->body.len) - length = h->body.len - h->body.pos; - r = ((h->body.fd == -1) ? fio___http_body_read_buf - : fio___http_body_read_fd)(h, length); - return r; -} + m = fio___pubsub_message_author(args); + m->data.is_json = ((!!args.is_json) | ((uint8_t)(uintptr_t)args.engine)); -/** - * Reads from the body until finding `token`, reaching `limit` or EOF. - * - * Note: `limit` is ignored if zero or larger than remaining data. - */ -SFUNC fio_str_info_s fio_http_body_read_until(fio_http_s *h, - char token, - size_t limit) { - fio_str_info_s r = {0}; - if (h->body.pos == h->body.len) - return r; - if (!limit || (h->body.pos + limit) > h->body.len) - limit = h->body.len - h->body.pos; - r = ((h->body.fd == -1) ? fio___http_body_read_until_buf - : fio___http_body_read_until_fd)(h, token, limit); - return r; -} + FIO_LOG_DDEBUG2("publishing pub/sub message (scheduling)"); + fio_io_defer(fio___publish_message_task, m, NULL); + return; -/** Allocates a body (payload) of (at least) the `expected_length`. */ -SFUNC void fio_http_body_expect(fio_http_s *h, size_t expected_length) { - ((h->body.fd == -1) ? fio___http_body_expect_buf - : fio___http_body_expect_fd)(h, expected_length); -} +external_engine: -/** Writes `data` to the body (payload) associated with the HTTP handle. */ -SFUNC void fio_http_body_write(fio_http_s *h, const void *data, size_t len) { - if (!data || !len) - return; - ((h->body.fd == -1) ? fio___http_body_write_buf - : fio___http_body_write_fd)(h, data, len); + msg.message = args.message; + args.message.buf = NULL; + args.message.len += 8; + + m = fio___pubsub_message_author(args); + m->data.is_json = ((!!args.is_json) | ((uint8_t)FIO___PUBSUB_FORWARDER)); + FIO_MEMCPY(m->data.message.buf, msg.message.buf, msg.message.len); + fio_u2buf64u(m->data.message.buf + msg.message.len, (uintptr_t)args.engine); + fio_io_defer(fio___publish_message_task, m, NULL); } /* ***************************************************************************** -A Response Payload +Pub/Sub Message on-the-wire parsing ***************************************************************************** */ -/** ETag Helper */ -FIO_IFUNC int fio___http_response_etag_if_none_match(fio_http_s *h); - -FIO_SFUNC int fio____http_write_done(fio_http_s *h, - fio_http_write_args_s *args) { - return -1; - (void)h, (void)args; -} - -FIO_SFUNC int fio____http_write_upgraded(fio_http_s *h, - fio_http_write_args_s *args) { - h->controller->write_body(h, *args); - h->sent += args->len; - return 0; -} - -FIO_SFUNC int fio____http_write_start(fio_http_s *h, - fio_http_write_args_s *args) { - /* if response has an `etag` header matching `if-none-match`, skip */ - fio___http_hmap_s *hdrs = h->headers + (!!h->status); - if (h->status) { - if (args->len && fio___http_response_etag_if_none_match(h)) - return -1; - if (!args->len && args->finish && h->status >= 400) { - fio_http_send_error_response(h, h->status); - return 0; +FIO_IFUNC void fio___pubsub_message_parse( + fio_io_s *io, + void (*cb)(fio_io_s *, fio___pubsub_message_s *)) { + fio___pubsub_message_parser_s *parser = fio___pubsub_message_parser(io); + if (!parser) + return; + size_t existing = parser->len; + if (!parser->msg) { + while (existing < FIO___PUBSUB_MESSAGE_HEADER) { /* get message length */ + size_t consumed = + fio_io_read(io, + parser->buf + existing, + FIO___PUBSUB_MESSAGE_OVERHEAD_NET - existing); + if (!consumed) { + parser->len = existing; + return; + } + existing += consumed; } - /* validate Date header */ - fio___http_hmap_set2( - hdrs, - FIO_STR_INFO2((char *)"date", 4), - fio_http_date(fio_http_get_timestump() / FIO___HTTP_TIME_DIV), - 0); + parser->msg = fio___pubsub_message_alloc(parser->buf); + FIO_MEMCPY(parser->msg->data.udata, parser->buf, existing); } - /* test if streaming / single body response */ - if (!fio___http_hmap_get_ptr(hdrs, - FIO_STR_INFO2((char *)"content-length", 14))) { - if (args->finish) { - /* validate / set Content-Length (not streaming) */ - char ibuf[32]; - fio_str_info_s k = FIO_STR_INFO2((char *)"content-length", 14); - fio_str_info_s v = FIO_STR_INFO3(ibuf, 0, 32); - v.len = fio_digits10u(args->len); - fio_ltoa10u(v.buf, args->len, v.len); - fio___http_hmap_set2(hdrs, k, v, -1); - } else { - h->state |= FIO_HTTP_STATE_STREAMING; + /* known message length, read to end and publish */ + fio___pubsub_message_s *m = parser->msg; + const size_t needed = m->data.channel.len + m->data.message.len + + FIO___PUBSUB_MESSAGE_OVERHEAD_NET; + FIO_LOG_DDEBUG2("(%d) pub/sub parsing IPC/peer message (%zu/%zu bytes)", + fio_io_pid(), + existing, + needed); + while (existing < needed) { + size_t consumed = + fio_io_read(io, (char *)m->data.udata + existing, needed - existing); + if (!consumed) { + parser->len = existing; + return; } + existing += consumed; } - - /* start a response, unless status == 0 (which starts a request). */ - h->controller->send_headers(h); - return (h->writer = fio____http_write_cont)(h, args); -} - -FIO_SFUNC int fio____http_write_cont(fio_http_s *h, - fio_http_write_args_s *args) { - if (args->buf || args->fd) { - h->controller->write_body(h, *args); - h->sent += args->len; - } - if (args->finish) { - h->state |= FIO_HTTP_STATE_FINISHED; - h->writer = (h->state & FIO_HTTP_STATE_UPGRADED) - ? fio____http_write_upgraded - : fio____http_write_done; - h->controller->on_finish(h); + parser->msg = NULL; + parser->len = 0; + m->data.io = io; + if (fio___pubsub_message_decrypt(m)) { + FIO_LOG_SECURITY("(%d) pub/sub message decryption error", fio_io_pid()); + fio_io_close_now(io); + } else { + cb(io, m); } - return 0; + fio___pubsub_message_free(m); + return; /* consume no more than 1 message at a time */ } -void fio_http_write___(void); /* IDE Marker */ -/** - * Writes `data` to the response body associated with the HTTP handle after - * sending all headers (no further headers may be sent). - */ -SFUNC void fio_http_write FIO_NOOP(fio_http_s *h, fio_http_write_args_s args) { - if (!h || !h->controller) - goto handle_error; - if (h->writer(h, &args)) - goto handle_error; - return; +/* ***************************************************************************** +Pub/Sub Protocols +***************************************************************************** */ -handle_error: - if (args.fd) - close(args.fd); - if (args.dealloc && args.buf) - args.dealloc((void *)args.buf); +FIO_SFUNC void fio___pubsub_on_message_master(fio_io_s *io, + fio___pubsub_message_s *msg) { + fio___pubsub_message_route(msg); + (void)io; +} +FIO_SFUNC void fio___pubsub_on_message_worker(fio_io_s *io, + fio___pubsub_message_s *msg) { + fio___pubsub_message_route(msg); + (void)io; +} +FIO_SFUNC void fio___pubsub_on_message_remote(fio_io_s *io, + fio___pubsub_message_s *msg) { + fio___pubsub_message_map_s *map = &FIO___PUBSUB_POSTOFFICE.remote_messages; + map += !!(msg->data.is_json & FIO___PUBSUB_REPLAY); + fio___pubsub_message_s *existing = fio___pubsub_message_map_set(map, msg); + if (existing != msg) + return; /* already received */ + fio___pubsub_message_route(msg); + (void)io; +} +FIO_SFUNC void fio___pubsub_protocol_on_attach(fio_io_s *io) { + fio___pubsub_message_parser_init(fio___pubsub_message_parser(io)); +} +FIO_SFUNC void fio___pubsub_protocol_on_data_master(fio_io_s *io) { + fio___pubsub_message_parse(io, fio___pubsub_on_message_master); +} +FIO_SFUNC void fio___pubsub_protocol_on_data_worker(fio_io_s *io) { + fio___pubsub_message_parse(io, fio___pubsub_on_message_worker); +} +FIO_SFUNC void fio___pubsub_protocol_on_data_remote(fio_io_s *io) { + fio___pubsub_message_parse(io, fio___pubsub_on_message_remote); } -/** ETag Helper */ -FIO_IFUNC int fio___http_response_etag_if_none_match(fio_http_s *h) { - if (!fio_http_etag_is_match(h)) - return 0; - h->status = 304; - fio___http_hmap_set2(HTTP_HDR_RESPONSE(h), - FIO_STR_INFO2((char *)"content-length", 14), - FIO_STR_INFO0, - -1); +FIO_SFUNC void fio___pubsub_protocol_on_close(void *p_, void *udata) { + fio___pubsub_message_parser_s *p = (fio___pubsub_message_parser_s *)p_; + if (p->uuid[0] || p->uuid[1]) { + // TODO!: fio___pubsub_broadcast_hello(fio_io_s *io) ? + fio___pubsub_broadcast_connected_remove( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + p->uuid[0], + p->uuid[1], + NULL); + FIO_LOG_INFO("(cluster) lost peer connection (%zu connections)", + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + } + fio___pubsub_message_parser_destroy(p); + if (!fio_io_is_master()) + fio_io_stop(); + (void)udata; +} - h->controller->send_headers(h); - h->state |= FIO_HTTP_STATE_FINISHED; - h->writer = fio____http_write_done; - h->controller->on_finish(h); - return -1; +static void fio___pubsub_protocol_on_timeout(fio_io_s *io) { + static const uint8_t ping_msg[FIO___PUBSUB_MESSAGE_OVERHEAD] = { + [23] = FIO___PUBSUB_PING}; + fio_io_write2(io, + .buf = (void *)ping_msg, + .len = FIO___PUBSUB_MESSAGE_OVERHEAD); } /* ***************************************************************************** -WebSocket / SSE Helpers +Pub/Sub Engine Support Implementation ***************************************************************************** */ -/** Returns non-zero if request headers ask for a WebSockets Upgrade.*/ -SFUNC int fio_http_websocket_requested(fio_http_s *h) { - fio_str_info_s val = - fio_http_request_header(h, FIO_STR_INFO2((char *)"connection", 10), 0); - /* test for "Connection: Upgrade" (TODO? allow for multi-value?) */ - if (val.len < 7 || - !(((fio_buf2u32u(val.buf) | 0x20202020UL) == fio_buf2u32u("upgr")) || - ((fio_buf2u32u(val.buf + 3) | 0x20202020) == fio_buf2u32u("rade")))) - return 0; - /* test for "Upgrade: websocket" (TODO? allow for multi-value?) */ - val = fio_http_request_header(h, FIO_STR_INFO2((char *)"upgrade", 7), 0); - if (val.len < 7 || - !(((fio_buf2u64u(val.buf) | 0x2020202020202020ULL) == - fio_buf2u64u("websocke")) || - ((fio_buf2u32u(val.buf + 5) | 0x20202020UL) == fio_buf2u32u("cket")))) - return 0; - val = fio_http_request_header(h, - FIO_STR_INFO2((char *)"sec-websocket-key", 17), - 0); - if (val.len != 24) - return 0; - /* test for version value */ - val = fio_http_request_header( - h, - FIO_STR_INFO2((char *)"sec-websocket-version", 21), - 0); - if (val.len != 2 || (val.buf[0] != '1' || val.buf[1] != '3')) - return -1; /* note the error value is still true, requested WebSocket... */ - return 1; +static void fio___pubsub_mock_detached(const fio_pubsub_engine_s *eng) { + (void)eng; +} +static void fio___pubsub_mock_sub_unsub(const fio_pubsub_engine_s *eng, + fio_buf_info_s channel, + int16_t filter) { + (void)eng, (void)channel, (void)filter; +} +static void fio___pubsub_mock_publish(const fio_pubsub_engine_s *eng, + fio_msg_s *msg) { + (void)eng, (void)msg; /* TODO:? sensible default? publish to cluster? */ } -/** Sets response data to agree to a WebSockets Upgrade.*/ -SFUNC void fio_http_upgrade_websocket(fio_http_s *h) { - { /* validate WebSocket version */ - fio_str_info_s val = fio_http_request_header( - h, - FIO_STR_INFO2((char *)"sec-websocket-version", 21), - 0); - if (val.len != 2 || (val.buf[0] != '1' || val.buf[1] != '3')) { - fio_http_response_header_set( - h, - FIO_STR_INFO2((char *)"sec-websocket-version", 21), - FIO_STR_INFO2((char *)"13", 2)); - fio_http_send_error_response(h, 400); - } - } - h->status = 101; - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"connection", 10), - FIO_STR_INFO2((char *)"Upgrade", 7)); - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"upgrade", 7), - FIO_STR_INFO2((char *)"websocket", 9)); - { /* Sec-WebSocket-Accept */ - fio_str_info_s k = - fio_http_request_header(h, - FIO_STR_INFO2((char *)"sec-websocket-key", 17), - 0); - FIO_STR_INFO_TMP_VAR(accept_val, 63); - if (k.len != 24) - goto handshake_error; - fio_string_write(&accept_val, NULL, k.buf, k.len); - fio_string_write(&accept_val, - NULL, - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", - 36); - fio_sha1_s sha = fio_sha1(accept_val.buf, accept_val.len); - fio_sha1_digest(&sha); - accept_val.len = 0; - fio_string_write_base64enc(&accept_val, - NULL, - fio_sha1_digest(&sha), - fio_sha1_len(), - 0); - fio_http_response_header_set( - h, - FIO_STR_INFO2((char *)"sec-websocket-accept", 20), - accept_val); +static void fio___pubsub_attach_task(void *engine_, void *ignr_) { + (void)ignr_; + fio_pubsub_engine_s *engine = (fio_pubsub_engine_s *)engine_; + if (!engine->detached) + engine->detached = fio___pubsub_mock_detached; + if (!engine->subscribe) + engine->subscribe = fio___pubsub_mock_sub_unsub; + if (!engine->unsubscribe) + engine->unsubscribe = fio___pubsub_mock_sub_unsub; + if (!engine->psubscribe) + engine->psubscribe = fio___pubsub_mock_sub_unsub; + if (!engine->punsubscribe) + engine->punsubscribe = fio___pubsub_mock_sub_unsub; + if (!engine->publish) + engine->publish = fio___pubsub_mock_publish; + fio___pubsub_engines_set(&FIO___PUBSUB_POSTOFFICE.engines, engine); + FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.channels, i) { + engine->subscribe(engine, + FIO_BUF_INFO2(i.key.buf, i.key.len), + (i.key.capa >> 16)); } - { /* finish up */ - h->state |= FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_WEBSOCKET; - fio_http_write_args_s args = {.finish = 1}; - fio_http_write FIO_NOOP(h, args); + FIO_MAP_EACH(fio___channel_map, &FIO___PUBSUB_POSTOFFICE.patterns, i) { + engine->psubscribe(engine, + FIO_BUF_INFO2(i.key.buf, i.key.len), + (i.key.capa >> 16)); } - return; -handshake_error: - fio_http_send_error_response(h, 403); - return; } -/** Sets request data to request a WebSockets Upgrade.*/ -SFUNC void fio_http_websocket_set_request(fio_http_s *h) { - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"connection", 10), - FIO_STR_INFO2((char *)"Upgrade", 7)); - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"pragma", 6), - FIO_STR_INFO2((char *)"no-cache", 8)); - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"cache-control", 13), - FIO_STR_INFO2((char *)"no-cache", 8)); - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"upgrade", 7), - FIO_STR_INFO2((char *)"websocket", 9)); - { - fio_http_request_header_set_if_missing( - h, - FIO_STR_INFO2((char *)"origin", 6), - fio_http_request_header(h, FIO_STR_INFO2((char *)"host", 4), 0)); - } - fio_http_request_header_set( - h, - FIO_STR_INFO2((char *)"sec-websocket-version", 21), - FIO_STR_INFO2((char *)"13", 2)); +FIO_SFUNC void fio___pubsub_detach_task(void *engine, void *ignr_) { + (void)ignr_; + fio_pubsub_engine_s *e = (fio_pubsub_engine_s *)engine; + fio___pubsub_engines_remove(&FIO___PUBSUB_POSTOFFICE.engines, e, NULL); +} - { - uint64_t tmp[2] = {fio_rand64(), fio_rand64()}; - FIO_STR_INFO_TMP_VAR(key, 64); - fio_string_write_base64enc(&key, NULL, tmp, 16, 0); - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"sec-websocket-key", 17), - key); - } - /* sec-websocket-extensions ? */ - /* send request? */ +/** Attaches an engine, so it's callback can be called by facil.io. */ +SFUNC void fio_pubsub_attach(fio_pubsub_engine_s *engine) { + if (!engine) + return; + fio_io_defer(fio___pubsub_attach_task, engine, NULL); } -/** Returns non-zero if the response accepts a WebSocket upgrade request. */ -SFUNC int fio_http_websocket_accepted(fio_http_s *h) { - if (h->status != 101) - return 0; - if (!fio_http_websocket_requested(h)) - return 0; - fio_str_info_s tst = - fio_http_response_header(h, FIO_STR_INFO2((char *)"connection", 10), 0); - if (tst.len < 7 || - (fio_buf2u64_le(tst.buf) | (uint64_t)0x20202020202020FFULL) != - (fio_buf2u64_le("upgrade") | (uint64_t)0x20202020202020FFULL)) - return 0; - tst = fio_http_response_header(h, FIO_STR_INFO2((char *)"upgrade", 7), 0); - if (tst.len < 9 || (tst.buf[0] | 32) != 'w' || - (fio_buf2u64u(tst.buf + 1) | (uint64_t)0x2020202020202020ULL) != - fio_buf2u64u("ebsocket")) - return 0; - { /* Sec-WebSocket-Accept */ - tst = fio_http_response_header( - h, - FIO_STR_INFO2((char *)"sec-websocket-accept", 20), - 0); - if (!tst.len) - return 0; +/** Schedules an engine for Detachment, so it could be safely destroyed. */ +SFUNC void fio_pubsub_detach(fio_pubsub_engine_s *engine) { + fio_queue_push(fio_io_queue(), fio___pubsub_detach_task, engine, NULL); +} - fio_str_info_s k = - fio_http_request_header(h, - FIO_STR_INFO2((char *)"sec-websocket-key", 17), - 0); - FIO_STR_INFO_TMP_VAR(accept_val, 63); - if (k.len != 24) - return 0; - fio_string_write(&accept_val, NULL, k.buf, k.len); - fio_string_write(&accept_val, - NULL, - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", - 36); - fio_sha1_s sha = fio_sha1(accept_val.buf, accept_val.len); - fio_sha1_digest(&sha); - accept_val.len = 0; - fio_string_write_base64enc(&accept_val, - NULL, - fio_sha1_digest(&sha), - fio_sha1_len(), - 0); - if (!FIO_STR_INFO_IS_EQ(tst, accept_val)) { - FIO_LOG_DDEBUG2( - "(%d) sec-websocket-key invalid, WebSocket handshake failed.\n\t" - "%s != %s", - getpid(), - tst.buf, - accept_val.buf); - return 0; +/* ***************************************************************************** +Channel Creation / Destruction Callback (notifying engines) +***************************************************************************** */ + +/** Callback for when a channel is created. */ +FIO_IFUNC void fio___channel_on_create(fio_channel_s *ch) { + fio_buf_info_s name = FIO_BUF_INFO2(ch->name, ch->name_len); + FIO_LOG_DDEBUG2("(%d) pub/sub %s created, filter %d, length %zu bytes: %s", + fio_io_pid(), + (ch->is_pattern ? "pattern" : "channel"), + (int)ch->filter, + (size_t)ch->name_len, + name.buf); + FIO_MAP_EACH(fio___pubsub_engines, &FIO___PUBSUB_POSTOFFICE.engines, i) { + (&i.key->subscribe + ch->is_pattern)[0](i.key, name, ch->filter); + } + if (!FIO___PUBSUB_POSTOFFICE.filter.remote) { /* inform root process */ + FIO_LOG_DDEBUG2("informing root process of new channel."); + fio___pubsub_message_s *m = + fio___pubsub_message_author((fio_publish_args_s){ + .id = (uint64_t)(ch->is_pattern + 1), + .channel = FIO_BUF_INFO2(ch->name, ch->name_len), + .filter = ch->filter, + .is_json = FIO___PUBSUB_SUB, + }); + if (m) { + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + fio___pubsub_message_free(m); } } - h->state |= (FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_WEBSOCKET | - FIO_HTTP_STATE_FINISHED); - h->writer = fio____http_write_upgraded; - return 1; -} - -/** Returns non-zero if request headers ask for an EventSource (SSE) Upgrade.*/ -SFUNC int fio_http_sse_requested(fio_http_s *h) { - fio_str_info_s val = - fio_http_request_header(h, FIO_STR_INFO2((char *)"accept", 6), 0); - if (val.len < 17) - return 0; - if ((val.buf[0] | 32) != 't') - return 0; - uint64_t t0 = fio_buf2u64u(val.buf + 1) | (uint64_t)0x2020202020202020ULL; - uint64_t t1 = fio_buf2u64u(val.buf + 9) | (uint64_t)0x2020202020202020ULL; - if ((t0 != fio_buf2u64u("ext/even")) || (t1 != fio_buf2u64u("t-stream"))) - return 0; /* note that '/' and '-' both have 32 (bit[5]) set */ - FIO_LOG_DDEBUG2("(%d) EventSource connection requested.", - fio_thread_getpid()); - return 1; } +/** Callback for when a channel is destroy. */ +FIO_IFUNC void fio___channel_on_destroy(fio_channel_s *ch) { + fio_buf_info_s name = FIO_BUF_INFO2(ch->name, ch->name_len); -/** Returns non-zero if the response accepts an SSE request. */ -SFUNC int fio_http_sse_accepted(fio_http_s *h) { - if (!fio_http_sse_requested(h)) - return 0; - if (h->status != 200) - return 0; - fio_str_info_s val = - fio_http_request_header(h, FIO_STR_INFO2((char *)"accept", 6), 0); - for (size_t i = 0; i < 2; ++i) { - if (val.len < 17) - return 0; - if ((val.buf[0] | 32) != 't') - return 0; - uint64_t t0 = fio_buf2u64u(val.buf + 1) | (uint64_t)0x2020202020202020ULL; - uint64_t t1 = fio_buf2u64u(val.buf + 9) | (uint64_t)0x2020202020202020ULL; - if ((t0 != fio_buf2u64u("ext/even")) || (t1 != fio_buf2u64u("t-stream"))) - return 0; /* note that '/' and '-' both have 32 (bit[5]) set */ - val = fio_http_response_header(h, - FIO_STR_INFO2((char *)"content-type", 12), - 0); + FIO_MAP_EACH(fio___pubsub_engines, &FIO___PUBSUB_POSTOFFICE.engines, i) { + (&i.key->unsubscribe + ch->is_pattern)[0](i.key, name, ch->filter); } - h->state |= - (FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_SSE | FIO_HTTP_STATE_FINISHED); - h->writer = fio____http_write_upgraded; - FIO_LOG_DDEBUG2("EventSource connection accepted."); - return 1; -} -/** Sets response data to agree to an EventSource (SSE) Upgrade.*/ -SFUNC void fio_http_upgrade_sse(fio_http_s *h) { - if (h->state) - return; - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"content-type", 12), - FIO_STR_INFO2((char *)"text/event-stream", 17)); - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"cache-control", 13), - FIO_STR_INFO2((char *)"no-store", 8)); - fio___http_hmap_remove(HTTP_HDR_RESPONSE(h), - FIO_STR_INFO2((char *)"content-length", 14), - NULL); - h->state |= - FIO_HTTP_STATE_FINISHED | FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_SSE; - h->controller->send_headers(h); - h->writer = fio____http_write_upgraded; - h->controller->on_finish(h); -} + if (!FIO___PUBSUB_POSTOFFICE.filter.remote) { /* inform root process */ + fio___pubsub_message_s *m = + fio___pubsub_message_author((fio_publish_args_s){ + .id = (uint64_t)(ch->is_pattern + 1), + .channel = FIO_BUF_INFO2(ch->name, ch->name_len), + .filter = ch->filter, + .is_json = FIO___PUBSUB_UNSUB, + }); + if (m) { + fio_io_protocol_each(&FIO___PUBSUB_POSTOFFICE.protocol.ipc, + fio___pubsub_message_write2io, + m); + fio___pubsub_message_free(m); + } + } -/** Sets request data to request an EventSource (SSE) Upgrade.*/ -SFUNC void fio_http_sse_set_request(fio_http_s *h) { - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"accept", 6), - FIO_STR_INFO2((char *)"text/event-stream", 17)); - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"connection", 10), - FIO_STR_INFO2((char *)"keep-alive", 10)); - fio_http_request_header_set(h, - FIO_STR_INFO2((char *)"cache-control", 13), - FIO_STR_INFO2((char *)"no-cache", 8)); + FIO_LOG_DDEBUG2("(%d) pub/sub %s destroyed, filter %d, length %zu bytes: %s", + fio_io_pid(), + (ch->is_pattern ? "pattern" : "channel"), + (int)ch->filter, + (size_t)ch->name_len, + name.buf); } /* ***************************************************************************** -Header Parsing Helpers - Implementation +Broadcasting for remote connections ***************************************************************************** */ -/** - * Assumes a Buffer of bytes containing length info and string data as such: - * - * [ 2 byte info: (type | (len << 2)) ] - * [ Optional 2 byte info: (len << 2) (if type was 1)] - * [ String of `len` bytes][ NUL byte (not counted in `len`)] - */ +FIO_IFUNC fio_u512 fio___pubsub_broadcast_compose(uint64_t tick) { + /* [0-1] Sender's 128 bit UUID + * [2] Random nonce + * [3] Timestamp in milliseconds + * [4-5] MAC + */ + fio_u512 u = {0}; + uint64_t hello_rand = fio_rand64(); + const void *k = fio___pubsub_secret_key(hello_rand); + u.u64[0] = FIO___PUBSUB_POSTOFFICE.uuid.u64[0]; + u.u64[1] = FIO___PUBSUB_POSTOFFICE.uuid.u64[1]; + u.u64[2] = fio_ltole64(hello_rand); /* persistent endienes required for k */ + u.u64[3] = fio_ltole64(tick); + fio_poly1305_auth(u.u64 + 4, k, NULL, 0, u.u64, 32); + return u; +} -FIO_SFUNC int fio___http_header_parse_properties(fio_str_info_s *dst, - char *start, - char *const end) { - for (;;) { - char *nxt = (char *)FIO_MEMCHR(start, ';', end - start); - if (!nxt) - nxt = end; - char *eq = (char *)FIO_MEMCHR(start, '=', nxt - start); - if (!eq) - eq = nxt; - /* write value to dst */ - size_t len = eq - start; - if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) - return -1; /* too long */ - fio_u2buf16u(dst->buf + dst->len, - ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_DATA)); - dst->len += 2; - if (len) - FIO_MEMCPY(dst->buf + dst->len, start, len); - dst->len += len; - dst->buf[dst->len++] = 0; +FIO_SFUNC void fio___pubsub_broadcast_hello(fio_io_s *io) { - eq += (eq[0] == '='); - eq += (eq[0] == ' ' || eq[0] == '\t'); - len = nxt - eq; - if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) - return -1; /* too long */ - fio_u2buf16u(dst->buf + dst->len, - ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_DATA)); - dst->len += 2; - if (len) - FIO_MEMCPY(dst->buf + dst->len, eq, len); - dst->len += len; - dst->buf[dst->len++] = 0; + if (!fio_io_is_running() || !(io = FIO___PUBSUB_POSTOFFICE.broadcaster)) + return; + static int64_t last_hello = 0; + int64_t this_hello = fio_io_last_tick(); + if (last_hello == this_hello) + return; + fio_u512 u = fio___pubsub_broadcast_compose((last_hello = this_hello)); + struct sockaddr_in addr = (struct sockaddr_in){ + .sin_family = AF_INET, + .sin_port = fio_lton16((uint16_t)(uintptr_t)fio_io_udata(io)), + .sin_addr.s_addr = INADDR_BROADCAST, // inet_addr("255.255.255.255"), + }; + FIO_LOG_DEBUG2("(%d) pub/sub sending broadcast.", fio_io_pid()); + sendto(fio_io_fd(io), + (const char *)u.u8, + 48, + 0, + (struct sockaddr *)&addr, + sizeof(addr)); +} - if (nxt == end) - return 0; - nxt += (*nxt == ';'); - while (*nxt == ' ' || *nxt == '\t') - ++nxt; - start = nxt; - } +FIO_SFUNC int fio___pubsub_broadcast_hello_task(void *io_, void *ignr_) { + (void)ignr_; + fio_io_s *io = (fio_io_s *)io_; + fio___pubsub_broadcast_hello(io); + fio_io_free(io); return 0; } -FIO_IFUNC int fio___http_header_parse(fio___http_hmap_s *map, - fio_str_info_s *dst, - fio_str_info_s header_name) { - fio___http_sary_s *a = - fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, header_name)); - if (!a) +FIO_SFUNC int fio___pubsub_broadcast_hello_validate(uint64_t *hello) { + uint64_t mac[2] = {0}; + /* test server UUID (ignore self generated messages) */ + if (hello[0] == FIO___PUBSUB_POSTOFFICE.uuid.u64[0] && + hello[1] == FIO___PUBSUB_POSTOFFICE.uuid.u64[1]) return -1; - dst->len = 0; - if (dst->capa < 3) + /* test time window */ + mac[0] = fio_io_last_tick(); + if (mac[0] > fio_ltole64(hello[3]) + 8192 || + mac[0] + 8192 < fio_ltole64(hello[3])) { + FIO_LOG_SECURITY( + "(%d) pub/sub-broadcast timing error - possible replay attack?", + fio_io_pid()); return -1; - dst->buf[dst->len++] = 0; /* first byte is a pretend NUL */ - FIO_ARRAY_EACH(fio___http_sary, a, pos) { - fio_buf_info_s i = fio_bstr_buf(*pos); - if (!i.len) - continue; - char *const end = i.buf + i.len; - char *sep; - do { - sep = (char *)FIO_MEMCHR(i.buf, ',', end - i.buf); - if (!sep) - sep = end; - char *prop = (char *)FIO_MEMCHR(i.buf, ';', sep - i.buf); - if (!prop) - prop = sep; - size_t len = prop - i.buf; - if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) - return -1; /* too long */ - fio_u2buf16u(dst->buf + dst->len, (len << 2)); - dst->len += 2; - FIO_MEMCPY(dst->buf + dst->len, i.buf, len); - dst->len += len; - dst->buf[dst->len++] = 0; - if (prop != sep) { /* parse properties */ - ++prop; - len = sep - prop; - if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) - return -1; - const size_t old_len = dst->len; - dst->len += 2; - if (fio___http_header_parse_properties(dst, prop, sep)) - return -1; - len = dst->len - old_len; - if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) - return -1; - fio_u2buf16u( - dst->buf + old_len, - ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN)); - } - sep += (*sep == ','); - while (*sep == ' ' || *sep == '\t') - ++sep; - i.buf = sep; - } while (sep < end); } - if (dst->len + 2 > dst->capa) + /* test for duplicate connections */ + if (fio___pubsub_broadcast_connected_get( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + hello[0], + hello[1])) { + FIO_LOG_DEBUG2("(%d) pub/sub-broadcast Prevented duplicate connection!", + fio_io_pid()); return -1; - /* last u16 must be zero (end marker) */ - dst->buf[dst->len++] = 0; - dst->buf[dst->len++] = 0; + } + /* test MAC */ + const void *k = fio___pubsub_secret_key(fio_ltole64(hello[2])); + fio_poly1305_auth(mac, k, NULL, 0, hello, 32); + if (mac[0] != hello[4] || mac[1] != hello[5]) { + FIO_LOG_SECURITY("(%d) pub/sub-broadcast MAC failure - under attack?", + fio_io_pid()); + return -1; + } return 0; } +/* ***************************************************************************** +Letter Listening to Remote Connections - TODO! +***************************************************************************** +*/ +FIO_SFUNC void fio___pubsub_broadcast_on_attach(fio_io_s *io) { + fio___pubsub_broadcast_hello((FIO___PUBSUB_POSTOFFICE.broadcaster = io)); + fio_io_run_every(.fn = fio___pubsub_broadcast_hello_task, + .udata1 = fio_io_dup(io), + .every = + (uint32_t)(1024 | + (1023 & FIO___PUBSUB_POSTOFFICE.uuid.u64[0])), + .repetitions = 2); +} +FIO_SFUNC void fio___pubsub_broadcast_on_close(void *ignr1_, void *ignr2_) { + FIO___PUBSUB_POSTOFFICE.broadcaster = NULL; + (void)ignr1_, (void)ignr2_; +} -SFUNC int fio_http_response_header_parse(fio_http_s *h, - fio_str_info_s *buf_parsed, - fio_str_info_s header_name) { - return fio___http_header_parse(HTTP_HDR_RESPONSE(h), buf_parsed, header_name); +FIO_SFUNC void fio___pubsub_broadcast_on_data(fio_io_s *io) { + uint64_t buf[16]; + struct sockaddr from[2]; + socklen_t from_len = sizeof(from); + ssize_t len; + int should_say_hello = 0; + fio___pubsub_message_s *m = fio___pubsub_message_author( + (fio_publish_args_s){.id = FIO___PUBSUB_POSTOFFICE.uuid.u64[0], + .published = FIO___PUBSUB_POSTOFFICE.uuid.u64[1], + .is_json = FIO___PUBSUB_IDENTIFY}); + + while ((len = recvfrom(fio_io_fd(io), (char *)buf, 128, 0, from, &from_len)) > + 0) { + if (len != 48) { + FIO_LOG_WARNING( + "(%d) pub/sub peer detection received invalid packet (%zu bytes)!", + fio_io_pid(), + len); + continue; + } + if (fio___pubsub_broadcast_hello_validate(buf)) { + FIO_LOG_WARNING( + "(%d) pub/sub peer detection received invalid packet payload!", + fio_io_pid()); + continue; + } + if (fio___pubsub_broadcast_connected_get( + &FIO___PUBSUB_POSTOFFICE.remote_uuids, + buf[0], + buf[1]) == buf[1]) { + FIO_LOG_DDEBUG2("skipping peer connection - already exists"); + continue; /* skip connection, already exists. */ + } + should_say_hello |= 1; + FIO_LOG_DDEBUG2("detected peer, should now connect"); + + /* TODO: fixme! */ + char addr_buf[128]; + if (getnameinfo(from, + from_len, + addr_buf, + 64, + addr_buf + 64, + 64, + (NI_NUMERICHOST | NI_NUMERICHOST))) { + FIO_LOG_ERROR("(%d) couldn't resolve peer address", fio_io_pid()); + continue; + } + int fd = fio_sock_open(addr_buf, + addr_buf + 64, + FIO_SOCK_NONBLOCK | FIO_SOCK_CLIENT | FIO_SOCK_TCP); + if (fd == -1) { + FIO_LOG_ERROR("couldn't connect to cluster peer: %s", strerror(errno)); + continue; + } + fio___pubsub_broadcast_connected_set(&FIO___PUBSUB_POSTOFFICE.remote_uuids, + addr_buf[0], + addr_buf[1]); + fio_io_s *peer = fio_io_attach_fd(fd, + &FIO___PUBSUB_POSTOFFICE.protocol.remote, + NULL, + NULL); + fio___pubsub_message_write2io(peer, m); + FIO_LOG_INFO("(%d) pub/sub-cluster connecting to peer (%zu connections).", + fio_io_pid(), + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); + } + fio___pubsub_message_free(m); + if (should_say_hello) + fio_io_run_every(.fn = fio___pubsub_broadcast_hello_task, + .udata1 = fio_io_dup(io), + .every = + (uint32_t)(1024 | + (1023 & + FIO___PUBSUB_POSTOFFICE.uuid.u64[0]))); } -SFUNC int fio_http_request_header_parse(fio_http_s *h, - fio_str_info_s *buf_parsed, - fio_str_info_s header_name) { - return fio___http_header_parse(HTTP_HDR_REQUEST(h), buf_parsed, header_name); +FIO_SFUNC void fio___pubsub_broadcast_on_incoming(fio_io_s *io) { + int fd; + while ((fd = accept(fio_io_fd(io), NULL, NULL)) != -1) { + FIO_LOG_DDEBUG2("accepting a cluster peer connection"); + fio_io_attach_fd(fd, &FIO___PUBSUB_POSTOFFICE.protocol.remote, NULL, NULL); + } + FIO_LOG_INFO("(cluster) accepted new peer(s) (%zu connections).", + fio___pubsub_broadcast_connected_count( + &FIO___PUBSUB_POSTOFFICE.remote_uuids)); } -/* ***************************************************************************** -Error Handling -***************************************************************************** */ +SFUNC void fio___pubsub_broadcast_on_port(void *port_) { + int16_t port = (int16_t)(uintptr_t)port_; + static fio_io_protocol_s broadcast = { + .on_attach = fio___pubsub_broadcast_on_attach, + .on_data = fio___pubsub_broadcast_on_data, + .on_close = fio___pubsub_broadcast_on_close, + .on_timeout = fio_io_touch, + }; + static fio_io_protocol_s accept_remote = { + .on_data = fio___pubsub_broadcast_on_incoming, + .on_timeout = fio_io_touch, + }; + if (FIO___PUBSUB_POSTOFFICE.secret_is_random) { + FIO_LOG_ERROR( + "Listening to cluster peer connections failed!" + "\n\tUsing a random (non-shared) secret, cannot validate peers."); + return; + } + if (!port || port < 0) + port = 3333; + FIO_STR_INFO_TMP_VAR(url, 32); + url.buf[0] = ':'; + url.len = 1; + fio_string_write_u(&url, NULL, (uint64_t)port); -/** Sends the requested error message and finishes the response. */ -SFUNC int fio_http_send_error_response(fio_http_s *h, size_t status) { - if (!h || h->writer != fio____http_write_start) - return -1; - if (!status || status > 1000) - status = 404; - h->status = (uint32_t)status; - FIO_STR_INFO_TMP_VAR(filename, 127); - /* read static error code file */ - fio_string_write2(&filename, - NULL, - FIO_STRING_WRITE_UNUM(status), - FIO_STRING_WRITE_STR2(".html", 5)); - char *body = fio_bstr_readfile(NULL, filename.buf, 0, 0); - fio_http_write_args_s args = {.buf = body, - .len = fio_bstr_len(body), - .dealloc = (void (*)(void *))fio_bstr_free, - .finish = 1}; - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"content-type", 12), - body ? FIO_STR_INFO2((char *)"text/html", 9) - : FIO_STR_INFO2((char *)"text/plain", 10)); - if (!body) { /* write a short error response (plain text fallback) */ - fio_str_info_s status_str = fio_http_status2str(status); - filename.len = 0; - fio_string_write2(&filename, - NULL, - FIO_STRING_WRITE_STR2("Error ", 6), - FIO_STRING_WRITE_UNUM(status), - FIO_STRING_WRITE_STR2(": ", 2), - FIO_STRING_WRITE_STR_INFO(status_str), - FIO_STRING_WRITE_STR2(".", 1)); - args.buf = filename.buf; - args.len = filename.len; - args.copy = 1; - args.dealloc = NULL; + int fd_udp = + fio_sock_open(NULL, + url.buf + 1, + FIO_SOCK_UDP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER); + FIO_ASSERT(fd_udp != -1, "couldn't open broadcast socket!"); + int fd_tcp = + fio_sock_open(NULL, + url.buf + 1, + FIO_SOCK_TCP | FIO_SOCK_NONBLOCK | FIO_SOCK_SERVER); + FIO_ASSERT(fd_tcp != -1, "couldn't open cluster-peer listening socket!"); + { +#if FIO_OS_WIN + char enabled = 1; +#else + int enabled = 1; +#endif + setsockopt(fd_udp, SOL_SOCKET, SO_BROADCAST, &enabled, sizeof(enabled)); + enabled = 1; + setsockopt(fd_udp, SOL_SOCKET, SO_REUSEADDR, &enabled, sizeof(enabled)); } - fio_http_write FIO_NOOP(h, args); - return 0; + fio_io_attach_fd(fd_udp, &broadcast, port_, NULL); + fio_io_attach_fd(fd_tcp, &accept_remote, NULL, NULL); + return; +} + +/** Auto-peer detection and pub/sub multi-machine clustering using `port`. */ +SFUNC void fio_pubsub_broadcast_on_port(int16_t port) { + fio_state_callback_add(FIO_CALL_PRE_START, + fio___pubsub_broadcast_on_port, + (void *)(uintptr_t)port); } -/* ***************************************************************************** -HTTP Logging -***************************************************************************** */ +/* ***************************************************************************** +Pub/Sub Cleanup +***************************************************************************** */ + +#endif /* FIO_EXTERN_COMPLETE */ +#undef FIO_PUBSUB +#endif /* FIO_PUBSUB */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_HTTP_HANDLE /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + + An HTTP connection Handle helper + +See also: +https://www.rfc-editor.org/rfc/rfc9110.html + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_HTTP_HANDLE) && !defined(H___FIO_HTTP_HANDLE___H) && \ + !defined(FIO___RECURSIVE_INCLUDE) +#define H___FIO_HTTP_HANDLE___H + +/* ***************************************************************************** +HTTP Handle Settings +***************************************************************************** */ +#ifndef FIO_HTTP_EXACT_LOGGING +/** + * By default, facil.io logs the HTTP request cycle using a fuzzy starting and + * ending point for the time stamp. + * + * The fuzzy timestamp includes delays that aren't related to the HTTP request + * and may ignore time passed due to timestamp caching. + * + * On the other hand, `FIO_HTTP_EXACT_LOGGING` collects exact time stamps to + * measure the time it took to process the HTTP request (excluding time spent + * reading / writing the data from the network). + * + * Due to the preference to err on the side of higher performance, fuzzy + * time-stamping is the default. + */ +#define FIO_HTTP_EXACT_LOGGING 0 +#ifndef H___FIO_IO___H +#undef FIO_HTTP_EXACT_LOGGING +#define FIO_HTTP_EXACT_LOGGING 1 +#endif +#endif + +#ifndef FIO_HTTP_BODY_RAM_LIMIT +/** + * The HTTP handle automatically switches between RAM storage and file storage + * once the HTTP body (payload) reaches a certain size. This control this point + * of transition + */ +#define FIO_HTTP_BODY_RAM_LIMIT (1 << 17) +#endif + +#ifndef FIO_HTTP_CACHE_LIMIT +/** Each of the HTTP String Caches will be limited to this String count. */ +#define FIO_HTTP_CACHE_LIMIT 0 /* ((1UL << 6) + (1UL << 5)) */ +#endif + +#ifndef FIO_HTTP_CACHE_STR_MAX_LEN +/** The HTTP handle will avoid caching strings longer than this value. */ +#define FIO_HTTP_CACHE_STR_MAX_LEN (1 << 12) +#endif + +#ifndef FIO_HTTP_CACHE_USES_MUTEX +/** The HTTP cache will use a mutex to allow headers to be set concurrently. */ +#define FIO_HTTP_CACHE_USES_MUTEX 1 +#endif + +#ifndef FIO_HTTP_PRE_CACHE_KNOWN_HEADERS +/** Adds a static cache for common HTTP header names. */ +#define FIO_HTTP_PRE_CACHE_KNOWN_HEADERS 1 +#endif -SFUNC void fio___http_write_pid(fio_str_info_s *dest) { - static int last_pid = 0; - static char buf[64]; - static size_t len = 0; -#ifdef H___FIO_SERVER___H - int pid = fio_srv_pid(); -#else - int pid = fio_thread_getpid(); +#ifndef FIO_HTTP_DEFAULT_INDEX_FILENAME +/** The default file name when a static file response points to a folder. */ +#define FIO_HTTP_DEFAULT_INDEX_FILENAME "index" #endif - if (last_pid != pid) - goto rewrite; -copy: - if (len) - FIO_MEMCPY(dest->buf + dest->len, buf, len); - dest->len += len; - return; -rewrite: - len = 0; - buf[len++] = '['; - if (pid > 0) { - size_t d = fio_digits10u((uint64_t)pid); - fio_ltoa10u(buf + 1, (uint64_t)pid, d); - len += d; - } - buf[len++] = ']'; - last_pid = pid; - goto copy; -} -/** Logs an HTTP (response) to STDOUT. */ -SFUNC void fio_http_write_log(fio_http_s *h) { - FIO_STR_INFO_TMP_VAR(buf, 1023); - intptr_t bytes_sent = h->sent; - uint64_t time_start, time_end, time_proxy = 0; - time_start = h->received_at; - time_end = fio_http_get_timestump(); - fio_str_info_s date = fio_http_log_time(time_end / FIO___HTTP_TIME_DIV); - fio_string_write_s to_write[16] = { - FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->method)), - FIO_STRING_WRITE_STR2((const char *)" ", 1), - FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->path)), - FIO_STRING_WRITE_STR2((const char *)" ", 1), - FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->version)), - FIO_STRING_WRITE_STR2((const char *)"\" ", 2), - FIO_STRING_WRITE_NUM(h->status), - FIO_STRING_WRITE_STR2(" ", 1), - ((bytes_sent > 0) ? (FIO_STRING_WRITE_UNUM(bytes_sent)) - : (FIO_STRING_WRITE_STR2((const char *)"---", 3))), - FIO_STRING_WRITE_STR2((const char *)" ", 1), - FIO_STRING_WRITE_NUM(time_end - time_start), - FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT "\r\n"), 4), - }; - if (FIO_HTTP_LOG_X_REQUEST_START) { - /* log request wait time using x-request-start header */ - fio_str_info_s xstart = - fio_http_request_header(h, - FIO_STR_INFO2((char *)"x-request-start", 15), - 0); - unsigned step = - (xstart.len > 1 && (xstart.buf[0] | 32) == 't' && xstart.buf[1] == '='); - step <<= 1; - xstart.buf += step; - xstart.len -= step; - time_proxy = fio_atol(&xstart.buf); - time_proxy *= (FIO___HTTP_TIME_DIV / 1000); /* assumes info in ms */ - time_proxy = time_start - time_proxy; - if (time_proxy < (512 * FIO___HTTP_TIME_DIV)) { /* was ms? */ - to_write[11] = - FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT " (wait "), - 9); - to_write[12] = FIO_STRING_WRITE_NUM(time_proxy); - to_write[13] = - FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT ")\r\n"), - 5); - } - } - /* Write log line to buffer */ - fio___http_write_pid(&buf); - buf.buf[buf.len++] = ' '; - fio_http_from(&buf, h); - FIO_MEMCPY(buf.buf + buf.len, " - - ", 5); - FIO_MEMCPY(buf.buf + buf.len + 5, date.buf, date.len); - buf.len += date.len + 6; - buf.buf[buf.len++] = ' '; - buf.buf[buf.len++] = '\"'; - fio_string_write2 FIO_NOOP(&buf, NULL, to_write); +#ifndef FIO_HTTP_STATIC_FILE_COMPLETION +/** Attempts to auto-complete static file paths with missing extensions. */ +#define FIO_HTTP_STATIC_FILE_COMPLETION 1 +#endif - if (buf.buf[buf.len - 1] != '\n') - buf.buf[buf.len++] = '\n'; /* log was truncated, data too long */ +#ifndef FIO_HTTP_LOG_X_REQUEST_START +#define FIO_HTTP_LOG_X_REQUEST_START 1 +#endif - /* Write log line to STDOUT */ - fwrite(buf.buf, 1, buf.len, stdout); - h->received_at = time_end; -} +#ifndef FIO_HTTP_ENFORCE_LOWERCASE_HEADERS +/** If true, the HTTP handle will copy input header names to lower case. */ +#define FIO_HTTP_ENFORCE_LOWERCASE_HEADERS 0 +#endif /* ***************************************************************************** -ETag helper +HTTP Handle Type ***************************************************************************** */ -/** Returns true (1) if the ETag response matches an if-none-match request. */ -SFUNC int fio_http_etag_is_match(fio_http_s *h) { - fio_str_info_s method = fio_keystr_info(&h->method); - if ((method.len < 3) | (method.len > 4)) - return 0; - if (!(((method.buf[0] | 32) == 'g') & ((method.buf[1] | 32) == 'e') & - ((method.buf[2] | 32) == 't')) && - !(((method.buf[0] | 32) == 'h') & ((method.buf[1] | 32) == 'e') & - ((method.buf[2] | 32) == 'a') & ((method.buf[3] | 32) == 'd'))) - return 0; - fio_str_info_s etag = fio___http_hmap_get2(HTTP_HDR_RESPONSE(h), - FIO_STR_INFO2((char *)"etag", 4), - 0); - if (!etag.len) - return 0; - fio_str_info_s cond = - fio___http_hmap_get2(HTTP_HDR_REQUEST(h), - FIO_STR_INFO2((char *)"if-none-match", 13), - 0); - if (!cond.len) - return 0; - char *end = cond.buf + cond.len; - for (;;) { - cond.buf += (cond.buf[0] == ','); - while (cond.buf[0] == ' ') - ++cond.buf; - if (cond.buf > end || (size_t)(end - cond.buf) < (size_t)etag.len) - return 0; - if (FIO_MEMCMP(cond.buf, etag.buf, etag.len)) { - cond.buf = (char *)FIO_MEMCHR(cond.buf, ',', end - cond.buf); - if (!cond.buf) - return 0; - continue; - } - return 1; - } -} +/** + * The HTTP Handle type. + * + * Note that the type is NOT designed to be thread-safe. + */ +typedef struct fio_http_s fio_http_s; + +/** + * The HTTP Controller points to all the callbacks required by the HTTP Handler. + * + * This allows the HTTP Handler to be somewhat protocol agnostic. + * + * Note: if the controller callbacks aren't thread-safe, than the `http_write` + * function MUST NOT be called from any thread except the thread that the + * controller is expecting. + */ +typedef struct fio_http_controller_s fio_http_controller_s; /* ***************************************************************************** -Param Parsing (TODO! - parse query, parse mime/multipart parse text/json) +Constructor / Destructor ***************************************************************************** */ -/* ***************************************************************************** +/** Create a new fio_http_s handle. */ +SFUNC fio_http_s *fio_http_new(void); +/** Creates a copy of an existing handle, copying only its request data. */ +SFUNC fio_http_s *fio_http_new_copy_request(fio_http_s *old); - TODO WIP Marker!!! +/** Reduces an fio_http_s handle's reference count or frees it. */ +SFUNC void fio_http_free(fio_http_s *); + +/** Increases an fio_http_s handle's reference count. */ +SFUNC fio_http_s *fio_http_dup(fio_http_s *); + +/** Destroyed the HTTP handle object, freeing all allocated resources. */ +SFUNC fio_http_s *fio_http_destroy(fio_http_s *h); +/** Collects an updated timestamp for logging purposes. */ +SFUNC void fio_http_start_time_set(fio_http_s *); + +/** Clears any response data. */ +SFUNC fio_http_s *fio_http_clear_response(fio_http_s *h, bool clear_body); +/* ***************************************************************************** +Opaque User and Controller Data ***************************************************************************** */ +/** Gets the opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata(fio_http_s *); + +/** Sets the opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata_set(fio_http_s *, void *); + +/** Gets the second opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata2(fio_http_s *); + +/** Sets a second opaque user pointer associated with the HTTP handle. */ +FIO_IFUNC void *fio_http_udata2_set(fio_http_s *, void *); + +/** Gets the HTTP Controller associated with the HTTP handle. */ +FIO_IFUNC fio_http_controller_s *fio_http_controller(fio_http_s *h); + +/** Gets the HTTP Controller associated with the HTTP handle. */ +FIO_IFUNC fio_http_controller_s *fio_http_controller_set( + fio_http_s *h, + fio_http_controller_s *controller); + +/** Returns the existing controller data (`void *` pointer). */ +FIO_IFUNC void *fio_http_cdata(fio_http_s *h); + +/** Sets a new controller data (`void *` pointer). */ +FIO_IFUNC void *fio_http_cdata_set(fio_http_s *h, void *cdata); + /* ***************************************************************************** -Static file helper +Data associated with the Request (usually set by the HTTP protocol) ***************************************************************************** */ +/** Gets the status associated with the HTTP handle (response). */ +SFUNC size_t fio_http_status(fio_http_s *); + +/** Sets the status associated with the HTTP handle (response). */ +SFUNC size_t fio_http_status_set(fio_http_s *, size_t status); + +/** Gets the method information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_method(fio_http_s *); + +/** Sets the method information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_method_set(fio_http_s *, fio_str_info_s); + +/** Gets the path information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_path(fio_http_s *); + +/** Sets the path information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_path_set(fio_http_s *, fio_str_info_s); + +/** Gets the query information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_query(fio_http_s *); + +/** Sets the query information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_query_set(fio_http_s *, fio_str_info_s); + +/** Gets the version information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_version(fio_http_s *); + +/** Sets the version information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_version_set(fio_http_s *, fio_str_info_s); + /** - * Attempts to send a static file from the `root` folder. On success the - * response is complete and 0 is returned. Otherwise returns -1. + * Gets the header information associated with the HTTP handle. + * + * Since more than a single value may be associated with a header name, the + * index may be used to collect subsequent values. + * + * An empty value is returned if no header value is available (or index is + * exceeded). */ -SFUNC int fio_http_static_file_response(fio_http_s *h, - fio_str_info_s rt, - fio_str_info_s fnm, - size_t max_age) { - int fd = -1; - /* combine public folder with path to get file name */ - fio_str_info_s mime_type = {0}; - FIO_STR_INFO_TMP_VAR(etag, 31); - FIO_STR_INFO_TMP_VAR(filename, 4095); - { /* test for HEAD and OPTIONS requests */ - fio_str_info_s m = fio_keystr_info(&h->method); - if ((m.len == 7 && (fio_buf2u64u(m.buf) | 0x2020202020202020ULL) == - (fio_buf2u64u("options") | 0x2020202020202020ULL))) - goto file_not_found; - } - rt.len -= ((rt.len > 0) && (fnm.len > 0 && fnm.buf[0] == '/') && - (rt.buf[rt.len - 1] == '/' || - rt.buf[rt.len - 1] == FIO_FOLDER_SEPARATOR)); - fio_string_write(&filename, NULL, rt.buf, rt.len); - fio_string_write_url_dec(&filename, NULL, fnm.buf, fnm.len); - if (fio_filename_is_unsafe_url(filename.buf)) - goto file_not_found; +SFUNC fio_str_info_s fio_http_request_header(fio_http_s *, + fio_str_info_s name, + size_t index); - { /* Test for incomplete file name */ - size_t file_type = fio_filename_type(filename.buf); -#if defined(S_IFDIR) && defined(FIO_HTTP_DEFAULT_INDEX_FILENAME) - if (file_type == S_IFDIR) { - filename.len -= (filename.buf[filename.len - 1] == '/' || - filename.buf[filename.len - 1] == FIO_FOLDER_SEPARATOR); -#if FIO_HTTP_STATIC_FILE_COMPLETION - fio_string_write(&filename, - NULL, - "/" FIO_HTTP_DEFAULT_INDEX_FILENAME, - sizeof(FIO_HTTP_DEFAULT_INDEX_FILENAME)); - file_type = 0; -#else - fio_string_write( - &filename, - NULL, /* note that sizeof will count NUL, so we skip 1 char: */ - "/" FIO_HTTP_DEFAULT_INDEX_FILENAME ".html", - sizeof(FIO_HTTP_DEFAULT_INDEX_FILENAME ".html")); - file_type = fio_filename_type(filename.buf); -#endif /* FIO_HTTP_STATIC_FILE_COMPLETION */ - } -#endif /* S_IFDIR */ -#if FIO_HTTP_STATIC_FILE_COMPLETION - const fio_buf_info_s extensions[] = {FIO_BUF_INFO1((char *)".html"), - FIO_BUF_INFO1((char *)".htm"), - FIO_BUF_INFO1((char *)".txt"), - FIO_BUF_INFO1((char *)".md"), - FIO_BUF_INFO0}; - const fio_buf_info_s *pext = extensions; - while (!file_type) { - fio_string_write(&filename, NULL, pext->buf, pext->len); - file_type = fio_filename_type(filename.buf); - if (file_type) - break; - filename.len -= pext->len; - ++pext; - if (!pext->buf) - goto file_not_found; - } - switch (file_type) { - case S_IFREG: break; -#ifdef S_IFLNK - case S_IFLNK: break; -#endif - default: goto file_not_found; - } -#else /* FIO_HTTP_STATIC_FILE_COMPLETION */ - if (!file_type) - goto file_not_found; -#endif /* FIO_HTTP_STATIC_FILE_COMPLETION */ - } - { - /* find mime type if registered */ - char *end = filename.buf + filename.len; - char *ext = end; - do { - --ext; - } while (ext[0] != '.' && ext[0] != '/'); - if ((ext++)[0] == '.') { - mime_type = fio_http_mimetype(ext, end - ext); - if (!mime_type.len) - FIO_LOG_WARNING("missing mime-type for extension %s (not registered).", - ext); - } - } - { - fio_str_info_s ac = - fio_http_request_header(h, - FIO_STR_INFO2((char *)"accept-encoding", 15), - 0); - if (!ac.len) - goto accept_encoding_header_test_done; - struct { - char *value; - fio_buf_info_s ext; - } options[] = {{(char *)"gzip", FIO_BUF_INFO2((char *)".gz", 3)}, - {(char *)"br", FIO_BUF_INFO2((char *)".br", 3)}, - {(char *)"deflate", FIO_BUF_INFO2((char *)".zip", 4)}, - {NULL}}; - for (size_t i = 0; options[i].value; ++i) { - if (!strstr(ac.buf, options[i].value)) - continue; - fio_string_write(&filename, NULL, options[i].ext.buf, options[i].ext.len); - if (!fio_filename_type(filename.buf)) { - filename.len -= options[i].ext.len; - filename.buf[filename.len] = 0; - continue; - } - fio_http_response_header_set( - h, - FIO_STR_INFO2((char *)"vary", 4), - FIO_STR_INFO2((char *)"accept-encoding", 15)); - fio_http_response_header_set( - h, - FIO_STR_INFO2((char *)"content-encoding", 16), - FIO_STR_INFO1(options[i].value)); - break; - } - } +/** + * Returns the number of headers named `name` that were received. + * + * If `name` buffer is `NULL`, returns the number of unique headers (not the + * number of unique values). + */ +SFUNC size_t fio_http_request_header_count(fio_http_s *, fio_str_info_s name); -accept_encoding_header_test_done: - /* attempt to open file */ - fd = fio_filename_open(filename.buf, O_RDONLY); - if (fd == -1) - goto file_not_found; +/** Sets the header information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_request_header_set(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); - { /* test / validate etag */ - struct stat stt; - if (fstat(fd, &stt)) - goto file_not_found; - uint64_t etag_hash = fio_risky_hash(&stt, sizeof(stt), 0); - fio_string_write_hex(&etag, NULL, etag_hash); - fio_http_response_header_set(h, FIO_STR_INFO2((char *)"etag", 4), etag); - filename.len = 0; - filename.len = fio_time2rfc7231(filename.buf, stt.st_mtime); - fio_http_response_header_set(h, - FIO_STR_INFO1((char *)"last-modified"), - filename); - if (max_age) { - filename.len = 0; - fio_string_write2(&filename, - NULL, - FIO_STRING_WRITE_STR2("max-age=", 8), - FIO_STRING_WRITE_UNUM(max_age)); - fio_http_response_header_set(h, - FIO_STR_INFO1((char *)"cache-control"), - filename); - } - filename.len = stt.st_size; - filename.capa = 0; - if (fio___http_response_etag_if_none_match(h)) - return 0; - } - /* Note: at this point filename.len holds the length of the file */ +/** Sets the header information associated with the HTTP handle. */ +SFUNC fio_str_info_s +fio_http_request_header_set_if_missing(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); - /* test for range requests. */ - { - /* test / validate range requests */ - fio_str_info_s rng = - fio_http_request_header(h, FIO_STR_INFO2((char *)"range", 5), 0); - if (!rng.len) - goto range_request_review_finished; - { - fio_str_info_s ifrng = - fio_http_request_header(h, FIO_STR_INFO2((char *)"if-range", 8), 0); - if (ifrng.len && !FIO_STR_INFO_IS_EQ(ifrng, etag)) - goto range_request_review_finished; - } - if (rng.len < 7 || fio_buf2u32u(rng.buf) != fio_buf2u32u("byte") || - fio_buf2u16u(rng.buf + 4) != fio_buf2u16u("s=")) - goto range_request_review_finished; - const size_t file_length = filename.len; - char *ipos = rng.buf + 6; - size_t start_range = fio_atol10u(&ipos); - if (ipos == rng.buf + 6) - start_range = (size_t)-1; - if (*ipos != '-') - goto range_request_review_finished; - ++ipos; - size_t end_range = fio_atol10u(&ipos); - if (end_range > file_length) - goto range_request_review_finished; - if (!end_range) - end_range = file_length - 1; - if (start_range > end_range) { - start_range = file_length - end_range; - end_range = file_length - 1; - } - if (!start_range && end_range + 1 == file_length) - goto range_request_review_finished; - /* update response headers and info */ - h->status = 206; - filename.len = 0; - filename.capa = 1024; - fio_string_write2(&filename, - NULL, - FIO_STRING_WRITE_STR2("bytes ", 6), - FIO_STRING_WRITE_UNUM(start_range), - FIO_STRING_WRITE_STR2("-", 1), - FIO_STRING_WRITE_UNUM((end_range)), - FIO_STRING_WRITE_STR2("/", 1), - FIO_STRING_WRITE_UNUM(file_length)); - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"content-range", 13), - filename); - filename.len = (end_range - start_range) + 1; - filename.capa = start_range; - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"etag", 4), - FIO_STR_INFO2(NULL, 0)); - } +/** Adds to the header information associated with the HTTP handle. */ +SFUNC fio_str_info_s fio_http_request_header_add(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); -range_request_review_finished: - /* allow interrupted downloads to resume */ - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"accept-ranges", 13), - FIO_STR_INFO2((char *)"bytes", 5)); - /* test for HEAD requests */ - { - fio_str_info_s m = fio_keystr_info(&h->method); - if ((m.len == 4 && (fio_buf2u32u(m.buf) | 0x20202020UL) == - (fio_buf2u32u("head") | 0x20202020UL))) - goto head_request; - } +/** + * Iterates through all request headers (except cookies!). + * + * A non-zero return will stop iteration. + * + * Returns the number of iterations performed. If `callback` is `NULL`, returns + * the number of headers available (multi-value headers are counted as 1). + * */ +SFUNC size_t fio_http_request_header_each(fio_http_s *, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata); - /* finish up (set mime type and send file) */ - if (mime_type.len) - fio_http_response_header_set(h, - FIO_STR_INFO2((char *)"content-type", 12), - mime_type); - { /* send response (avoid macro for C++ compatibility) */ - fio_http_write_args_s args = { - .len = filename.len, /* now holds body length */ - .offset = filename.capa, /* now holds starting offset */ - .fd = fd, - .finish = 1}; - fio_http_write FIO_NOOP(h, args); - } - return 0; +/** Gets the body (payload) length associated with the HTTP handle. */ +SFUNC size_t fio_http_body_length(fio_http_s *); -file_not_found: - if (fd != -1) - close(fd); - return -1; +/** Adjusts the body's reading position. Negative values start at the end. */ +SFUNC size_t fio_http_body_seek(fio_http_s *, ssize_t pos); -head_request: - /* TODO! HEAD responses should close?. */ - if (fd != -1) - close(fd); - { - fio_http_write_args_s args = {.finish = 1}; - fio_http_write FIO_NOOP(h, args); - } - return 0; -} +/** Reads up to `length` of data from the body, returns nothing on EOF. */ +SFUNC fio_str_info_s fio_http_body_read(fio_http_s *, size_t length); + +/** + * Reads from the body until finding `token`, reaching `limit` or EOF. + * + * Note: `limit` is ignored if zero or if the remaining data is lower than + * limit. + */ +SFUNC fio_str_info_s fio_http_body_read_until(fio_http_s *, + char token, + size_t limit); + +/** Allocates a body (payload) of (at least) the `expected_length`. */ +SFUNC void fio_http_body_expect(fio_http_s *, size_t expected_length); + +/** Writes `data` to the body (payload) associated with the HTTP handle. */ +SFUNC void fio_http_body_write(fio_http_s *, const void *data, size_t len); + +/** + * If the body is stored in a temporary file, returns the file's handle. + * + * Otherwise returns -1. + */ +SFUNC int fio_http_body_fd(fio_http_s *); /* ***************************************************************************** -Status Strings +Cookies ***************************************************************************** */ -/** Returns a human readable string related to the HTTP status number. */ -SFUNC fio_str_info_s fio_http_status2str(size_t status) { - fio_str_info_s r = {0}; -#define HTTP_RETURN_STATUS(str) \ - do { \ - r.len = FIO_STRLEN(str); \ - r.buf = (char *)str; \ - return r; \ - } while (0); - switch (status) { - // clang-format off - case 100: HTTP_RETURN_STATUS("Continue"); - case 101: HTTP_RETURN_STATUS("Switching Protocols"); - case 102: HTTP_RETURN_STATUS("Processing"); - case 103: HTTP_RETURN_STATUS("Early Hints"); - case 110: HTTP_RETURN_STATUS("Response is Stale"); /* caching code*/ - case 111: HTTP_RETURN_STATUS("Re-validation Failed"); /* caching code*/ - case 112: HTTP_RETURN_STATUS("Disconnected Operation"); /* caching code*/ - case 113: HTTP_RETURN_STATUS("Heuristic Expiration"); /* caching code*/ - case 199: HTTP_RETURN_STATUS("Miscellaneous Warning"); /* caching code*/ - case 200: HTTP_RETURN_STATUS("OK"); - case 201: HTTP_RETURN_STATUS("Created"); - case 202: HTTP_RETURN_STATUS("Accepted"); - case 203: HTTP_RETURN_STATUS("Non-Authoritative Information"); - case 204: HTTP_RETURN_STATUS("No Content"); - case 205: HTTP_RETURN_STATUS("Reset Content"); - case 206: HTTP_RETURN_STATUS("Partial Content"); - case 207: HTTP_RETURN_STATUS("Multi-Status"); - case 208: HTTP_RETURN_STATUS("Already Reported"); - case 214: HTTP_RETURN_STATUS("Transformation Applied"); /* caching code*/ - case 218: HTTP_RETURN_STATUS("This is fine (Apache Web Server)"); /* unofficial */ - case 226: HTTP_RETURN_STATUS("IM Used"); - case 299: HTTP_RETURN_STATUS("Miscellaneous Persistent Warning"); /* caching code*/ - case 300: HTTP_RETURN_STATUS("Multiple Choices"); - case 301: HTTP_RETURN_STATUS("Moved Permanently"); - case 302: HTTP_RETURN_STATUS("Found"); - case 303: HTTP_RETURN_STATUS("See Other"); - case 304: HTTP_RETURN_STATUS("Not Modified"); - case 305: HTTP_RETURN_STATUS("Use Proxy"); - case 307: HTTP_RETURN_STATUS("Temporary Redirect"); - case 308: HTTP_RETURN_STATUS("Permanent Redirect"); - case 400: HTTP_RETURN_STATUS("Bad Request"); - case 401: HTTP_RETURN_STATUS("Unauthorized"); - case 402: HTTP_RETURN_STATUS("Payment Required"); - case 403: HTTP_RETURN_STATUS("Forbidden"); - case 404: HTTP_RETURN_STATUS("Not Found"); - case 405: HTTP_RETURN_STATUS("Method Not Allowed"); - case 406: HTTP_RETURN_STATUS("Not Acceptable"); - case 407: HTTP_RETURN_STATUS("Proxy Authentication Required"); - case 408: HTTP_RETURN_STATUS("Request Timeout"); - case 409: HTTP_RETURN_STATUS("Conflict"); - case 410: HTTP_RETURN_STATUS("Gone"); - case 411: HTTP_RETURN_STATUS("Length Required"); - case 412: HTTP_RETURN_STATUS("Precondition Failed"); - case 413: HTTP_RETURN_STATUS("Content Too Large"); - case 414: HTTP_RETURN_STATUS("URI Too Long"); - case 415: HTTP_RETURN_STATUS("Unsupported Media Type"); - case 416: HTTP_RETURN_STATUS("Range Not Satisfiable"); - case 417: HTTP_RETURN_STATUS("Expectation Failed"); - case 418: HTTP_RETURN_STATUS("I am a Teapot"); /* April Fool's Day, 1998 */ - case 419: HTTP_RETURN_STATUS("Page Expired (Laravel Framework)"); /* unofficial */ - case 420: HTTP_RETURN_STATUS("Enhance Your Calm (Twitter) - Method Failure (Spring Framework)"); /* unofficial */ - case 421: HTTP_RETURN_STATUS("Misdirected Request"); - case 422: HTTP_RETURN_STATUS("Unprocessable Content"); - case 423: HTTP_RETURN_STATUS("Locked"); - case 424: HTTP_RETURN_STATUS("Failed Dependency"); - case 425: HTTP_RETURN_STATUS("Too Early"); - case 426: HTTP_RETURN_STATUS("Upgrade Required"); - case 427: HTTP_RETURN_STATUS("Unassigned"); - case 428: HTTP_RETURN_STATUS("Precondition Required"); - case 429: HTTP_RETURN_STATUS("Too Many Requests"); - case 430: HTTP_RETURN_STATUS("Request Header Fields Too Large (Shopify)"); /* unofficial */ - case 431: HTTP_RETURN_STATUS("Request Header Fields Too Large"); - case 444: HTTP_RETURN_STATUS("No Response"); /* nginx code */ - case 450: HTTP_RETURN_STATUS("Blocked by Windows Parental Controls (Microsoft)"); /* unofficial */ - case 451: HTTP_RETURN_STATUS("Unavailable For Legal Reasons"); - case 494: HTTP_RETURN_STATUS("Request header too large"); /* nginx code */ - case 495: HTTP_RETURN_STATUS("SSL Certificate Error"); /* nginx code */ - case 496: HTTP_RETURN_STATUS("SSL Certificate Required"); /* nginx code */ - case 497: HTTP_RETURN_STATUS("HTTP Request Sent to HTTPS Port"); /* nginx code */ - case 498: HTTP_RETURN_STATUS("Invalid Token (Esri)"); /* unofficial */ - case 499: HTTP_RETURN_STATUS("Client Closed Request"); /* nginx code */ - case 500: HTTP_RETURN_STATUS("Internal Server Error"); - case 501: HTTP_RETURN_STATUS("Not Implemented"); - case 502: HTTP_RETURN_STATUS("Bad Gateway"); - case 503: HTTP_RETURN_STATUS("Service Unavailable"); - case 504: HTTP_RETURN_STATUS("Gateway Timeout"); - case 505: HTTP_RETURN_STATUS("HTTP Version Not Supported"); - case 506: HTTP_RETURN_STATUS("Variant Also Negotiates"); - case 507: HTTP_RETURN_STATUS("Insufficient Storage"); - case 508: HTTP_RETURN_STATUS("Loop Detected"); - case 509: HTTP_RETURN_STATUS("Bandwidth Limit Exceeded (Apache Web Server/cPanel)"); /* unofficial */ - case 510: HTTP_RETURN_STATUS("Not Extended"); - case 511: HTTP_RETURN_STATUS("Network Authentication Required"); - case 529: HTTP_RETURN_STATUS("Site is overloaded (Qualys)"); /* unofficial */ - case 530: HTTP_RETURN_STATUS("Site is frozen (Pantheon web)"); /* unofficial */ - case 598: HTTP_RETURN_STATUS("Network read timeout error"); /* unofficial */ - // clang-format on - } - HTTP_RETURN_STATUS("Unknown"); -#undef HTTP_RETURN_STATUS -} +/** + * Possible values for the `same_site` property in the cookie settings. + * + * See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie + */ +typedef enum fio_http_cookie_same_site_e { + /** allow the browser to dictate this property */ + FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT = 0, + /** The browser sends the cookie with cross-site and same-site requests. */ + FIO_HTTP_COOKIE_SAME_SITE_NONE, + /** + * The cookie is withheld on cross-site sub-requests. + * + * The cookie is sent when a user navigates to the URL from an external + * site. + */ + FIO_HTTP_COOKIE_SAME_SITE_LAX, + /** The browser sends the cookie only for same-site requests. */ + FIO_HTTP_COOKIE_SAME_SITE_STRICT, +} fio_http_cookie_same_site_e; + +/** + * This is a helper for setting cookie data. + * + * This struct is used together with the `fio_http_cookie_set` macro. i.e.: + * + * fio_http_set_cookie(h, + * .name = FIO_STR_INFO1("my_cookie"), + * .value = FIO_STR_INFO1("data")); + * + */ +typedef struct fio_http_cookie_args_s { + /** The cookie's name. */ + fio_str_info_s name; + /** The cookie's value (leave blank to delete cookie). */ + fio_str_info_s value; + /** The cookie's domain (optional). */ + fio_str_info_s domain; + /** The cookie's path (optional). */ + fio_str_info_s path; + /** Max Age (how long should the cookie persist), in seconds (0 == session).*/ + int max_age; + /** SameSite value. */ + fio_http_cookie_same_site_e same_site; + /** Limit cookie to secure connections.*/ + unsigned secure : 1; + /** Limit cookie to HTTP (intended to prevent JavaScript access/hijacking).*/ + unsigned http_only : 1; + /** + * Set the Partitioned (third party) cookie flag: + * https://developer.mozilla.org/en-US/docs/Web/Privacy/Partitioned_cookies + */ + unsigned partitioned : 1; +} fio_http_cookie_args_s; + +/** + * Sets a response cookie. + * + * Returns -1 on error and 0 on success. + * + * Note: Long cookie names and long cookie values will be considered a security + * violation and an error will be returned. Many browsers and proxies impose + * limits on headers and cookies, cookies often limited to 4Kb in total for both + * name and value. + */ +SFUNC int fio_http_cookie_set(fio_http_s *h, fio_http_cookie_args_s); + +/** Named arguments helper. See fio_http_cookie_args_s for details. */ +#define fio_http_cookie_set(http___handle, ...) \ + fio_http_cookie_set((http___handle), (fio_http_cookie_args_s){__VA_ARGS__}) + +/** Returns a cookie value (either received of newly set), if any. */ +SFUNC fio_str_info_s fio_http_cookie(fio_http_s *, + const char *name, + size_t name_len); + +/** Iterates through all cookies. A non-zero return will stop iteration. */ +SFUNC size_t fio_http_cookie_each(fio_http_s *, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata); + +/** + * Iterates through all response set cookies. + * + * A non-zero return value from the callback will stop iteration. + */ +SFUNC size_t +fio_http_set_cookie_each(fio_http_s *h, + int (*callback)(fio_http_s *, + fio_str_info_s set_cookie_header, + fio_str_info_s value, + void *udata), + void *udata); /* ***************************************************************************** -MIME File Type Helpers +Responding to an HTTP event. ***************************************************************************** */ -typedef struct { - uint64_t ext; - uint16_t len; - char mime[118]; /* all together 128 bytes per node */ -} fio___http_mime_info_s; +/** Returns true if no HTTP headers / data was sent (a clean slate). */ +SFUNC int fio_http_is_clean(fio_http_s *); -#define FIO___HTTP_MIME_IS_VALID(o) ((o)->ext != 0) -#define FIO___HTTP_MIME_CMP(a, b) ((a)->ext == (b)->ext) -#define FIO___HTTP_MIME_HASH(o) fio_risky_num(((o)->ext), 0) +/** Returns true if the HTTP handle's response was sent. */ +SFUNC int fio_http_is_finished(fio_http_s *); -#undef FIO_TYPEDEF_IMAP_REALLOC -#define FIO_TYPEDEF_IMAP_REALLOC(ptr, old_size, new_size, copy_len) \ - realloc(ptr, new_size) -#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE -#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE 0 -#undef FIO_TYPEDEF_IMAP_FREE -#define FIO_TYPEDEF_IMAP_FREE(ptr, len) free(ptr) +/** Returns true if the HTTP handle's response is streaming. */ +SFUNC int fio_http_is_streaming(fio_http_s *); -FIO_TYPEDEF_IMAP_ARRAY(fio___http_mime_map, - fio___http_mime_info_s, - uint32_t, - FIO___HTTP_MIME_HASH, - FIO___HTTP_MIME_CMP, - FIO___HTTP_MIME_IS_VALID) +/** Returns true if the HTTP connection was (or should have been) upgraded. */ +SFUNC int fio_http_is_upgraded(fio_http_s *h); -static fio___http_mime_map_s FIO___HTTP_MIMETYPES; -#undef FIO___HTTP_MIME_IS_VALID -#undef FIO___HTTP_MIME_CMP -#undef FIO___HTTP_MIME_HASH +/** Returns true if the HTTP handle refers to a WebSocket connection. */ +SFUNC int fio_http_is_websocket(fio_http_s *); -#undef FIO_TYPEDEF_IMAP_REALLOC -#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC -#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE -#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE -#undef FIO_TYPEDEF_IMAP_FREE -#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE +/** Returns true if the HTTP handle refers to an EventSource connection. */ +SFUNC int fio_http_is_sse(fio_http_s *); -/** Registers a Mime-Type to be associated with the file extension. */ -SFUNC int fio_http_mimetype_register(char *file_ext, - size_t file_ext_len, - fio_str_info_s mime_type) { - fio___http_mime_info_s tmp, *old; - if (file_ext_len > 7 || mime_type.len > 117) - return -1; - tmp.ext = 0; - FIO_MEMCPY(&tmp.ext, file_ext, file_ext_len); - if (!mime_type.len) - goto remove_mime; - FIO_MEMCPY(&tmp.mime, mime_type.buf, mime_type.len); - tmp.len = mime_type.len; - tmp.mime[mime_type.len] = 0; - old = fio___http_mime_map_get(&FIO___HTTP_MIMETYPES, tmp); - if (old && old->len == tmp.len && !FIO_MEMCMP(old->mime, tmp.mime, tmp.len)) { - FIO_LOG_WARNING("mime-type collision: %.*s was %s, now %s", - (int)file_ext_len, - file_ext, - old->mime, - tmp.mime); - } - fio___http_mime_map_set(&FIO___HTTP_MIMETYPES, tmp, 1); - return 0; +/** Returns true if handle is in the process of freeing itself. */ +SFUNC int fio_http_is_freeing(fio_http_s *); -remove_mime: - return fio___http_mime_map_remove(&FIO___HTTP_MIMETYPES, tmp); -} +/** + * Gets the header information associated with the HTTP handle. + * + * Since more than a single value may be associated with a header name, the + * index may be used to collect subsequent values. + * + * An empty value is returned if no header value is available (or index is + * exceeded). + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s fio_http_response_header(fio_http_s *, + fio_str_info_s name, + size_t index); +/** + * Returns the number of headers named `name` in the response. + * + * If `name` buffer is `NULL`, returns the number of unique headers (not the + * number of unique values). + */ +SFUNC size_t fio_http_response_header_count(fio_http_s *, fio_str_info_s name); -/** Finds the Mime-Type associated with the file extension (if registered). */ -SFUNC fio_str_info_s fio_http_mimetype(char *file_ext, size_t file_ext_len) { - fio_str_info_s r = {0}; - fio___http_mime_info_s tmp, *val; - tmp.ext = 0; - FIO_MEMCPY(&tmp.ext, file_ext, file_ext_len); - val = fio___http_mime_map_get(&FIO___HTTP_MIMETYPES, tmp); - if (!val) - return r; - r.len = val->len; - r.buf = val->mime; - return r; -} +/** + * Sets the header information associated with the HTTP handle. + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s fio_http_response_header_set(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); +/** + * Sets the header information associated with the HTTP handle. + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s +fio_http_response_header_set_if_missing(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); -#define REGISTER_MIME(ext, type) \ - fio_http_mimetype_register((char *)ext, \ - sizeof(ext) - 1, \ - FIO_STR_INFO2((char *)type, sizeof(type) - 1)) +/** + * Adds to the header information associated with the HTTP handle. + * + * If the response headers were already sent, the returned value is always + * empty. + */ +SFUNC fio_str_info_s fio_http_response_header_add(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value); -/** Registers known mime-types that aren't often used by Web Servers. */ -FIO_SFUNC void fio_http_mime_register_essential(void) { - /* clang-format off */ - REGISTER_MIME("3ds", "image/x-3ds"); - REGISTER_MIME("3g2", "video/3gpp"); - REGISTER_MIME("3gp", "video/3gpp"); - REGISTER_MIME("7z", "application/x-7z-compressed"); - REGISTER_MIME("aac", "audio/aac"); - REGISTER_MIME("abw", "application/x-abiword"); - REGISTER_MIME("aif", "audio/x-aiff"); - REGISTER_MIME("aifc", "audio/x-aiff"); - REGISTER_MIME("aiff", "audio/x-aiff"); - REGISTER_MIME("arc", "application/x-freearc"); - REGISTER_MIME("atom", "application/atom+xml"); - REGISTER_MIME("avi", "video/x-msvideo"); - REGISTER_MIME("avif", "image/avif"); - REGISTER_MIME("azw", "application/vnd.amazon.ebook"); - REGISTER_MIME("bin", "application/octet-stream"); - REGISTER_MIME("bmp", "image/bmp"); - REGISTER_MIME("bz", "application/x-bzip"); - REGISTER_MIME("bz2", "application/x-bzip2"); - REGISTER_MIME("cda", "application/x-cdf"); - REGISTER_MIME("csh", "application/x-csh"); - REGISTER_MIME("css", "text/css"); - REGISTER_MIME("csv", "text/csv"); - REGISTER_MIME("dmg", "application/x-apple-diskimage"); - REGISTER_MIME("doc", "application/msword"); - REGISTER_MIME("docx", "application/" "vnd.openxmlformats-officedocument.wordprocessingml.document"); - REGISTER_MIME("eot", "application/vnd.ms-fontobject"); - REGISTER_MIME("epub", "application/epub+zip"); - REGISTER_MIME("gif", "image/gif"); - REGISTER_MIME("gz", "application/gzip"); - REGISTER_MIME("htm", "text/html"); - REGISTER_MIME("html", "text/html"); - REGISTER_MIME("ico", "image/vnd.microsoft.icon"); - REGISTER_MIME("ics", "text/calendar"); - REGISTER_MIME("iso", "application/x-iso9660-image"); - REGISTER_MIME("jar", "application/java-archive"); - REGISTER_MIME("jpe", "image/jpeg"); - REGISTER_MIME("jpeg", "image/jpeg"); - REGISTER_MIME("jpg", "image/jpeg"); - REGISTER_MIME("jpgm", "video/jpm"); - REGISTER_MIME("jpgv", "video/jpeg"); - REGISTER_MIME("jpm", "video/jpm"); - REGISTER_MIME("js", "application/javascript"); - REGISTER_MIME("json", "application/json"); - REGISTER_MIME("jsonld", "application/ld+json"); - REGISTER_MIME("jsonml", "application/jsonml+json"); - REGISTER_MIME("md", "text/markdown"); - REGISTER_MIME("mid", "audio/midi"); - REGISTER_MIME("midi", "audio/midi"); - REGISTER_MIME("mjs", "text/javascript"); - REGISTER_MIME("mp3", "audio/mpeg"); - REGISTER_MIME("mp4", "video/mp4"); - REGISTER_MIME("m4v", "video/mp4"); - REGISTER_MIME("mpeg", "video/mpeg"); - REGISTER_MIME("mpkg", "application/vnd.apple.installer+xml"); - REGISTER_MIME("odp", "application/vnd.oasis.opendocument.presentation"); - REGISTER_MIME("ods", "application/vnd.oasis.opendocument.spreadsheet"); - REGISTER_MIME("odt", "application/vnd.oasis.opendocument.text"); - REGISTER_MIME("oga", "audio/ogg"); - REGISTER_MIME("ogv", "video/ogg"); - REGISTER_MIME("ogx", "application/ogg"); - REGISTER_MIME("opus", "audio/opus"); - REGISTER_MIME("otf", "font/otf"); - REGISTER_MIME("pdf", "application/pdf"); - REGISTER_MIME("php", "application/x-httpd-php"); - REGISTER_MIME("png", "image/png"); - REGISTER_MIME("ppt", "application/vnd.ms-powerpoint"); - REGISTER_MIME("pptx","application/""vnd.openxmlformats-officedocument.presentationml.presentation"); - REGISTER_MIME("rar", "application/vnd.rar"); - REGISTER_MIME("rtf", "application/rtf"); - REGISTER_MIME("sh", "application/x-sh"); - REGISTER_MIME("svg", "image/svg+xml"); - REGISTER_MIME("svgz", "image/svg+xml"); - REGISTER_MIME("tar", "application/x-tar"); - REGISTER_MIME("tif", "image/tiff"); - REGISTER_MIME("tiff", "image/tiff"); - REGISTER_MIME("ts", "video/mp2t"); - REGISTER_MIME("ttf", "font/ttf"); - REGISTER_MIME("txt", "text/plain"); - REGISTER_MIME("vsd", "application/vnd.visio"); - REGISTER_MIME("wav", "audio/wav"); - REGISTER_MIME("weba", "audio/webm"); - REGISTER_MIME("webm", "video/webm"); - REGISTER_MIME("webp", "image/webp"); - REGISTER_MIME("woff", "font/woff"); - REGISTER_MIME("woff2", "font/woff2"); - REGISTER_MIME("xhtml", "application/xhtml+xml"); - REGISTER_MIME("xls", "application/vnd.ms-excel"); - REGISTER_MIME("xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - REGISTER_MIME("xml", "application/xml"); - REGISTER_MIME("xul", "application/vnd.mozilla.xul+xml"); - REGISTER_MIME("zip", "application/zip"); - /* clang-format on */ -} +/** + * Iterates through all response headers (except cookies!). + * + * A non-zero return will stop iteration. + * */ +SFUNC size_t fio_http_response_header_each(fio_http_s *, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata); -#undef REGISTER_MIME +/** Arguments for the fio_http_write function. */ +typedef struct fio_http_write_args_s { + /** The data to be written. */ + const void *buf; + /** The length of the data to be written. */ + size_t len; + /** The offset at which writing should begin. */ + size_t offset; + /** If streaming a file, set this value. The file is always closed. */ + int fd; + /** If the data is a buffer, this callback may be set to free it once sent. */ + void (*dealloc)(void *); + /** If the data is a buffer / a file - should it be copied? */ + int copy; + /** + * If `finish` is set, this data marks the end of the response. + * + * Otherwise the response will stream the data. + */ + int finish; +} fio_http_write_args_s; -/* ***************************************************************************** -Constructor / Destructor -***************************************************************************** */ +/** + * Writes `data` to the response body associated with the HTTP handle after + * sending all headers (no further headers may be sent). + */ +SFUNC void fio_http_write(fio_http_s *, fio_http_write_args_s args); -FIO_SFUNC void fio___http_cleanup(void *ignr_) { - (void)ignr_; -#if FIO_HTTP_CACHE_LIMIT - for (size_t i = 0; i < 2; ++i) { - const char *names[] = {"cookie names", "header values"}; - FIO_LOG_DEBUG2( - "freeing %zu strings from %s cache (capacity was: %zu)", - fio___http_str_cache_count(&FIO___HTTP_STRING_CACHE[i].cache), - names[i], - fio___http_str_cache_capa(&FIO___HTTP_STRING_CACHE[i].cache)); -#ifdef FIO_LOG_LEVEL_DEBUG - if (FIO_LOG_LEVEL_DEBUG == FIO_LOG_LEVEL) { - FIO_MAP_EACH(fio___http_str_cache, - (&FIO___HTTP_STRING_CACHE[i].cache), - pos) { - fprintf(stderr, "\t \"%s\" (%zu bytes)\n", pos.key.buf, pos.key.len); - } - } -#endif - fio___http_str_cache_destroy(&FIO___HTTP_STRING_CACHE[i].cache); - FIO___LOCK_DESTROY(FIO___HTTP_STRING_CACHE[i].lock); - (void)names; /* if unused */ - } -#endif /* FIO_HTTP_CACHE_LIMIT */ - FIO_LOG_DEBUG2("(%d) HTTP MIME hash storage count/capa: %zu / %zu", - fio_getpid(), - FIO___HTTP_MIMETYPES.count, - fio___http_mime_map_capa(&FIO___HTTP_MIMETYPES)); - fio___http_mime_map_destroy(&FIO___HTTP_MIMETYPES); -} +/** Named arguments helper. See fio_http_write and fio_http_write_args_s. */ +#define fio_http_write(http_handle, ...) \ + fio_http_write(http_handle, (fio_http_write_args_s){__VA_ARGS__}) +#define fio_http_finish(http_handle) fio_http_write(http_handle, .finish = 1) -FIO_CONSTRUCTOR(fio___http_str_cache_static_builder) { - fio___http_str_cached_init(); - fio_state_callback_add(FIO_CALL_AT_EXIT, fio___http_cleanup, NULL); - fio_http_mime_register_essential(); -} +/** Closes a persistent HTTP connection (i.e., if upgraded). */ +SFUNC void fio_http_close(fio_http_s *h); /* ***************************************************************************** -Cleanup +WebSocket / SSE Helpers ***************************************************************************** */ -#undef FIO___HTTP_TIME_DIV -#undef FIO___HTTP_TIME_UNIT -#endif /* FIO_EXTERN_COMPLETE */ +/** Returns non-zero if request headers ask for a WebSockets Upgrade.*/ +SFUNC int fio_http_websocket_requested(fio_http_s *); -#undef FIO_HTTP_HANDLE -#endif /* FIO_HTTP_HANDLE */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_HTTP1_PARSER /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** +/** Returns non-zero if the response accepts a WebSocket upgrade request. */ +SFUNC int fio_http_websocket_accepted(fio_http_s *h); +/** Sets response data to agree to a WebSockets Upgrade.*/ +SFUNC void fio_http_upgrade_websocket(fio_http_s *); +/** Sets request data to request a WebSockets Upgrade.*/ +SFUNC void fio_http_websocket_set_request(fio_http_s *); +/** Returns non-zero if request headers ask for an EventSource (SSE) Upgrade.*/ +SFUNC int fio_http_sse_requested(fio_http_s *); - HTTP/1.1 Parser +/** Returns non-zero if the response accepts an SSE request. */ +SFUNC int fio_http_sse_accepted(fio_http_s *h); + +/** Sets response data to agree to an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_upgrade_sse(fio_http_s *); + +/** Sets request data to request an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_sse_set_request(fio_http_s *); +/* ***************************************************************************** +MIME File Type Helpers - NOT thread safe! +***************************************************************************** */ +/** Registers a Mime-Type to be associated with the file extension. */ +SFUNC int fio_http_mimetype_register(char *file_ext, + size_t file_ext_len, + fio_str_info_s mime_type); +/** Finds the Mime-Type associated with the file extension (if registered). */ +SFUNC fio_str_info_s fio_http_mimetype(char *file_ext, size_t file_ext_len); -Copyright and License: see header file (000 copyright.h) or top of file +/* ***************************************************************************** +HTTP Body Parsing Helpers (TODO!) ***************************************************************************** */ -#if defined(FIO_HTTP1_PARSER) && !defined(H___FIO_HTTP1_PARSER___H) && \ - (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) + /* ***************************************************************************** -The HTTP/1.1 provides static functions only, always as part or implementation. +Header Parsing Helpers ***************************************************************************** */ -#define H___FIO_HTTP1_PARSER___H + +/** + * Copies all header data, from possibly an array of identical response headers, + * resulting in a parsed format outputted to `buf_parsed`. + * + * Returns 0 on success or -1 on error (i.e., `buf_parsed.capa` wasn't enough + * for the parsed output). + * + * Note that the parsed output isn't readable as a string, but is designed to + * work with the `FIO_HTTP_PARSED_HEADER_EACH` and + * `FIO_HTTP_HEADER_VALUE_EACH_PROPERTY` property. + * + * See also `fio_http_response_header_parse`. + */ +SFUNC int fio_http_response_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name); + +/** + * Copies all header data, from possibly an array of identical response headers, + * resulting in a parsed format outputted to `buf_parsed`. + * + * Returns 0 on success or -1 on error (i.e., `buf_parsed.capa` wasn't enough + * for the parsed output). + * + * Note that the parsed output isn't readable as a string, but is designed to + * work with the `FIO_HTTP_PARSED_HEADER_EACH` and + * `FIO_HTTP_HEADER_VALUE_EACH_PROPERTY` property. + * + * i.e.: + * + * ```c + * FIO_STR_INFO_TMP_VAR(buf, 1023); // tmp buffer for the parsed output + * fio_http_s *h = fio_http_new(); // using a mock HTTP handle + * fio_http_request_header_add( + * h, + * FIO_STR_INFO2("accept", 6), + * FIO_STR_INFO1("text/html, application/json;q=0.9; d=500, image/png")); + * fio_http_request_header_add(h, + * FIO_STR_INFO2("accept", 6), + * FIO_STR_INFO1("text/yaml")); + * FIO_ASSERT( // in production do NOT assert, but route to error instead! + * !fio_http_request_header_parse(h, &buf, FIO_STR_INFO2("accept", 6)), + * "parse returned error!"); + * FIO_HTTP_PARSED_HEADER_EACH(buf, value) { + * printf("* processing value (%zu bytes): %s\n", value.len, value.buf); + * FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(value, prop) { + * printf("* for value %s: (%zu,%zu bytes) %s = %s\n", + * value.buf, + * prop.name.len, + * prop.value.len, + * prop.name.buf, + * prop.value.buf); + * } + * } + * ``` + */ +SFUNC int fio_http_request_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name); + +/** + * Parses header for multiple values and properties and iterates over all + * values. + * + * This MACRO will allocate 2048 bytes on the stack for parsing the header + * values and properties, if more space is necessary dig deeper. + * + * Use FIO_HTTP_HEADER_VALUE_EACH_PROPERTY to iterate over a value's properties. + */ +#define FIO_HTTP_HEADER_EACH_VALUE(/* fio_http_s */ http_handle, \ + /* int / bool */ is_request, \ + /* fio_str_info_s */ header_name, \ + /* chosen var named */ value) \ + for (char fio___buf__##value##__[2048], /* allocate buffer on stack */ \ + *fio___buf__##value##_ptr = NULL; \ + !fio___buf__##value##_ptr; \ + fio___buf__##value##_ptr = fio___buf__##value##__) \ + for (fio_str_info_s fio___buf__##value##__str = /* declare buffer var */ \ + FIO_STR_INFO3(fio___buf__##value##__, 0, 2048); \ + fio___buf__##value##__str.buf == fio___buf__##value##__; \ + fio___buf__##value##__str.buf = fio___buf__##value##__ + 1) \ + if (!((is_request ? fio_http_request_header_parse \ + : fio_http_response_header_parse)( \ + http_handle, /* parse headers */ \ + &fio___buf__##value##__str, \ + header_name))) \ + FIO_HTTP_PARSED_HEADER_EACH(fio___buf__##value##__str, value) /* loop \ + */ + +/** Iterated through the properties associated with a parsed header values. */ +#define FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(/* fio_str_info_s */ value, \ + /* chosen var named */ property) + +/** Used internally to iterate over a parsed header buffer. */ +#define FIO_HTTP_PARSED_HEADER_EACH(/* fio_str_info_s */ buf_parsed, \ + /* chosen var named */ value) /* ***************************************************************************** -HTTP/1.x Parser API +General Helpers ***************************************************************************** */ -/** The HTTP/1.1 parser type */ -typedef struct fio_http1_parser_s fio_http1_parser_s; -/** Initialization value for the parser */ -#define FIO_HTTP1_PARSER_INIT ((fio_http1_parser_s){0}) +/** Sends the requested error message and finishes the response. */ +SFUNC int fio_http_send_error_response(fio_http_s *h, size_t status); + +/** Returns true (1) if the ETag response matches an if-none-match request. */ +SFUNC int fio_http_etag_is_match(fio_http_s *h); /** - * Parses HTTP/1.x data, calling any callbacks. + * Attempts to send a static file from the `root` folder. On success the + * response is complete and 0 is returned. Otherwise returns -1. + */ +SFUNC int fio_http_static_file_response(fio_http_s *h, + fio_str_info_s root_folder, + fio_str_info_s file_name, + size_t max_age); + +/** Returns a human readable string related to the HTTP status number. */ +SFUNC fio_str_info_s fio_http_status2str(size_t status); + +/** Logs an HTTP (response) to STDOUT. */ +SFUNC void fio_http_write_log(fio_http_s *h); + +/** + * Writes peer address to `dest` starting with the `forwarded` header, with a + * fallback to actual socket address and a final fallback to `"[unknown]"`. * - * Returns bytes consumed or `FIO_HTTP1_PARSER_ERROR` (`(size_t)-1`) on error. + * If `unknown` is returned, the function returns -1. if `dest` capacity is too + * small, the number of bytes required will be returned. + * + * If all goes well, this function returns 0. */ -FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, - fio_buf_info_s buf, - void *udata); +SFUNC int fio_http_from(fio_str_info_s *dest, const fio_http_s *h); -/** Returns true if the parser is waiting to parse a new request/response .*/ -FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p); +/* date/time string caching for HTTP date header */ +SFUNC fio_str_info_s fio_http_date(uint64_t now_in_seconds); -/** The error return value for fio_http1_parse. */ -#define FIO_HTTP1_PARSER_ERROR ((size_t)-1) +/* date/time string caching for HTTP logging */ +SFUNC fio_str_info_s fio_http_log_time(uint64_t now_in_seconds); +/* ***************************************************************************** +The HTTP Controller +***************************************************************************** */ -/** Returns the number of bytes of payload still expected to be received. */ -FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p); -/** A return value for `fio_http1_expected` when chunked data is expected. */ -#define FIO_HTTP1_EXPECTED_CHUNKED ((size_t)(-1)) +/** + * The HTTP Controller manages all the callbacks required by the HTTP Handler in + * order for HTTP responses and requests to be sent. + */ +struct fio_http_controller_s { + /* MUST be initialized to zero, used internally by the HTTP Handle. */ + uintptr_t private_flags; + /** Called when an HTTP handle is freed. */ + void (*on_destroyed)(fio_http_s *h); + /** Informs the controller that request / response headers must be sent. */ + void (*send_headers)(fio_http_s *h); + /** called by the HTTP handle for each body chunk (or to finish a response. */ + void (*write_body)(fio_http_s *h, fio_http_write_args_s args); + /** called once a request / response had finished */ + void (*on_finish)(fio_http_s *h); + /** called to close an HTTP connection */ + void (*close_io)(fio_http_s *h); + /** called when the file descriptor is directly required */ + int (*get_fd)(fio_http_s *h); +}; /* ***************************************************************************** -HTTP/1.x callbacks (to be implemented by parser user) +HTTP Handle Implementation - inlined static functions ***************************************************************************** */ -/** called when either a request or a response was received. */ -static void fio_http1_on_complete(void *udata); -/** called when a request method is parsed. */ -static int fio_http1_on_method(fio_buf_info_s method, void *udata); -/** called when a response status is parsed. the status_str is the string - * without the prefixed numerical status indicator.*/ -static int fio_http1_on_status(size_t istatus, - fio_buf_info_s status, - void *udata); -/** called when a request URL is parsed. */ -static int fio_http1_on_url(fio_buf_info_s path, void *udata); -/** called when a the HTTP/1.x version is parsed. */ -static int fio_http1_on_version(fio_buf_info_s version, void *udata); -/** called when a header is parsed. */ -static int fio_http1_on_header(fio_buf_info_s name, - fio_buf_info_s value, - void *udata); -/** called when the special content-length header is parsed. */ -static int fio_http1_on_header_content_length(fio_buf_info_s name, - fio_buf_info_s value, - size_t content_length, - void *udata); -/** called when `Expect` arrives and may require a 100 continue response. */ -static int fio_http1_on_expect(void *udata); -/** called when a body chunk is parsed. */ -static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata); +#define FIO___HTTP_GETSET_PTR(type, name, index_, pre_set_code) \ + /** Used internally to set / get the propecrty at its known pointer index. \ + */ \ + FIO_IFUNC type *fio_http_##name(fio_http_s *h) { \ + return ((type **)h)[index_]; \ + } \ + /** Used internally to set / get the propercty at its known pointer index. \ + */ \ + FIO_IFUNC type *fio_http_##name##_set(fio_http_s *h, type *ptr) { \ + pre_set_code; \ + return (((type **)h)[index_] = ptr); \ + } + +SFUNC fio_http_controller_s *fio___http_controller_validate( + fio_http_controller_s *c); + +/* Create fio_http_udata_(get|set) functions */ +FIO___HTTP_GETSET_PTR(void, udata, 0, (void)0) +/* Create fio_http_pdata_(get|set) functions */ +FIO___HTTP_GETSET_PTR(void, udata2, 1, (void)0) +/* Create fio_http_cdata_(get|set) functions */ +FIO___HTTP_GETSET_PTR(void, cdata, 2, (void)0) +/* Create fio_http_controller_(get|set) functions */ +FIO___HTTP_GETSET_PTR(fio_http_controller_s, + controller, + 3, + ptr = fio___http_controller_validate(ptr)) + +#undef FIO___HTTP_GETSET_PTR +/* +REMEMBER: +======== + +All memory allocations should use: +* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) +* FIO_MEM_FREE_(ptr, size) + +*/ /* ***************************************************************************** -Implementation Stage Helpers +Header Parsing Helpers - inlined helpers ***************************************************************************** */ -/* parsing stage 0 - read first line (proxy?). */ -static int fio_http1___start(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); -/* parsing stage 1 - read headers. */ -static int fio_http1___read_header(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); -/* parsing stage 2 - read body. */ -static int fio_http1___read_body(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); -/* parsing stage 2 - read chunked body. */ -static int fio_http1___read_body_chunked(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); -/* parsing stage 1 - read headers. */ -static int fio_http1___read_trailer(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); -/* completed parsing. */ -static int fio_http1___finish(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); +#define FIO___HTTP_PARSED_HEADER_VALUE 0 +#define FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN 1 +#define FIO___HTTP_PARSED_HEADER_PROPERTY_DATA 2 + +typedef struct { + fio_str_info_s name; + fio_str_info_s value; +} fio___http_header_property_s; + +/** + * Assumes a Buffer of bytes containing length info and string data as such: + * [ NUL byte - 1 byte at head of format ] + * repeat + * [ 2 byte info: (type | (len << 2)) ] + * [ Optional 2 byte info: (len << 2) (if type was 1)] + * [ String of `len` bytes][ NUL byte (not counted in `len`)] + */ + +FIO_IFUNC fio_str_info_s fio___http_parsed_headers_next(fio_str_info_s value) { + for (;;) { + const size_t coded = (size_t)fio_buf2u16u(value.buf + value.len + 1U); + if (!coded) + return (value = (fio_str_info_s){0}); + const size_t block_len = coded >> 2; + value.buf += value.len + 3; + value.len = block_len; + if (!(coded & 3)) + return value; + value.buf -= 3; /* reposition to read NUL + value rather than text start */ + } +} + +FIO_IFUNC fio___http_header_property_s +fio___http_parsed_property_next(fio___http_header_property_s property) { + for (;;) { + size_t coded = + (size_t)fio_buf2u16u(property.value.buf + property.value.len + 1); + if (!(coded & 3)) + return (property = (fio___http_header_property_s){{0}, {0}}); + if ((coded & 3) == FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN) { + property.value.buf += 2; + coded = (size_t)fio_buf2u16u(property.value.buf + property.value.len + 1); + } + if ((coded & 3) != 2) + return (property = (fio___http_header_property_s){{0}, {0}}); + coded >>= 2; + property.name.buf = property.value.buf + property.value.len + 3; + property.name.len = coded; + coded = (size_t)fio_buf2u16u(property.name.buf + property.name.len + 1); + FIO_ASSERT_DEBUG((coded & 3) == 2, + "header property value parsing format error"); + property.value.buf = property.name.buf + property.name.len + 3; + property.value.len = coded >> 2; + return property; + } +} + +#undef FIO_HTTP_PARSED_HEADER_EACH +#define FIO_HTTP_PARSED_HEADER_EACH(buf_parsed, value) \ + for (fio_str_info_s value = \ + fio___http_parsed_headers_next(FIO_STR_INFO2(buf_parsed.buf, 0)); \ + value.len; \ + value = fio___http_parsed_headers_next(value)) + +#undef FIO_HTTP_HEADER_VALUE_EACH_PROPERTY +#define FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(value_, property) \ + for (fio___http_header_property_s property = \ + fio___http_parsed_property_next( \ + (fio___http_header_property_s){.value = value_}); \ + property.name.len; \ + property = fio___http_parsed_property_next(property)) /* ***************************************************************************** -HTTP Parser Type +HTTP Handle Implementation - possibly externed functions. +***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + +/* ***************************************************************************** +Helpers - reading time ***************************************************************************** */ -/** The HTTP/1.1 parser type implementation */ -struct fio_http1_parser_s { - int (*fn)(fio_http1_parser_s *, fio_buf_info_s *, void *); - size_t expected; -}; +#if FIO_HTTP_EXACT_LOGGING +#define FIO___HTTP_TIME_DIV 1000000 +#define FIO___HTTP_TIME_UNIT "us" +FIO_IFUNC int64_t fio_http_get_timestump(void) { + return fio_time2micro(fio_time_real()); +} +#else +#define FIO___HTTP_TIME_DIV 1000 +#define FIO___HTTP_TIME_UNIT "ms" +#define fio_http_get_timestump() fio_io_last_tick() +#endif -/** Returns true if the parser is waiting to parse a new request/response .*/ -FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p) { - return !p->fn || p->fn == fio_http1___start; +/* date/time string caching for HTTP date header */ +SFUNC fio_str_info_s fio_http_date(uint64_t now_in_seconds) { + static char date_buf[128]; + static size_t date_len; + static uint64_t date_buf_val; + if (date_buf_val == now_in_seconds) + return FIO_STR_INFO2(date_buf, date_len); + date_len = fio_time2rfc7231(date_buf, now_in_seconds); + date_buf[date_len] = 0; + date_buf_val = now_in_seconds; + return FIO_STR_INFO2(date_buf, date_len); } -/** Returns the number of bytes of payload still expected to be received. */ -FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p) { - return p->expected; +/* date/time string caching for HTTP logging */ +SFUNC fio_str_info_s fio_http_log_time(uint64_t now_in_seconds) { + static char date_buf[128]; + static size_t date_len; + static uint64_t date_buf_val; + if (date_buf_val == now_in_seconds) + return FIO_STR_INFO2(date_buf, date_len); + date_len = fio_time2log(date_buf, now_in_seconds); + date_buf[date_len] = 0; + date_buf_val = now_in_seconds; + return FIO_STR_INFO2(date_buf, date_len); } /* ***************************************************************************** -Main Parsing Loop +Helpers - fio_keystr_s memory allocation callbacks ***************************************************************************** */ -FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, - fio_buf_info_s buf, - void *udata) { - int i = 0; - char *buf_start = buf.buf; - if (!buf.len) - return 0; - if (!p->fn) - p->fn = fio_http1___start; - while (!(i = p->fn(p, &buf, udata))) - ; - if (i < 0) - return FIO_HTTP1_PARSER_ERROR; - return buf.buf - buf_start; +FIO_LEAK_COUNTER_DEF(http___keystr_allocator) + +FIO_SFUNC void fio___http_keystr_free(void *ptr, size_t len) { + if (!ptr) + return; + FIO_LEAK_COUNTER_ON_FREE(http___keystr_allocator); + FIO_MEM_FREE_(ptr, len); + (void)len; /* if unused */ } - -/* completed parsing. */ -static int fio_http1___finish(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - (void)buf; - *p = (fio_http1_parser_s){0}; - fio_http1_on_complete(udata); - return 1; +FIO_SFUNC void *fio___http_keystr_alloc(size_t capa) { + FIO_LEAK_COUNTER_ON_ALLOC(http___keystr_allocator); + return FIO_MEM_REALLOC_(NULL, 0, capa, 0); } /* ***************************************************************************** -Reading the first line +Helper Types +***************************************************************************** */ +#define FIO___RECURSIVE_INCLUDE 1 +/* ***************************************************************************** +String Cache ***************************************************************************** */ -/* parsing stage 0 - read first line (TODO: proxy protocol support?). */ -static int fio_http1___start(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - /* find line start/end and test */ - fio_buf_info_s wrd[3]; - char *start = buf->buf; - char *tmp; - while ((start[0] == ' ' || start[0] == '\r' || start[0] == '\n') && - start < buf->buf + buf->len) /* skip white space */ - ++start; - if (start == buf->buf + buf->len) { - buf->buf = start; - return 1; +#define FIO_MAP_NAME fio___http_str_cache +#define FIO_MAP_LRU FIO_HTTP_CACHE_LIMIT +#define FIO_MAP_KEY_BSTR +#include FIO_INCLUDE_FILE + +static struct { + fio___http_str_cache_s cache; + FIO___LOCK_TYPE lock; +} FIO___HTTP_STRING_CACHE[2] = {{.lock = FIO___LOCK_INIT}, + {.lock = FIO___LOCK_INIT}}; +#define FIO___HTTP_STR_CACHE_COOKIE 0 +#define FIO___HTTP_STR_CACHE_VALUE 1 + +#if FIO_HTTP_PRE_CACHE_KNOWN_HEADERS + +#define FIO___HTTP_STATIC_CACHE_CAPA_BITS 7 +#define FIO___HTTP_STATIC_CACHE_CAPA (1U << FIO___HTTP_STATIC_CACHE_CAPA_BITS) +#define FIO___HTTP_STATIC_CACHE_MASK (FIO___HTTP_STATIC_CACHE_CAPA - 1) +#define FIO___HTTP_STATIC_CACHE_STEP_LIMIT 4 + +typedef struct FIO___HTTP_STATIC_CACHE_T { + fio___bstr_meta_s meta; + char str[32]; +} FIO___HTTP_STATIC_CACHE_T; +static FIO___HTTP_STATIC_CACHE_T FIO___HTTP_STATIC_CACHE[] = { +#define FIO___HTTP_STATIC_CACHE_SET(s) \ + { \ + .meta = {.len = (uint32_t)(sizeof(s) - 1), .ref = ((uint32_t)(~0) >> 8)}, \ + .str = s \ } - char *eol = (char *)FIO_MEMCHR(start, '\n', buf->len); - if (!eol) - return 1; - if (start + 13 > eol) /* test for minimal data GET HTTP/1 or ### HTTP/1 */ - return -1; + FIO___HTTP_STATIC_CACHE_SET("a-im"), + FIO___HTTP_STATIC_CACHE_SET("accept"), + FIO___HTTP_STATIC_CACHE_SET("accept-charset"), + FIO___HTTP_STATIC_CACHE_SET("accept-datetime"), + FIO___HTTP_STATIC_CACHE_SET("accept-encoding"), + FIO___HTTP_STATIC_CACHE_SET("accept-language"), + FIO___HTTP_STATIC_CACHE_SET("accept-ranges"), + FIO___HTTP_STATIC_CACHE_SET("access-control-allow-origin"), + FIO___HTTP_STATIC_CACHE_SET("access-control-request-headers"), + FIO___HTTP_STATIC_CACHE_SET("access-control-request-method"), + FIO___HTTP_STATIC_CACHE_SET("age"), + FIO___HTTP_STATIC_CACHE_SET("allow"), + FIO___HTTP_STATIC_CACHE_SET("authorization"), + FIO___HTTP_STATIC_CACHE_SET("cache-control"), + FIO___HTTP_STATIC_CACHE_SET("connection"), + FIO___HTTP_STATIC_CACHE_SET("content-disposition"), + FIO___HTTP_STATIC_CACHE_SET("content-encoding"), + FIO___HTTP_STATIC_CACHE_SET("content-language"), + FIO___HTTP_STATIC_CACHE_SET("content-length"), + FIO___HTTP_STATIC_CACHE_SET("content-location"), + FIO___HTTP_STATIC_CACHE_SET("content-range"), + FIO___HTTP_STATIC_CACHE_SET("content-type"), + FIO___HTTP_STATIC_CACHE_SET("cookie"), + FIO___HTTP_STATIC_CACHE_SET("date"), + FIO___HTTP_STATIC_CACHE_SET("dnt"), + FIO___HTTP_STATIC_CACHE_SET("etag"), + FIO___HTTP_STATIC_CACHE_SET("expect"), + FIO___HTTP_STATIC_CACHE_SET("expires"), + FIO___HTTP_STATIC_CACHE_SET("forwarded"), + FIO___HTTP_STATIC_CACHE_SET("from"), + FIO___HTTP_STATIC_CACHE_SET("host"), + FIO___HTTP_STATIC_CACHE_SET("if-match"), + FIO___HTTP_STATIC_CACHE_SET("if-modified-since"), + FIO___HTTP_STATIC_CACHE_SET("if-none-match"), + FIO___HTTP_STATIC_CACHE_SET("if-range"), + FIO___HTTP_STATIC_CACHE_SET("if-unmodified-since"), + FIO___HTTP_STATIC_CACHE_SET("last-modified"), + FIO___HTTP_STATIC_CACHE_SET("link"), + FIO___HTTP_STATIC_CACHE_SET("location"), + FIO___HTTP_STATIC_CACHE_SET("max-forwards"), + FIO___HTTP_STATIC_CACHE_SET("origin"), + FIO___HTTP_STATIC_CACHE_SET("pragma"), + FIO___HTTP_STATIC_CACHE_SET("proxy-authenticate"), + FIO___HTTP_STATIC_CACHE_SET("proxy-authorization"), + FIO___HTTP_STATIC_CACHE_SET("range"), + FIO___HTTP_STATIC_CACHE_SET("referer"), + FIO___HTTP_STATIC_CACHE_SET("refresh"), + FIO___HTTP_STATIC_CACHE_SET("retry-after"), + FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua"), + FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua-mobile"), + FIO___HTTP_STATIC_CACHE_SET("sec-ch-ua-platform"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-dest"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-mode"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-site"), + FIO___HTTP_STATIC_CACHE_SET("sec-fetch-user"), + FIO___HTTP_STATIC_CACHE_SET("server"), + FIO___HTTP_STATIC_CACHE_SET("set-cookie"), + FIO___HTTP_STATIC_CACHE_SET("strict-transport-security"), + FIO___HTTP_STATIC_CACHE_SET("te"), + FIO___HTTP_STATIC_CACHE_SET("transfer-encoding"), + FIO___HTTP_STATIC_CACHE_SET("upgrade"), + FIO___HTTP_STATIC_CACHE_SET("upgrade-insecure-requests"), + FIO___HTTP_STATIC_CACHE_SET("user-agent"), + FIO___HTTP_STATIC_CACHE_SET("vary"), + FIO___HTTP_STATIC_CACHE_SET("via"), + FIO___HTTP_STATIC_CACHE_SET("warning"), + FIO___HTTP_STATIC_CACHE_SET("www-authenticate"), + FIO___HTTP_STATIC_CACHE_SET("x-csrf-token"), + FIO___HTTP_STATIC_CACHE_SET("x-forwarded-for"), + FIO___HTTP_STATIC_CACHE_SET("x-forwarded-host"), + FIO___HTTP_STATIC_CACHE_SET("x-forwarded-proto"), + FIO___HTTP_STATIC_CACHE_SET("x-requested-with"), + {{0}}}; - /* prep next stage */ - buf->len -= (eol - buf->buf) + 1; - buf->buf = eol + 1; - eol -= eol[-1] == '\r'; +static uint32_t FIO___HTTP_STATIC_CACHE_IMAP[FIO___HTTP_STATIC_CACHE_CAPA]; - /* parse first line */ - /* request: method path version ; response: version code txt */ - if (!(tmp = (char *)FIO_MEMCHR(start, ' ', (size_t)(eol - start)))) - return -1; - wrd[0] = FIO_BUF_INFO2(start, (size_t)(tmp - start)); - start = tmp + 1; - if (!(tmp = (char *)FIO_MEMCHR(start, ' ', eol - start))) - return -1; - wrd[1] = FIO_BUF_INFO2(start, (size_t)(tmp - start)); - start = tmp + 1; - if (start >= eol) - return -1; - wrd[2] = FIO_BUF_INFO2(start, (size_t)(eol - start)); - if (fio_c2i(wrd[1].buf[0]) < 10) /* test if path or code */ - goto parse_response_line; - if (wrd[2].len > 14) - wrd[2].len = 14; - if (fio_http1_on_method(wrd[0], udata)) - return -1; - if (fio_http1_on_url(wrd[1], udata)) - return -1; - if (fio_http1_on_version(wrd[2], udata)) - return -1; - return (p->fn = fio_http1___read_header)(p, buf, udata); +static uint64_t fio___http_str_cached_hash(char *str, size_t len) { + /* use low-case hash specific for the HTTP handle (change resilient) */ + const fio_u256 primes = fio_u256_init64(FIO_U64_HASH_PRIME1, + FIO_U64_HASH_PRIME2, + FIO_U64_HASH_PRIME3, + FIO_U64_HASH_PRIME4); + uint64_t hash = 0; + if (len > 32) + return hash; + fio_u512 s = {0}; + fio_memcpy63x(s.u8, str, len); + for (size_t i = 0; i < 4; ++i) + s.u64[i] = fio_ltole64(s.u64[i]); +#if 0 + fio_u256_cor64(s.u256, s.u256, 0x2020202020202020ULL); /* lower case hash */ + fio_u256_cadd16(s.u256, s.u256, len); + for (size_t i = 0; i < 4; ++i) + s.u64[i] = fio_math_mulc64(s.u64[i], primes.u64[i], s.u64 + 4 + i); + hash = fio_u256_reduce_add64(s.u256 + 1); + hash ^= fio_u256_reduce_add64(s.u256); + fio_u256_cxor64(s.u256, s.u256, hash); + hash += fio_u256_reduce_add64(s.u256); +#else + FIO_MATH_UXXX_COP(s.u256[0], s.u256[0], 0x2020202020202020ULL, 64, |); + FIO_MATH_UXXX_COP(s.u256[0], s.u256[0], (uint16_t)len, 16, +); + for (size_t i = 0; i < 4; ++i) { + s.u64[i] = fio_math_mulc64(s.u64[i], primes.u64[i], s.u64 + 4 + i); + } + uint64_t tmp; + FIO_MATH_UXXX_REDUCE(hash, s.u256[1], 64, +); + FIO_MATH_UXXX_REDUCE(tmp, s.u256[0], 64, +); + hash ^= tmp; + FIO_MATH_UXXX_COP(s.u256[0], s.u256[0], hash, 64, ^); + FIO_MATH_UXXX_REDUCE(tmp, s.u256[0], 64, +); + hash += tmp; +#endif + // hash += hash >> 4; + hash += hash >> 5; + return hash; +} -parse_response_line: - if (wrd[0].len > 14) - wrd[0].len = 14; - if (fio_http1_on_version(wrd[0], udata)) - return -1; - if (fio_http1_on_status(fio_atol10u(&wrd[1].buf), wrd[2], udata)) - return -1; - return (p->fn = fio_http1___read_header)(p, buf, udata); +#ifndef FIO___HTTP_STATIC_CACHE_CMP_SECURE +#define FIO___HTTP_STATIC_CACHE_CMP_SECURE 0 +#endif +static bool fio___http_str_cached_cmp(void *arry, void *obj, uint32_t indx) { + FIO___HTTP_STATIC_CACHE_T *ary = (FIO___HTTP_STATIC_CACHE_T *)arry; + if (indx > + ((sizeof(FIO___HTTP_STATIC_CACHE) / sizeof(FIO___HTTP_STATIC_CACHE[0])) - + 1)) + return 0; + fio_buf_info_s *s = (fio_buf_info_s *)obj; + fio_buf_info_s t = FIO_BUF_INFO2(ary[indx].str, ary[indx].meta.len); +#if FIO___HTTP_STATIC_CACHE_CMP_SECURE + return FIO_BUF_INFO_IS_EQ((*s), t); +#else + return s[0].len == t.len && s[0].buf[0] == (t.buf[0] | 32); +#endif } +#undef FIO___HTTP_STATIC_CACHE_CMP_SECURE -/* ***************************************************************************** -Reading Headers -***************************************************************************** */ +static char *fio___http_str_cached_static(char *str, size_t len) { + if (len >= 32) + return NULL; + fio_buf_info_s obj = FIO_BUF_INFO2(str, len); + uint64_t hash = fio___http_str_cached_hash(obj.buf, obj.len); + fio___imap32_seeker_s pos = + fio___imap32_seek((void *)FIO___HTTP_STATIC_CACHE, + FIO___HTTP_STATIC_CACHE_IMAP, + FIO___HTTP_STATIC_CACHE_CAPA_BITS, + (void *)&obj, + (uint32_t)hash, + fio___http_str_cached_cmp, + FIO___HTTP_STATIC_CACHE_STEP_LIMIT); + if (!pos.is_valid) + return NULL; + ++FIO___HTTP_STATIC_CACHE[pos.pos].meta.ref; + return FIO___HTTP_STATIC_CACHE[pos.pos].str; +} -/* parsing stage 1 - read headers (after `expect` header). */ -static int fio_http1___read_header_post_expect(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata); +static void fio___http_str_cached_init(void) { + FIO_MEMSET(FIO___HTTP_STATIC_CACHE_IMAP, + 0, + sizeof(FIO___HTTP_STATIC_CACHE_IMAP)); -/* handle headers before calling callback. */ -static inline int fio_http1___on_header(fio_http1_parser_s *p, - fio_buf_info_s name, - fio_buf_info_s value, - void *udata) { - /* test for special headers */ - switch (name.len) { - case 6: /* test for "expect" */ - if (value.len == 12 && fio_buf2u32u(name.buf) == fio_buf2u32u("expe") && - fio_buf2u32u(name.buf + 2) == fio_buf2u32u("pect") && - fio_buf2u64u(value.buf) == fio_buf2u64u("100-cont") && - fio_buf2u32u(value.buf + 8) == fio_buf2u32u("inue")) { /* Expect */ - p->fn = fio_http1___read_header_post_expect; - return 0; - } - break; - case 14: /* test for "content-length" */ - if (fio_buf2u64u(name.buf) == fio_buf2u64u("content-") && - fio_buf2u64u(name.buf + 6) == fio_buf2u64u("t-length")) { - char *tmp = value.buf; - uint64_t clen = fio_atol10u(&tmp); - if (tmp != value.buf + value.len) - return -1; - if (p->expected) - return 0 - (p->expected != clen); - p->expected = clen; - return 0 - - (fio_http1_on_header_content_length(name, value, clen, udata) == - -1); - } - break; - case 17: /* test for "transfer-encoding" (chunked?) */ - if (value.len >= 7 && (name.buf[16] == 'g') && - !((fio_buf2u64u(name.buf) ^ fio_buf2u64u("transfer")) | - (fio_buf2u64u(name.buf + 8) ^ fio_buf2u64u("-encodin")))) { - char *c_start = value.buf + value.len - 7; - if ((fio_buf2u32u(c_start) | 0x20202020UL) == fio_buf2u32u("chun") && - (fio_buf2u32u(c_start + 3) | 0x20202020UL) == fio_buf2u32u("nked")) { - if (p->expected && p->expected != FIO_HTTP1_EXPECTED_CHUNKED) - return -1; - p->expected = FIO_HTTP1_EXPECTED_CHUNKED; - /* endpoint does not need to know if the body was chunked or not */ - if (value.len == 7) - return 0; - if (c_start[-1] != ' ' && c_start[-1] != ',' && c_start[-1] != '\t') - return -1; - while ( - (c_start[-1] == ' ' || c_start[-1] == ',' || c_start[-1] == '\t') && - c_start > value.buf) - --c_start; - if (c_start == value.buf) - return 0; - value.len = c_start - value.buf; - } - } - break; - } - /* perform callback */ - return 0 - (fio_http1_on_header(name, value, udata) == -1); -} + for (size_t i = 0; FIO___HTTP_STATIC_CACHE[i].meta.ref; ++i) { + fio_buf_info_s obj = FIO_BUF_INFO2(FIO___HTTP_STATIC_CACHE[i].str, + FIO___HTTP_STATIC_CACHE[i].meta.len); + uint64_t hash = fio___http_str_cached_hash(obj.buf, obj.len); + fio___imap32_seeker_s pos = + fio___imap32_seek((void *)FIO___HTTP_STATIC_CACHE, + FIO___HTTP_STATIC_CACHE_IMAP, + FIO___HTTP_STATIC_CACHE_CAPA_BITS, + (void *)&obj, + hash, + fio___http_str_cached_cmp, + FIO___HTTP_STATIC_CACHE_STEP_LIMIT); + FIO_ASSERT(!pos.is_valid && pos.ipos < FIO___HTTP_STATIC_CACHE_CAPA && + !FIO___HTTP_STATIC_CACHE_IMAP[pos.ipos], + "HTTP static cache collision / overflow @ %zu (%s)", + i, + obj.buf); -/* handle trailers (chunked encoding only) before calling callback. */ -static inline int fio_http1___on_trailer(fio_http1_parser_s *p, - fio_buf_info_s name, - fio_buf_info_s value, - void *udata) { - (void)p; - fio_buf_info_s forbidden[] = { - FIO_BUF_INFO1((char *)"authorization"), - FIO_BUF_INFO1((char *)"cache-control"), - FIO_BUF_INFO1((char *)"content-encoding"), - FIO_BUF_INFO1((char *)"content-length"), - FIO_BUF_INFO1((char *)"content-range"), - FIO_BUF_INFO1((char *)"content-type"), - FIO_BUF_INFO1((char *)"expect"), - FIO_BUF_INFO1((char *)"host"), - FIO_BUF_INFO1((char *)"max-forwards"), - FIO_BUF_INFO1((char *)"set-cookie"), - FIO_BUF_INFO1((char *)"te"), - FIO_BUF_INFO1((char *)"trailer"), - FIO_BUF_INFO1((char *)"transfer-encoding"), - FIO_BUF_INFO2(NULL, 0), - }; /* known forbidden headers in trailer */ - for (size_t i = 0; forbidden[i].buf; ++i) { - if (FIO_BUF_INFO_IS_EQ(name, forbidden[i])) - return -1; + pos.set_val |= i; + fio___imap32_set(FIO___HTTP_STATIC_CACHE_IMAP, pos.ipos, pos.set_val); + + FIO_ASSERT(fio___http_str_cached_static(obj.buf, obj.len) == obj.buf, + "HTTP static cache initialization round-trip error @ %zu (%s)", + i, + obj.buf); } - return fio_http1_on_header(name, value, udata); } -/* returns either a lower case (ASCI) or the original char. */ -static uint8_t fio_http1_tolower(uint8_t c) { - if ((c - ((uint8_t)'A' - 1U)) < ((uint8_t)'Z' - (uint8_t)'A')) - c |= 32; - return c; +#undef FIO___HTTP_STATIC_CACHE_CAPA_BITS +#undef FIO___HTTP_STATIC_CACHE_CAPA +#undef FIO___HTTP_STATIC_CACHE_MASK +#undef FIO___HTTP_STATIC_CACHE_STEP_LIMIT +#else +#define fio___http_str_cached_init() (void)0 +#endif /* FIO_HTTP_PRE_CACHE_KNOWN_HEADERS */ + +FIO_IFUNC char *fio___http_str_cached_inner(size_t group, + uint64_t hash, + fio_str_info_s s) { +#if !FIO_HTTP_CACHE_LIMIT + return fio_bstr_write(NULL, s.buf, s.len); +#endif + fio_str_info_s cached; + hash ^= (uint64_t)(uintptr_t)fio_http_new; +#if FIO_HTTP_CACHE_USES_MUTEX + FIO___LOCK_LOCK(FIO___HTTP_STRING_CACHE[group].lock); +#endif + cached = + fio___http_str_cache_set_if_missing(&FIO___HTTP_STRING_CACHE[group].cache, + hash, + s); +#if FIO_HTTP_CACHE_USES_MUTEX + FIO___LOCK_UNLOCK(FIO___HTTP_STRING_CACHE[group].lock); +#endif + return fio_bstr_copy(cached.buf); +} +FIO_IFUNC char *fio___http_str_cached(size_t group, fio_str_info_s s) { +#if !FIO_HTTP_CACHE_LIMIT + return fio_bstr_write(NULL, s.buf, s.len); +#endif + if (!s.len) + return NULL; + if (s.len > FIO_HTTP_CACHE_STR_MAX_LEN) + goto avoid_caching; + return fio___http_str_cached_inner(group, fio_risky_hash(s.buf, s.len, 0), s); +avoid_caching: + return fio_bstr_write(NULL, s.buf, s.len); } -/* seeks to the ':' divisor while testing and converting to downcase. */ -static char *fio_http1___seek_header_div(char *p) { - /* this is the subset of the forbidden chars that allows UTF-8 headers */ - static const _Bool forbidden_name_chars[256] = { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; - for (;;) { - *p = (char)fio_http1_tolower((uint8_t)(*p)); - ++p; - if (FIO_UNLIKELY(forbidden_name_chars[((uint8_t)(*p))])) - return p; +FIO_IFUNC char *fio___http_str_cached_with_static(fio_str_info_s s) { +#if FIO_HTTP_PRE_CACHE_KNOWN_HEADERS + char *tmp; + if (!s.len) + return NULL; + if (s.len > FIO_HTTP_CACHE_STR_MAX_LEN) + goto skip_cache_test; + tmp = fio___http_str_cached_static(s.buf, s.len); + if (tmp) { + FIO_LOG_DDEBUG2("(%d) using statically cached header: %s", + fio_io_pid(), + tmp); + return tmp; /* reference count increased by fio___http_str_cached_static */ } +skip_cache_test: +#endif /* FIO_HTTP_PRE_CACHE_KNOWN_HEADERS */ + return fio_bstr_write(NULL, s.buf, s.len); } -/* extract header name and value from a line and pass info to handler */ -static inline int fio_http1___read_header_line( - fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata, - int (*handler)(fio_http1_parser_s *, - fio_buf_info_s, - fio_buf_info_s, - void *)) { - for (;;) { - char *start = buf->buf; - char *eol = (char *)FIO_MEMCHR(start, '\n', buf->len); - char *div; - fio_buf_info_s name, value; - if (!eol) - return 1; +/* ***************************************************************************** +Headers Maps +***************************************************************************** */ - buf->len -= (eol - buf->buf) + 1; - buf->buf = eol + 1; - eol -= (eol[-1] == '\r'); - if (FIO_UNLIKELY(eol == start)) - goto headers_finished; +#define FIO_ARRAY_NAME fio___http_sary +#define FIO_ARRAY_TYPE char * +#define FIO_ARRAY_TYPE_DESTROY(obj) fio_bstr_free(obj) - div = fio_http1___seek_header_div(start); - if (div[0] != ':') - return -1; - name = FIO_BUF_INFO2(start, (size_t)(div - start)); - do { - ++div; - } while (*div == ' ' || *div == '\t'); +#define FIO_MAP_NAME fio___http_hmap +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL char * +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio___http_str_cached_with_static((src)) +#define FIO_MAP_KEY_DISCARD(key) +#define FIO_MAP_VALUE fio___http_sary_s +#define FIO_MAP_VALUE_COPY(a, b) \ + do { \ + (a) = (fio___http_sary_s)FIO_ARRAY_INIT; \ + (void)(b); \ + } while (0) /*no-op*/ +#define FIO_MAP_VALUE_DESTROY(o) fio___http_sary_destroy(&(o)) +#define FIO_MAP_HASH_FN(k) \ + fio_risky_hash((k).buf, (k).len, (uint64_t)(uintptr_t)fio___http_sary_destroy) +#include FIO_INCLUDE_FILE - if (div != eol) - while (eol[-1] == ' ' || eol[-1] == '\t') - --eol; - value = FIO_BUF_INFO2((div == eol) ? NULL : div, (size_t)(eol - div)); - int r = handler(p, name, value, udata); - if (FIO_UNLIKELY(r)) - return r; +#if FIO_HTTP_ENFORCE_LOWERCASE_HEADERS +#define FIO___HTTP_ENFORCE_LOWERCASE(var_name, inpute_var) \ + FIO_STR_INFO_TMP_VAR(var_name, 4096); \ + fio___http_hmap_key_to_lower(&var_name, &inpute_var); + +/** Converts a Header key to lower-case */ +FIO_IFUNC void fio___http_hmap_key_to_lower(fio_str_info_s *t, + fio_str_info_s *k) { + if (k->len >= t->capa) + goto too_big; + for (size_t i = 0; i < k->len; ++i) { + uint8_t c = (uint8_t)k->buf[i]; + c |= (uint8_t)(c >= 'A' || c <= 'Z') << 5; + t->buf[i] = c; } + t->len = k->len; + return; +too_big: + *t = *k; +} -headers_finished: - if (p->fn == fio_http1___read_header_post_expect && p->expected && - fio_http1_on_expect(udata)) - goto expect_failed; - p->fn = (!p->expected) ? fio_http1___finish - : (!(p->expected + 1)) ? fio_http1___read_body_chunked - : fio_http1___read_body; - return p->fn(p, buf, udata); +#else +#define FIO___HTTP_ENFORCE_LOWERCASE(var_name, inpute_var) \ + fio_str_info_s var_name = inpute_var; +#endif -expect_failed: - *p = (fio_http1_parser_s){0}; - return 1; -} +/** set `add` to positive to add multiple values or negative to overwrite. */ +FIO_IFUNC fio_str_info_s fio___http_hmap_set2(fio___http_hmap_s *map, + fio_str_info_s key_input, + fio_str_info_s val, + int add) { + fio_str_info_s r = {0}; + if (!key_input.buf || !key_input.len || !map) + return r; + /* make sure key is all lower-case? */ + FIO___HTTP_ENFORCE_LOWERCASE(key, key_input); + fio___http_sary_s *o; + if (!val.buf || !val.len) + goto remove_key; + o = fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); + if (!o) { + fio___http_sary_s va = {0}; + o = fio___http_hmap_node2val_ptr( + fio___http_hmap_set_ptr(map, key, va, NULL, 1)); + add = 1; + } + if (FIO_UNLIKELY(!o)) { + FIO_LOG_ERROR("Couldn't add value to header: %.*s:%.*s", + (int)key.len, + key.buf, + (int)val.len, + val.buf); + return r; + } + if (add) { + if (add < 0) { + fio___http_sary_destroy(o); + } + r = fio_bstr_info(fio___http_str_cached(FIO___HTTP_STR_CACHE_VALUE, val)); + fio___http_sary_push(o, r.buf); + return r; + } + r = fio_bstr_info(fio___http_sary_get(o, -1)); + return r; -/* parsing stage 1 - read headers. */ -static int fio_http1___read_header(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - return fio_http1___read_header_line(p, buf, udata, fio_http1___on_header); +remove_key: + if (add < 1) + fio___http_hmap_remove(map, key, NULL); + return r; } -/* parsing stage 1 - read headers (after `expect` header). */ -static int fio_http1___read_header_post_expect(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - return fio_http1___read_header_line(p, buf, udata, fio_http1___on_header); +FIO_IFUNC fio_str_info_s fio___http_hmap_get2(fio___http_hmap_s *map, + fio_str_info_s key_input, + int32_t index) { + fio_str_info_s r = {0}; + FIO___HTTP_ENFORCE_LOWERCASE(key, key_input); + fio___http_sary_s *a = + fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); + if (!a) + return r; + const uint32_t count = fio___http_sary_count(a); + if (!count) + return r; + if (index < 0) { + index += count; + if (index < 0) + index = 0; + } + if ((uint32_t)index >= count) + return r; + r = fio_bstr_info(fio___http_sary_get(a, index)); + return r; } -/* parsing stage 1 - read headers. */ -static int fio_http1___read_trailer(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - return fio_http1___read_header_line(p, buf, udata, fio_http1___on_trailer); +FIO_IFUNC size_t fio___http_hmap_count2(fio___http_hmap_s *map, + fio_str_info_s key) { + size_t r = 0; + fio___http_sary_s *a = + fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, key)); + if (!a) + return r; + r = fio___http_sary_count(a); + return r; } /* ***************************************************************************** -Reading the Body +Header iteration Task ***************************************************************************** */ -/* parsing stage 2 - read body - known content length. */ -static int fio_http1___read_body(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - if (!buf->len) - return 1; - if (buf->len >= p->expected) { - buf->len = p->expected; - if (fio_http1_on_body_chunk(*buf, udata)) +typedef struct { + fio_http_s *h; + int (*callback)(fio_http_s *, fio_str_info_s, fio_str_info_s, void *); + void *udata; +} fio___http_hmap_each_info_s; + +FIO_SFUNC int fio___http_h_each_task_wrapper(fio___http_hmap_each_s *e) { + fio___http_hmap_each_info_s *data = (fio___http_hmap_each_info_s *)(e->udata); + FIO_ARRAY_EACH(fio___http_sary, &e->value, pos) { + if (data->callback(data->h, e->key, fio_bstr_info(*pos), data->udata) == -1) return -1; - buf->buf += buf->len; - return fio_http1___finish(p, buf, udata); } - if (fio_http1_on_body_chunk(*buf, udata)) - return -1; - buf->buf += buf->len; - p->expected -= buf->len; - buf->len = 0; - return 1; + return 0; } /* ***************************************************************************** -Reading the Body (chunked) +Cookie Maps ***************************************************************************** */ -/* parsing stage 2 - read chunked body - read chunk data. */ -static int fio_http1___read_body_chunked_read(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - if (!buf->len) - return 1; - if (buf->len >= p->expected) { - if (fio_http1_on_body_chunk(FIO_BUF_INFO2(buf->buf, p->expected), udata)) - return -1; - buf->buf += p->expected; - buf->len -= p->expected; - p->fn = fio_http1___read_body_chunked; - return 0; - } - if (fio_http1_on_body_chunk(buf[0], udata)) - return -1; - p->expected -= buf->len; - buf->buf += buf->len; - return 1; -} +#define FIO_MAP_NAME fio___http_cmap /* cached names */ +#define FIO_MAP_KEY fio_str_info_s +#define FIO_MAP_KEY_INTERNAL char * +#define FIO_MAP_KEY_FROM_INTERNAL(k) fio_bstr_info((k)) +#define FIO_MAP_KEY_CMP(a, b) fio_bstr_is_eq2info((a), (b)) +#define FIO_MAP_KEY_DESTROY(key) fio_bstr_free((key)) +#define FIO_MAP_KEY_COPY(dest, src) \ + (dest) = fio___http_str_cached(FIO___HTTP_STR_CACHE_COOKIE, (src)) +#define FIO_MAP_KEY_DISCARD(key) -/* parsing stage 2 - read chunked body - read next chunk length. */ -static int fio_http1___read_body_chunked(fio_http1_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - (void)udata; - if (buf->len < 3) - return 1; - { /* remove possible extra EOL after chunk payload */ - size_t tmp = (buf->buf[0] == '\r'); - tmp += (buf->buf[tmp] == '\n'); - buf->len -= tmp; - buf->buf += tmp; - } +#define FIO_MAP_VALUE_BSTR /* not cached */ +#define FIO_MAP_HASH_FN(k) \ + fio_risky_hash((k).buf, (k).len, (uint64_t)(uintptr_t)fio___http_cmap_destroy) +#include FIO_INCLUDE_FILE - // if (!FIO_MEMCHR(buf->buf, '\n', buf->len)) /* prevent read overflow? */ - // return 1; +/* ***************************************************************************** +Controller Validation +***************************************************************************** */ - char *eol = buf->buf; - size_t expected = fio_atol16u(&eol); /* may read overflow, tests after */ - if (eol == buf->buf) - return -1; - eol += (eol[0] == '\r'); - if (eol >= buf->buf + buf->len) - return 1; /* read overflowed */ - if (eol[0] != '\n') - return -1; - ++eol; - p->expected = expected; - if (p->expected) { - /* further data expected */ - buf->len -= eol - buf->buf; - buf->buf = eol; - return (p->fn = fio_http1___read_body_chunked_read)(p, buf, udata); - } - if ((eol + 1 < buf->buf + buf->len) && (eol[0] == '\r' || eol[0] == '\n')) { - /* no trailers, finish now. */ - eol += (eol[0] == '\r'); - ++eol; - buf->len -= eol - buf->buf; - buf->buf = eol; - return fio_http1___finish(p, buf, udata); +FIO_SFUNC int fio___mock_controller_get_fd_cb(fio_http_s *h) { + return -1; + (void)h; +} +FIO_SFUNC void fio___mock_controller_cb(fio_http_s *h) { (void)h; } +FIO_SFUNC void fio___mock_c_write_body(fio_http_s *h, + fio_http_write_args_s args) { + if (args.buf) { + if (args.dealloc) + args.dealloc((void *)args.buf); + } else if ((unsigned)(args.fd + 1) > 1U && !args.copy && + args.fd != fio_http_body_fd(h)) { + close(args.fd); } - /* possible trailers */ - buf->len -= eol - buf->buf; - buf->buf = eol; - return (p->fn = fio_http1___read_trailer)(p, buf, udata); + (void)h; +} + +static fio_http_controller_s FIO___MOCK_CONTROLLER = { + .on_destroyed = fio___mock_controller_cb, + .send_headers = fio___mock_controller_cb, + .write_body = fio___mock_c_write_body, + .on_finish = fio___mock_controller_cb, + .close_io = fio___mock_controller_cb, + .get_fd = fio___mock_controller_get_fd_cb, +}; + +SFUNC fio_http_controller_s *fio___http_controller_validate( + fio_http_controller_s *c) { + if (!c) + c = &FIO___MOCK_CONTROLLER; + if (c->private_flags) + return c; + if (!c->on_destroyed) + c->on_destroyed = fio___mock_controller_cb; + if (!c->send_headers) + c->send_headers = fio___mock_controller_cb; + if (!c->write_body) + c->write_body = fio___mock_c_write_body; + if (!c->on_finish) + c->on_finish = fio___mock_controller_cb; + if (!c->close_io) + c->close_io = fio___mock_controller_cb; + if (!c->get_fd) + c->get_fd = fio___mock_controller_get_fd_cb; + return c; } /* ***************************************************************************** -Cleanup +HTTP Handle Type ***************************************************************************** */ -#undef FIO_HTTP1_PARSER -#endif /* FIO_HTTP1_PARSER && FIO_EXTERN_COMPLETE*/ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_BITWISE /* Development inclusion - ignore line */ -#define FIO_RAND /* Development inclusion - ignore line */ -#define FIO_WEBSOCKET_PARSER /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** +#define FIO_HTTP_STATE_STREAMING 1 +#define FIO_HTTP_STATE_FINISHED 2 +#define FIO_HTTP_STATE_UPGRADED 4 +#define FIO_HTTP_STATE_WEBSOCKET 8 +#define FIO_HTTP_STATE_SSE 16 +#define FIO_HTTP_STATE_COOKIES_PARSED 32 +#define FIO_HTTP_STATE_FREEING 64 + +FIO_SFUNC int fio____http_write_start(fio_http_s *, fio_http_write_args_s *); +FIO_SFUNC int fio____http_write_cont(fio_http_s *, fio_http_write_args_s *); + +struct fio_http_s { + void *udata; + void *udata2; + void *cdata; + fio_http_controller_s *controller; + int (*writer)(fio_http_s *, fio_http_write_args_s *); + int64_t received_at; + size_t sent; + uint32_t state; + uint32_t status; + fio_keystr_s method; + fio_keystr_s path; + fio_keystr_s query; + fio_keystr_s version; + fio___http_hmap_s headers[2]; /* request, response */ + fio___http_cmap_s cookies[2]; /* read, write */ + struct { + char *buf; + size_t len; + size_t pos; + int fd; + } body; +}; + +#define HTTP_HDR_REQUEST(h) (h->headers + 0) +#define HTTP_HDR_RESPONSE(h) (h->headers + 1) +#define FIO_REF_NAME fio_http +#define FIO_REF_INIT(h) \ + h = (fio_http_s) { \ + .controller = &FIO___MOCK_CONTROLLER, .writer = fio____http_write_start, \ + .received_at = fio_http_get_timestump(), .body.fd = -1 \ + } +#define FIO_REF_DESTROY(h) fio_http_destroy(&(h)) +SFUNC fio_http_s *fio_http_destroy(fio_http_s *h) { + if (!h) + return h; + h->state |= FIO_HTTP_STATE_FREEING; + h->controller->on_destroyed(h); + fio_keystr_destroy(&h->method, fio___http_keystr_free); + fio_keystr_destroy(&h->path, fio___http_keystr_free); + fio_keystr_destroy(&h->query, fio___http_keystr_free); + fio_keystr_destroy(&h->version, fio___http_keystr_free); + fio___http_hmap_destroy(h->headers); + fio___http_hmap_destroy(h->headers + 1); + fio___http_cmap_destroy(h->cookies); + fio___http_cmap_destroy(h->cookies + 1); + fio_bstr_free(h->body.buf); + if (h->body.fd != -1) + close(h->body.fd); + FIO_REF_INIT(*h); + return h; +} +#include FIO_INCLUDE_FILE - WebSocket Parser +/** Clears any response data. */ +SFUNC fio_http_s *fio_http_clear_response(fio_http_s *h, bool clear_body) { + fio___http_hmap_destroy(HTTP_HDR_RESPONSE(h)); + h->state = 0; + h->writer = fio____http_write_start; + h->received_at = fio_http_get_timestump(); + h->status = 0; + if (!clear_body) + return h; + fio_bstr_free(h->body.buf); + if (h->body.fd != -1) + close(h->body.fd); + h->body.buf = NULL; + h->body.len = h->body.pos = 0; + h->body.fd = -1; + return h; +} +/** Create a new http_s handle. */ +SFUNC fio_http_s *fio_http_new(void) { return fio_http_new2(); } +/** Reduces an http_s handle's reference count or frees it. */ +SFUNC void fio_http_free(fio_http_s *h) { fio_http_free2(h); } +/** Increases an http_s handle's reference count. */ +SFUNC fio_http_s *fio_http_dup(fio_http_s *h) { return fio_http_dup2(h); } -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_WEBSOCKET_PARSER) && !defined(H___FIO_WEBSOCKET_PARSER___H) && \ - (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) -/* ***************************************************************************** -The parser provides static functions only, always as part or implementation. -***************************************************************************** */ -#define H___FIO_WEBSOCKET_PARSER___H +/** Collects an updated timestamp for logging purposes. */ +SFUNC void fio_http_start_time_set(fio_http_s *h) { + h->received_at = fio_http_get_timestump(); +} + +/** Closes a persistent HTTP connection (i.e., if upgraded). */ +SFUNC void fio_http_close(fio_http_s *h) { h->controller->close_io(h); } + +/** Creates a copy of an existing handle, copying only its request data. */ +SFUNC fio_http_s *fio_http_new_copy_request(fio_http_s *o) { + fio_http_s *h = fio_http_new(); + FIO_ASSERT_ALLOC(h); + fio_http_path_set(h, fio_http_path(o)); + fio_http_method_set(h, fio_http_method(o)); + fio_http_query_set(h, fio_http_query(o)); + fio_http_version_set(h, fio_http_version(o)); + /* copy headers */ + fio___http_hmap_reserve(h->headers, fio___http_hmap_count(o->headers)); + FIO_MAP_EACH(fio___http_hmap, o->headers, i) { + fio___http_sary_s *a = fio___http_hmap_node2val_ptr( + fio___http_hmap_set_ptr(h->headers, + i.key, + (fio___http_sary_s){0}, + NULL, + 0)); + FIO_ARRAY_EACH(fio___http_sary, &i.value, v) { + fio___http_sary_push(a, fio_bstr_copy(*v)); + } + } + /* copy cookies */ + FIO_MAP_EACH(fio___http_cmap, o->cookies, i) { + fio___http_cmap_set(h->cookies, i.key, i.value, NULL); + } + return h; +} +#undef FIO___RECURSIVE_INCLUDE /* ***************************************************************************** -WebSocket Parsing API +Simple Property Set / Get ***************************************************************************** */ -typedef struct fio_websocket_parser_s fio_websocket_parser_s; -/** - * Parses WebSocket data, calling any callbacks. - * - * Returns bytes consumed or `FIO_WEBSOCKET_PARSER_ERROR` (`(size_t)-1`) on - * error. - */ -FIO_SFUNC size_t fio_websocket_parse(fio_websocket_parser_s *p, - fio_buf_info_s buf, - void *udata); +#define HTTP___MAKE_GET_SET(property) \ + FIO_IFUNC fio_str_info_s fio_http_##property(fio_http_s *h) { \ + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); \ + return fio_keystr_info(&h->property); \ + } \ + \ + FIO_IFUNC fio_str_info_s fio_http_##property##_set(fio_http_s *h, \ + fio_str_info_s value) { \ + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); \ + fio_keystr_destroy(&h->property, fio___http_keystr_free); \ + h->property = fio_keystr_init(value, fio___http_keystr_alloc); \ + return fio_keystr_info(&h->property); \ + } -// FIO_SFUNC +HTTP___MAKE_GET_SET(method) +HTTP___MAKE_GET_SET(path) +HTTP___MAKE_GET_SET(query) +HTTP___MAKE_GET_SET(version) -/** The parsers return value on error. */ -#define FIO_WEBSOCKET_PARSER_ERROR ((size_t)-1) +#undef HTTP___MAKE_GET_SET + +FIO_IFUNC_DEF_GET(fio_http, fio_http_s, size_t, status) +/** Sets the status associated with the HTTP handle (response). */ +SFUNC size_t fio_http_status_set(fio_http_s *h, size_t status) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + if (status > 1023) + status = 500; + if (!status) + status = 200; + return (h->status = (uint32_t)status); +} /* ***************************************************************************** -WebSocket Parsing Callbacks +Handler State ***************************************************************************** */ -/** Called when a message frame was received. */ -FIO_SFUNC void fio_websocket_on_message(void *udata, - fio_buf_info_s msg, - unsigned char is_text); - -/** - * Called when the parser needs to copy the message to an external buffer. - * - * MUST return the external buffer, as it may need to be unmasked. - * - * Partial message length may be equal to zero (`partial.len == 0`). - */ -FIO_SFUNC fio_buf_info_s fio_websocket_write_partial(void *udata, - fio_buf_info_s partial, - size_t more_expected); - -/** Called when the permessage-deflate extension requires decompression. */ -FIO_SFUNC fio_buf_info_s fio_websocket_decompress(void *udata, - fio_buf_info_s msg); +SFUNC int fio_http_is_clean(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return !h->state; +} -/** Called when a `ping` message was received. */ -FIO_SFUNC void fio_websocket_on_protocol_ping(void *udata, fio_buf_info_s msg); +/** Returns true if the HTTP handle's response was sent. */ +SFUNC int fio_http_is_finished(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_FINISHED)); +} -/** Called when a `pong` message was received. */ -FIO_SFUNC void fio_websocket_on_protocol_pong(void *udata, fio_buf_info_s msg); +/** Returns true if handle is in the process of freeing itself. */ +SFUNC int fio_http_is_freeing(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_FREEING)); +} -/** Called when a `close` message was received. */ -FIO_SFUNC void fio_websocket_on_protocol_close(void *udata, fio_buf_info_s msg); +/** Returns true if the HTTP handle's response is streaming. */ +SFUNC int fio_http_is_streaming(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_STREAMING)); +} -/* ***************************************************************************** -WebSocket Formatting API -***************************************************************************** */ -/** - * Returns the length of the buffer required to wrap a message `len` long - * - * Client connections should add 4 to this number to accommodate for the mask. - */ -FIO_IFUNC uint64_t fio_websocket_wrapped_len(uint64_t len); +/** Returns true if the HTTP connection was (or should have been) upgraded. */ +SFUNC int fio_http_is_upgraded(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_UPGRADED)); +} -/** - * Wraps a WebSocket server message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * * client: set to 1 to use client mode (data masking). - * - * Further opcode values: - * * %x0 denotes a continuation frame - * * %x1 denotes a text frame - * * %x2 denotes a binary frame - * * %x3-7 are reserved for further non-control frames - * * %x8 denotes a connection close - * * %x9 denotes a ping - * * %xA denotes a pong - * * %xB-F are reserved for further control frames - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len)` - */ -FIO_SFUNC uint64_t fio_websocket_server_wrap(void *target, - const void *msg, - uint64_t len, - unsigned char opcode, - unsigned char first, - unsigned char last, - unsigned char rsv); +/** Returns true if the HTTP handle establishes a WebSocket Upgrade. */ +SFUNC int fio_http_is_websocket(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_WEBSOCKET)); +} -/** - * Wraps a WebSocket client message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * * client: set to 1 to use client mode (data masking). - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4` - */ -FIO_SFUNC uint64_t fio_websocket_client_wrap(void *target, - const void *msg, - uint64_t len, - unsigned char opcode, - unsigned char first, - unsigned char last, - unsigned char rsv); +/** Returns true if the HTTP handle establishes an EventSource connection. */ +SFUNC int fio_http_is_sse(fio_http_s *h) { + FIO_ASSERT_DEBUG(h, "NULL HTTP handler!"); + return (!!(h->state & FIO_HTTP_STATE_SSE)); +} /* ***************************************************************************** -API - Parsing (unwrapping) +Header Data Management ***************************************************************************** */ -/* ***************************************************************************** +#define FIO___HTTP_HEADER_SET_FN(category, name_, headers, add_val) \ + /** Sets the header information associated with the HTTP handle. */ \ + fio_str_info_s fio_http_##category##_header_##name_(fio_http_s *h, \ + fio_str_info_s name, \ + fio_str_info_s value) { \ + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); \ + return fio___http_hmap_set2(headers(h), name, value, add_val); \ + } +FIO___HTTP_HEADER_SET_FN(request, set, HTTP_HDR_REQUEST, -1) +FIO___HTTP_HEADER_SET_FN(request, set_if_missing, HTTP_HDR_REQUEST, 0) +FIO___HTTP_HEADER_SET_FN(request, add, HTTP_HDR_REQUEST, 1) +FIO___HTTP_HEADER_SET_FN(response, set, HTTP_HDR_RESPONSE, -1) +FIO___HTTP_HEADER_SET_FN(response, set_if_missing, HTTP_HDR_RESPONSE, 0) +FIO___HTTP_HEADER_SET_FN(response, add, HTTP_HDR_RESPONSE, 1) +#undef FIO___HTTP_HEADER_SET_FN + +fio_str_info_s fio_http_request_header(fio_http_s *h, + fio_str_info_s name, + size_t index) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + return fio___http_hmap_get2(HTTP_HDR_REQUEST(h), name, (int32_t)index); +} +fio_str_info_s fio_http_response_header(fio_http_s *h, + fio_str_info_s name, + size_t index) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + return fio___http_hmap_get2(HTTP_HDR_RESPONSE(h), name, (int32_t)index); +} - Implementation +/** Returns the number of headers named `name` that were received. */ +SFUNC size_t fio_http_request_header_count(fio_http_s *h, fio_str_info_s name) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!name.buf) + return fio___http_hmap_count(HTTP_HDR_REQUEST(h)); + return fio___http_hmap_count2(HTTP_HDR_REQUEST(h), name); +} +/** Returns the number of headers named `name` that were received. */ +SFUNC size_t fio_http_response_header_count(fio_http_s *h, + fio_str_info_s name) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!name.buf) + return fio___http_hmap_count(HTTP_HDR_RESPONSE(h)); + return fio___http_hmap_count2(HTTP_HDR_RESPONSE(h), name); +} -***************************************************************************** */ +/** Iterates through all headers. A non-zero return will stop iteration. */ +size_t fio_http_request_header_each(fio_http_s *h, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!callback) + return fio___http_hmap_count(HTTP_HDR_REQUEST(h)); + fio___http_hmap_each_info_s d = {.h = h, + .callback = callback, + .udata = udata}; + return fio___http_hmap_each(HTTP_HDR_REQUEST(h), + fio___http_h_each_task_wrapper, + &d, + 0); +} -/** returns the length of the buffer required to wrap a message `len` long */ -FIO_IFUNC uint64_t fio_websocket_wrapped_len(uint64_t len) { - return len + 2ULL + ((len > 125) << 1) + - ((0ULL - (len > ((1UL << 16) - 1))) & 6ULL); +/** Iterates through all headers. A non-zero return will stop iteration. */ +size_t fio_http_response_header_each( + fio_http_s *h, + int (*callback)(fio_http_s *, fio_str_info_s, fio_str_info_s, void *), + void *udata) { + FIO_ASSERT_DEBUG(h, "NULL HTTP Handle!"); + if (!callback) + return fio___http_hmap_count(HTTP_HDR_RESPONSE(h)); + fio___http_hmap_each_info_s d = {.h = h, + .callback = callback, + .udata = udata}; + return fio___http_hmap_each(HTTP_HDR_RESPONSE(h), + fio___http_h_each_task_wrapper, + &d, + 0); } /* ***************************************************************************** -Message Wrapping +Cookies ***************************************************************************** */ -FIO_IFUNC uint64_t fio_websocket_header(void *target, - uint64_t message_len, - uint32_t mask, - unsigned char opcode, - unsigned char first, - unsigned char last, - unsigned char rsv) { - ((uint8_t *)target)[0] = 0U | - /*fin*/ ((last & 1U) << 7) | - /* opcode */ ((16U - !!first) & (opcode & 15U)) | - /* rsv */ ((rsv & 7) << 4); - ((uint8_t *)target)[1] = ((!!mask) << 7U); - size_t mask_l = ((!!mask) << 2); - if (message_len < 126) { - ((uint8_t *)target)[1] |= message_len; - if (mask) - fio_u2buf32u(((uint8_t *)target + 2), mask); - return 2 + mask_l; - } else if (message_len < (1UL << 16)) { - /* head is 4 bytes */ - ((uint8_t *)target)[1] |= 126; - fio_u2buf16_be(((uint8_t *)target + 2), message_len); - if (mask) - fio_u2buf32u(((uint8_t *)target + 4), mask); - return 4 + mask_l; - } else { - /* Really Long Message */ - ((uint8_t *)target)[1] |= 127; - fio_u2buf64_be(((uint8_t *)target + 2), message_len); - if (mask) - fio_u2buf32u(((uint8_t *)target + 10), mask); - return 10 + mask_l; +/** (Helper) HTTP Cookie Parser */ +FIO_IFUNC void fio___http_cookie_parse_cookie(fio_http_s *h, fio_str_info_s s) { + /* loop and read Cookie: name=value; name2=value2; name3=value3 */ + while (s.len) { + fio_str_info_s k = {0}, v = {0}; + /* remove white-space */ + while ((s.buf[0] == ' ' || s.buf[0] == '\t') && s.len) { + ++s.buf; + --s.len; + } + if (!s.len) + return; + char *div = (char *)FIO_MEMCHR(s.buf, '=', s.len); + char *end = (char *)FIO_MEMCHR(s.buf, ';', s.len); + if (!end) + end = s.buf + s.len; + v.buf = s.buf; + if (div) { + /* cookie name may be an empty string */ + k.buf = s.buf; + k.len = div - s.buf; + v.buf = div + 1; + } + v.len = end - v.buf; + s.len = (s.buf + s.len) - end; + s.buf = end; + /* skip the ';' if exists (if len is not zero, !!s.len == 1). */ + s.buf += !!s.len; + s.len -= !!s.len; + fio___http_cmap_set_if_missing(h->cookies, k, v); } } -/** - * Wraps a WebSocket server message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * * client: set to 1 to use client mode (data masking). - * - * Further opcode values: - * * %x0 denotes a continuation frame - * * %x1 denotes a text frame - * * %x2 denotes a binary frame - * * %x3-7 are reserved for further non-control frames - * * %x8 denotes a connection close - * * %x9 denotes a ping - * * %xA denotes a pong - * * %xB-F are reserved for further control frames - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len)` - */ -FIO_SFUNC uint64_t fio_websocket_server_wrap(void *restrict target, - const void *restrict msg, - uint64_t len, - unsigned char opcode, - unsigned char first, - unsigned char last, - unsigned char rsv) { - uint64_t r = fio_websocket_header(target, len, 0, opcode, first, last, rsv); - FIO_MEMCPY(((uint8_t *)target) + r, msg, len); - r += len; - return r; +/** (Helper) HTTP Cookie Parser */ +FIO_IFUNC void fio___http_cookie_parse_set_cookie(fio_http_s *h, + fio_str_info_s s) { + /* TODO! */ + fio_str_info_s k = {0}, v = {0}; + /* remove white-space */ + while ((s.buf[0] == ' ' || s.buf[0] == '\t') && s.len) { + ++s.buf; + --s.len; + } + if (!s.len) + return; + char *div = (char *)FIO_MEMCHR(s.buf, '=', s.len); + char *end = (char *)FIO_MEMCHR(s.buf, ';', s.len); + if (div == s.buf || !div) + return; + if (!end) + end = s.buf + s.len; + const uint64_t prefix_secure = fio_buf2u64u("_Secure-"); + const uint32_t prefix_host = fio_buf2u32u("Host"); + uint32_t cont; + k.buf = s.buf; + k.len = div - s.buf; + v.buf = div + 1; + v.len = end - v.buf; + do { /* loop to clear away cookie prefixes in any order */ + cont = 0; + if (k.len > 8 && k.buf[0] == '_' && + fio_buf2u64u(k.buf + 1) == prefix_secure) { + cont = 1; + k.len -= 9; + k.buf += 9; + } + if (k.len > 6 && k.buf[0] == '_' && k.buf[1] == '_' && k.buf[6] == '-' && + fio_buf2u32u(k.buf + 2) == prefix_host) { + cont = 1; + k.len -= 7; + k.buf += 7; + } + } while (cont); + if (k.len) + fio___http_cmap_set_if_missing(h->cookies, k, v); } -/** - * Wraps a WebSocket client message and writes it to the target buffer. - * - * The `first` and `last` flags can be used to support message fragmentation. - * - * * target: the target buffer to write to. - * * msg: the message to be wrapped. - * * len: the message length. - * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. - * * first: set to 1 if `msg` points the beginning of the message. - * * last: set to 1 if `msg + len` ends the message. - * - * Returns the number of bytes written. Always `websocket_wrapped_len(len) + - * 4` - */ -FIO_SFUNC uint64_t fio_websocket_client_wrap(void *restrict target, - const void *restrict msg, - uint64_t len, - unsigned char opcode, - unsigned char first, - unsigned char last, - unsigned char rsv) { - uint64_t mask = (fio_rand64() | 0x01020408ULL) & 0xFFFFFFFFULL; /* non-zero */ - mask |= mask << 32; - uint64_t r = fio_websocket_header(target, - len, - (uint32_t)mask, - opcode, - first, - last, - rsv); - fio_xmask_cpy((((char *)target) + r), (const char *)msg, len, mask); - r += len; - return r; +/** (Helper) Parses all HTTP Cookies */ +FIO_SFUNC void fio___http_cookie_collect(fio_http_s *h) { + fio___http_sary_s *header = NULL; + header = fio___http_hmap_node2val_ptr( + fio___http_hmap_get_ptr(h->headers, FIO_STR_INFO1((char *)"cookie"))); + if (header) { + FIO_ARRAY_EACH(fio___http_sary, header, pos) { + fio___http_cookie_parse_cookie(h, fio_bstr_info(*pos)); + } + } + /* if headers were sent, set-cookie data might belong to the handle */ + if (h->writer != fio____http_write_start) + return; + header = fio___http_hmap_node2val_ptr( + fio___http_hmap_get_ptr(h->headers + 1, + FIO_STR_INFO1((char *)"set-cookie"))); + if (!header) + return; + FIO_ARRAY_EACH(fio___http_sary, header, pos) { + fio___http_cookie_parse_set_cookie(h, fio_bstr_info(*pos)); + } + return; } -/* ***************************************************************************** -WebSocket Parser Type -***************************************************************************** */ - -/** The WebSocket parser type implementation */ -struct fio_websocket_parser_s { - int (*fn)(fio_websocket_parser_s *, fio_buf_info_s *, void *); - uint64_t start_at; - uint64_t expect; - uint32_t mask; - uint8_t first; - uint8_t current; - uint8_t must_mask; -}; - -/* ***************************************************************************** -Frame Consumption -***************************************************************************** */ -FIO_SFUNC int fio___websocket_consume_header(fio_websocket_parser_s *p, - fio_buf_info_s *buf, - void *udata); +int fio_http_cookie_set___(void); /* IDE Marker */ +/* Sets a response cookie. */ +SFUNC int fio_http_cookie_set FIO_NOOP(fio_http_s *h, + fio_http_cookie_args_s cookie) { + FIO_ASSERT_DEBUG(h, "Can't set cookie for NULL HTTP handler!"); + if (!h || (h->state & (FIO_HTTP_STATE_FINISHED | FIO_HTTP_STATE_STREAMING))) + return -1; + /* promises that some warnings print only once. */ + static unsigned int warn_illegal = 0; + unsigned int need2warn = 0; -FIO_SFUNC int fio___websocket_consume_frame_partial(fio_websocket_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - fio_websocket_write_partial(udata, *buf, (p->expect -= buf->len)); - buf->buf += buf->len; - buf->len = 0; - return 1; -} + /* valid / invalid characters in cookies, create with Ruby using: + a = [] + 256.times {|i| a[i] = 1;} + ('a'.ord..'z'.ord).each {|i| a[i] = 0;} + ('A'.ord..'Z'.ord).each {|i| a[i] = 0;} + ('0'.ord..'9'.ord).each {|i| a[i] = 0;} + "!#$%&'*+-.^_`|~".bytes.each {|i| a[i] = 0;} + p a; nil + "!#$%&'()*+-./:<=>?@[]^_`{|}~".bytes.each {|i| a[i] = 0;} # for values + p a; nil + */ + static const char invalid_cookie_name_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + static const char invalid_cookie_value_char[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + /* write name and value while auto-correcting encoding issues */ + if ((cookie.name.len + cookie.value.len + cookie.domain.len + + cookie.path.len + 128) > 5119) { + FIO_LOG_ERROR("cookie data too long!"); + } + char tmp_buf[5120]; + fio_str_info_s t = FIO_STR_INFO3(tmp_buf, 0, 5119); -FIO_SFUNC int fio___websocket_consume_frame_finish(fio_websocket_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - fio_buf_info_s msg = FIO_BUF_INFO2(buf->buf, p->expect); - buf->buf += p->expect; - buf->len -= p->expect; - p->expect = 0; - msg = fio_websocket_write_partial(udata, msg, 0); - if (!msg.buf) /* protocol error response from callback */ - return -1; - fio_xmask(msg.buf + p->start_at, - msg.len - p->start_at, - (((uint64_t)p->mask) << 32) | (uint64_t)p->mask); - p->start_at += msg.len; - p->fn = fio___websocket_consume_header; - if (!(p->current & 128)) /* done? if not, consume next frame */ - return 0; - /* done */ - if (p->first & 64) { /* RSV1 set: decompress */ - msg = fio_websocket_decompress(udata, msg); - if (!msg.buf) - return -1; +#define fio___http_h_copy_cookie_ch(ch_var) \ + if (!invalid_cookie_##ch_var##_char[(uint8_t)cookie.ch_var.buf[tmp]]) { \ + t.buf[t.len++] = cookie.ch_var.buf[tmp]; \ + } else { \ + need2warn |= 1; \ + t.buf[t.len++] = '%'; \ + t.buf[t.len++] = fio_i2c(((uint8_t)cookie.ch_var.buf[tmp] >> 4) & 0x0F); \ + t.buf[t.len++] = fio_i2c((uint8_t)cookie.ch_var.buf[tmp] & 0x0F); \ + } \ + tmp += 1; \ + if (t.capa <= t.len + 3) { \ + ((t.buf == tmp_buf) \ + ? FIO_STRING_ALLOC_COPY \ + : FIO_STRING_REALLOC)(&t, fio_string_capa4len(t.len + 3)); \ } - size_t cond = (p->first & 15); - *p = (fio_websocket_parser_s){.fn = fio___websocket_consume_header}; - switch (cond) { - case 0: return -1; /* continuation - error? */ - case 1: /* fall through */ /* text / data frame */ - case 2: fio_websocket_on_message(udata, msg, (cond & 1)); return 1; - case 8: fio_websocket_on_protocol_close(udata, msg); return 1; - case 9: fio_websocket_on_protocol_ping(udata, msg); return 1; - case 10: fio_websocket_on_protocol_pong(udata, msg); return 1; - default: - FIO_LOG_DDEBUG2("ERROR: WebSocket protocol error - unknown opcode %u\n", - (unsigned int)(p->first & 15)); - return -1; + + if (cookie.name.buf) { + size_t tmp = 0; + if (cookie.name.len) { + while (tmp < cookie.name.len) { + fio___http_h_copy_cookie_ch(name); + } + } else { + while (cookie.name.buf[tmp]) { + fio___http_h_copy_cookie_ch(name); + } + } + if (need2warn && !warn_illegal) { + warn_illegal |= 1; + FIO_LOG_WARNING("illegal char 0x%.2x in cookie name (in %s)\n" + " automatic %% encoding applied", + cookie.name.buf[tmp], + cookie.name.buf); + } } - return 1; -} + t.buf[t.len++] = '='; + if (cookie.value.buf) { + size_t tmp = 0; + if (cookie.value.len) { + while (tmp < cookie.value.len) { + fio___http_h_copy_cookie_ch(value); + } + } else { + while (cookie.value.buf[tmp]) { + fio___http_h_copy_cookie_ch(value); + } + } + if (need2warn && !warn_illegal) { + warn_illegal |= 1; + FIO_LOG_WARNING("illegal char 0x%.2x in cookie value (in %s)\n" + " automatic %% encoding applied", + cookie.value.buf[tmp], + cookie.value.buf); + } + } else + cookie.max_age = -1; +#undef fio___http_h_copy_cookie_ch -FIO_SFUNC int fio___websocket_consume_frame(fio_websocket_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - return (p->expect > buf->len - ? fio___websocket_consume_frame_partial - : fio___websocket_consume_frame_finish)(p, buf, udata); -} + /* server cookie data */ + t.buf[t.len++] = ';'; + t.buf[t.len++] = ' '; -/* ***************************************************************************** -Header Consumption -***************************************************************************** */ -FIO_SFUNC int fio___websocket_consume_header(fio_websocket_parser_s *p, - fio_buf_info_s *buf, - void *udata) { - if (buf->len < 2) - return 1; - const uint8_t mask_f = (((uint8_t *)buf->buf)[1] >> 7) & 1; - const uint8_t mask_l = (mask_f << 2); - const uint8_t info = (uint8_t)(buf->buf[0]); - uint8_t len_indicator = ((((uint8_t *)buf->buf)[1]) & 127U); - switch (len_indicator) { - case 126: - if (buf->len < 8UL) - return 1; - p->expect = fio_buf2u16_be(buf->buf + 2); - p->mask = (0ULL - mask_f) & fio_buf2u32u(buf->buf + 4); - buf->buf += 4 + mask_l; - buf->len -= 4 + mask_l; + if (cookie.max_age) { + fio_string_write2( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + FIO_STRING_WRITE_STR2((char *)"Max-Age=", 8), + FIO_STRING_WRITE_NUM(cookie.max_age), + FIO_STRING_WRITE_STR2((char *)"; ", 2)); + } + + if (cookie.domain.buf && cookie.domain.len) { + fio_string_write2( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + FIO_STRING_WRITE_STR2((char *)"domain=", 7), + FIO_STRING_WRITE_STR2((char *)cookie.domain.buf, cookie.domain.len), + FIO_STRING_WRITE_STR2((char *)"; ", 2)); + } + if (cookie.path.buf && cookie.path.len) { + fio_string_write2( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + FIO_STRING_WRITE_STR2((char *)"path=", 5), + FIO_STRING_WRITE_STR2((char *)cookie.path.buf, cookie.path.len), + FIO_STRING_WRITE_STR2((char *)"; ", 2)); + } + if (cookie.http_only) { + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "HttpOnly; ", + 10); + } + if (cookie.secure) { + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "secure; ", + 8); + } + if (cookie.partitioned) { + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "partitioned; ", + 13); + } + switch (cookie.same_site) { + case FIO_HTTP_COOKIE_SAME_SITE_BROWSER_DEFAULT: /* fall through */ + default: break; + case FIO_HTTP_COOKIE_SAME_SITE_NONE: + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "SameSite=None;", + 14); break; - - case 127: - if (buf->len < 14UL) - return 1; - p->expect = fio_buf2u64_be(buf->buf + 2); - if (p->expect & 0xFF00000000000000ULL) - return -1; /* really?! */ - p->mask = (0ULL - mask_f) & fio_buf2u32u(buf->buf + 10); - buf->buf += 10 + mask_l; - buf->len -= 10 + mask_l; + case FIO_HTTP_COOKIE_SAME_SITE_LAX: + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "SameSite=Lax;", + 13); break; - - default: - if (buf->len < (2ULL + mask_l)) - return 1; - p->expect = len_indicator; - p->mask = mask_f ? fio_buf2u32u(buf->buf + 2) : 0; - buf->buf += 2 + mask_l; - buf->len -= 2 + mask_l; + case FIO_HTTP_COOKIE_SAME_SITE_STRICT: + fio_string_write( + &t, + ((t.buf == tmp_buf) ? FIO_STRING_ALLOC_COPY : FIO_STRING_REALLOC), + "SameSite=Strict;", + 16); break; } - if (p->first) { - p->current = info; - if ((info & 15)) /* continuation frame == 0 ; is it missing? */ - return -1; - } else { - p->first = p->current = info; - p->start_at = 0; - if (!(info & 15)) /* continuation frame == 0 ; where's the first? */ - return -1; + if (t.buf[t.len - 1] == ' ') + --t.len; + + /* set the "write" cookie store data */ + fio___http_cmap_set(h->cookies + 1, cookie.name, t, NULL); + /* set the "read" cookie store data */ + fio___http_cmap_set(h->cookies, cookie.name, cookie.value, NULL); + if (t.buf != tmp_buf) + FIO_STRING_FREE2(t); + return 0; +} + +/** Returns a cookie value (either received of newly set), if any. */ +SFUNC fio_str_info_s fio_http_cookie(fio_http_s *h, + const char *name, + size_t name_len) { + if (!(fio_atomic_or(&h->state, FIO_HTTP_STATE_COOKIES_PARSED) & + FIO_HTTP_STATE_COOKIES_PARSED)) + fio___http_cookie_collect(h); + fio_str_info_s r = + fio___http_cmap_get(h->cookies, FIO_STR_INFO2((char *)name, name_len)); + return r; +} + +/** Iterates through all cookies. A non-zero return will stop iteration. */ +SFUNC size_t fio_http_cookie_each(fio_http_s *h, + int (*callback)(fio_http_s *, + fio_str_info_s name, + fio_str_info_s value, + void *udata), + void *udata) { + if (!(fio_atomic_or(&h->state, FIO_HTTP_STATE_COOKIES_PARSED) & + FIO_HTTP_STATE_COOKIES_PARSED)) + fio___http_cookie_collect(h); + size_t i = 0; + FIO_MAP_EACH(fio___http_cmap, h->cookies, pos) { + ++i; + if (callback(h, pos.key, pos.value, udata)) + return i; } - if (p->must_mask && !p->mask) - return -1; - return (p->fn = fio___websocket_consume_frame)(p, buf, udata); + return i; } -/* ***************************************************************************** -Main Parsing Loop -***************************************************************************** */ -FIO_SFUNC size_t fio_websocket_parse(fio_websocket_parser_s *p, - fio_buf_info_s buf, - void *udata) { - int i = 0; - char *buf_start = buf.buf; - if (!buf.len) - return 0; - if (!p->fn) - p->fn = fio___websocket_consume_header; - while (!(i = p->fn(p, &buf, udata))) - ; - if (i < 0) - return FIO_WEBSOCKET_PARSER_ERROR; - return buf.buf - buf_start; +/** + * Iterates through all response set cookies. + * + * A non-zero return value from the callback will stop iteration. + */ +SFUNC size_t +fio_http_set_cookie_each(fio_http_s *h, + int (*callback)(fio_http_s *h, + fio_str_info_s set_cookie_header, + fio_str_info_s value, + void *udata), + void *udata) { + size_t i = 0; + fio___http_cmap_s *set_cookies = h->cookies + 1; + fio_str_info_s header_name = FIO_STR_INFO2((char *)"set-cookie", 10); + FIO_MAP_EACH(fio___http_cmap, set_cookies, pos) { + ++i; + if (callback(h, header_name, pos.value, udata)) + return i; + } + return i; } /* ***************************************************************************** -Reading the first line +Peer Address ***************************************************************************** */ -/* ***************************************************************************** -Cleanup -***************************************************************************** */ +/** + * Writes peer address to `dest` starting with the `forwarded` header, with a + * fallback to actual socket address and a final fallback to `"[unknown]"`. + * + * If `unknown` is returned, the function returns -1. if `dest` capacity is too + * small, the number of bytes required will be returned. + * + * If all goes well, this function returns 0. + */ +SFUNC int fio_http_from(fio_str_info_s *dest, const fio_http_s *h) { + int r = 0; + /* Guess IP address from headers (forwarded) where possible */ + fio_str_info_s forwarded = + fio_http_request_header((fio_http_s *)h, + FIO_STR_INFO2((char *)"forwarded", 9), + -1); + fio_buf_info_s buf; + char *end; + if (forwarded.len) { + forwarded.len &= 1023; /* limit possible attack surface */ + for (; forwarded.len > 5;) { + if ((forwarded.buf[0] | 32) != 'f' || (forwarded.buf[1] | 32) != 'o' || + (forwarded.buf[2] | 32) != 'r' || forwarded.buf[3] != '=') { + ++forwarded.buf; + --forwarded.len; + continue; + } + forwarded.buf += 4 + (forwarded.buf[4] == '"'); + break; + } + client_address_found: + buf.buf = end = forwarded.buf; + while (*end && *end != '"' && *end != ',' && *end != ' ' && *end != ';' && + (end - forwarded.buf) < 48) + ++end; + buf.len = (size_t)(end - forwarded.buf); + } else { + forwarded = + fio_http_request_header((fio_http_s *)h, + FIO_STR_INFO2((char *)"x-forwarded-for", 15), + -1); + if (forwarded.len) { + forwarded.buf += (forwarded.buf[0] == '"'); + goto client_address_found; + } +#if defined(H___FIO_SOCK___H) + if (!(buf = fio_sock_peer_addr( + fio_http_controller((fio_http_s *)h)->get_fd((fio_http_s *)h))) + .len) +#endif + buf = FIO_BUF_INFO1((char *)"[unknown]"); + r = -1; + } + if (dest->capa > dest->len + buf.len) { /* enough space? */ + FIO_MEMCPY(dest->buf + dest->len, buf.buf, buf.len); + dest->len += buf.len; + dest->buf[dest->len] = 0; + } else + r = (int)buf.len - (!buf.len); + return r; +} -#undef FIO_WEBSOCKET_PARSER -#endif /* FIO_WEBSOCKET_PARSER && FIO_EXTERN_COMPLETE */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_HTTP /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ /* ***************************************************************************** +Body Management - file descriptor +***************************************************************************** */ +FIO_SFUNC fio_str_info_s fio___http_body_read_fd(fio_http_s *h, size_t len) { + h->body.buf = fio_bstr_len_set(h->body.buf, 0); + h->body.buf = fio_bstr_readfd(h->body.buf, h->body.fd, h->body.pos, len); + fio_str_info_s r = fio_bstr_info(h->body.buf); + h->body.pos += r.len; + return r; +} +FIO_SFUNC fio_str_info_s fio___http_body_read_until_fd(fio_http_s *h, + char token, + size_t limit) { + h->body.buf = fio_bstr_len_set(h->body.buf, 0); + h->body.buf = + fio_bstr_getdelim_fd(h->body.buf, h->body.fd, h->body.pos, token, limit); + fio_str_info_s r = fio_bstr_info(h->body.buf); + h->body.pos += r.len; + return r; +} +FIO_SFUNC void fio___http_body_expect_fd(fio_http_s *h, size_t len) { + (void)h, (void)len; +} +FIO_SFUNC void fio___http_body_write_fd(fio_http_s *h, + const void *data, + size_t len) { + ssize_t written = fio_fd_write(h->body.fd, data, len); + if (written > 0) + h->body.len += written; +} - - - HTTP Implementation for FIO_SERVER - - - - -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_HTTP) && !defined(H___FIO_HTTP___H) && \ - !defined(FIO___RECURSIVE_INCLUDE) -#define H___FIO_HTTP___H /* ***************************************************************************** -HTTP Setting Defaults +Body Management - buffer ***************************************************************************** */ -#ifndef FIO_HTTP_DEFAULT_MAX_HEADER_SIZE -#define FIO_HTTP_DEFAULT_MAX_HEADER_SIZE 32768 /* (1UL << 15) */ -#endif -#ifndef FIO_HTTP_DEFAULT_MAX_LINE_LEN -#define FIO_HTTP_DEFAULT_MAX_LINE_LEN 8192 /* (1UL << 13) */ -#endif -#ifndef FIO_HTTP_DEFAULT_MAX_BODY_SIZE -#define FIO_HTTP_DEFAULT_MAX_BODY_SIZE 33554432 /* (1UL << 25) */ -#endif -#ifndef FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE -#define FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE 262144 /* (1UL << 18) */ -#endif -#ifndef FIO_HTTP_DEFAULT_TIMEOUT -#define FIO_HTTP_DEFAULT_TIMEOUT 50 -#endif -#ifndef FIO_HTTP_DEFAULT_TIMEOUT_LONG -#define FIO_HTTP_DEFAULT_TIMEOUT_LONG 50 -#endif - -#ifndef FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER -/** Adds a "content-length" header to the HTTP handle (usually redundant). */ -#define FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER 0 -#endif - -#ifndef FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT -/** UTF-8 validity tests will be performed only for data shorter than this. */ -#define FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT ((1UL << 16) - 10UL) -#endif - -#ifndef FIO_WEBSOCKET_STATS -/* If true, logs longest WebSocket round-trips (using FIO_LOG_INFO). */ -#define FIO_WEBSOCKET_STATS 0 +FIO_SFUNC int fio___http_body___move_buf2fd(fio_http_s *h) { + h->body.fd = fio_filename_tmp(); + if (h->body.fd == -1) { +#if 1 + static int error_printed = 0; + if (!error_printed) { + error_printed = 1; + FIO_LOG_ERROR("fio_http_s couldn't open temporary file! (%d) %s", + errno, + strerror(errno)); + } #endif + return -1; + } + fio_buf_info_s b = fio_bstr_buf(h->body.buf); + if (!b.len) + return 0; + ssize_t written = fio_fd_write(h->body.fd, b.buf, b.len); + if (written == (ssize_t)b.len) + return 0; + close(h->body.fd); + FIO_LOG_ERROR("fio_http_s couldn't transfer data to temporary file " + "(transferred %zd / %zu)", + written, + b.len); + return (h->body.fd = -1); +} +FIO_SFUNC fio_str_info_s fio___http_body_read_buf(fio_http_s *h, size_t len) { + fio_str_info_s r = FIO_STR_INFO2((h->body.buf + h->body.pos), len); + h->body.pos += len; + return r; +} +FIO_SFUNC fio_str_info_s fio___http_body_read_until_buf(fio_http_s *h, + char token, + size_t limit) { + fio_str_info_s r = FIO_STR_INFO2((h->body.buf + h->body.pos), limit); + char *end = (char *)FIO_MEMCHR(r.buf, token, limit); + if (end) { + ++end; + r.len = end - r.buf; + h->body.pos = end - h->body.buf; + } + return r; +} +FIO_SFUNC void fio___http_body_expect_buf(fio_http_s *h, size_t len) { + if (len + h->body.len > FIO_HTTP_BODY_RAM_LIMIT) { + fio___http_body___move_buf2fd(h); + return; + } + h->body.buf = fio_bstr_reserve(h->body.buf, len); +} +FIO_SFUNC void fio___http_body_write_buf(fio_http_s *h, + const void *data, + size_t len) { + if (len + h->body.len > FIO_HTTP_BODY_RAM_LIMIT) + goto switch_to_fd; +write_to_buf: + h->body.buf = fio_bstr_write(h->body.buf, data, len); + h->body.len += len; + return; +switch_to_fd: + if (fio___http_body___move_buf2fd(h)) + goto write_to_buf; + fio___http_body_write_fd(h, data, len); +} /* ***************************************************************************** -HTTP Listen +Body Management - Public API ***************************************************************************** */ -typedef struct fio_http_settings_s { - /** Called before body uploads, when a client sends an `Expect` header. */ - void (*pre_http_body)(fio_http_s *h); - /** Callback for HTTP requests (server) or responses (client). */ - void (*on_http)(fio_http_s *h); - /** Called when a request / response cycle is finished with no Upgrade. */ - void (*on_finish)(fio_http_s *h); - /** (optional) the callback to be performed when the HTTP service closes. */ - void (*on_stop)(struct fio_http_settings_s *settings); - - /** Authenticate EventSource (SSE) requests, return non-zero to deny.*/ - int (*on_authenticate_sse)(fio_http_s *h); - /** Authenticate WebSockets Upgrade requests, return non-zero to deny.*/ - int (*on_authenticate_websocket)(fio_http_s *h); - - /** Called once a WebSocket / SSE connection upgrade is complete. */ - void (*on_open)(fio_http_s *h); - - /** Called when a WebSocket message is received. */ - void (*on_message)(fio_http_s *h, fio_buf_info_s msg, uint8_t is_text); - /** Called when an EventSource event is received. */ - void (*on_eventsource)(fio_http_s *h, - fio_buf_info_s id, - fio_buf_info_s event, - fio_buf_info_s data); - /** Called when an EventSource reconnect event requests an ID. */ - void (*on_eventsource_reconnect)(fio_http_s *h, fio_buf_info_s id); - - /** Called for WebSocket / SSE connections when outgoing buffer is empty. */ - void (*on_ready)(fio_http_s *h); - /** Called for open WebSocket / SSE connections during shutting down. */ - void (*on_shutdown)(fio_http_s *h); - /** Called after a WebSocket / SSE connection is closed (for cleanup). */ - void (*on_close)(fio_http_s *h); - - /** Default opaque user data for HTTP handles (fio_http_s). */ - void *udata; - /** Optional SSL/TLS support. */ - fio_io_functions_s *tls_io_func; - /** Optional SSL/TLS support. */ - fio_tls_s *tls; - /** Optional HTTP task queue (for multi-threading HTTP responses) */ - fio_srv_async_s *queue; - /** - * A public folder for file transfers - allows to circumvent any application - * layer logic and simply serve static files. - * - * Supports automatic `gz` pre-compressed alternatives. - */ - fio_str_info_s public_folder; - /** - * The max-age value (in seconds) for possibly caching static files from the - * public folder specified. - * - * Defaults to 0 (not sent). - */ - size_t max_age; - /** - * The maximum total of bytes for the overall size of the request string and - * headers, combined. - * - * Defaults to FIO_HTTP_DEFAULT_MAX_HEADER_SIZE bytes. - */ - uint32_t max_header_size; - /** - * The maximum number of bytes allowed per header / request line. - * - * Defaults to FIO_HTTP_DEFAULT_MAX_LINE_LEN bytes. - */ - uint32_t max_line_len; - /** - * The maximum size of an HTTP request's body (posting / downloading). - * - * Defaults to FIO_HTTP_DEFAULT_MAX_BODY_SIZE bytes. - */ - size_t max_body_size; - /** - * The maximum websocket message size/buffer (in bytes) for Websocket - * connections. Defaults to FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE bytes. - */ - size_t ws_max_msg_size; - /** reserved for future use. */ - intptr_t reserved1; - /** reserved for future use. */ - intptr_t reserved2; - /** - * An HTTP/1.x connection timeout. - * - * Defaults to FIO_HTTP_DEFAULT_TIMEOUT seconds. - * - * Note: the connection might be closed (by other side) before timeout occurs. - */ - uint8_t timeout; - /** - * Timeout for the WebSocket connections in seconds. Defaults to - * FIO_HTTP_DEFAULT_TIMEOUT_LONG seconds. - * - * A ping will be sent whenever the timeout is reached. - * - * Connections are only closed when a ping cannot be sent (the network layer - * fails). Pongs are ignored. - */ - uint8_t ws_timeout; - /** - * Timeout for EventSource (SSE) connections in seconds. Defaults to - * FIO_HTTP_DEFAULT_TIMEOUT_LONG seconds. - * - * A ping will be sent whenever the timeout is reached. - * - * Connections are only closed when a ping cannot be sent (the network layer - * fails). - */ - uint8_t sse_timeout; - /** Timeout for client connections (only relevant in client mode). */ - uint8_t connect_timeout; - /** Logging flag - set to TRUE to log HTTP requests. */ - uint8_t log; -} fio_http_settings_s; - -/** Listens to HTTP / WebSockets / SSE connections on `url`. */ -SFUNC void *fio_http_listen(const char *url, fio_http_settings_s settings); - -/** Listens to HTTP / WebSockets / SSE connections on `url`. */ -#define fio_http_listen(url, ...) \ - fio_http_listen(url, (fio_http_settings_s){__VA_ARGS__}) - -/** Allows all clients to connect (bypasses authentication). */ -SFUNC int FIO_HTTP_AUTHENTICATE_ALLOW(fio_http_s *h); - -/** Returns the IO object associated with the HTTP object (request only). */ -SFUNC fio_s *fio_http_io(fio_http_s *); - -/** Macro helper for HTTP handle pub/sub subscriptions. */ -#define fio_http_subscribe(h, ...) \ - fio_subscribe(.io = fio_http_io(h), __VA_ARGS__) - -/** Connects to HTTP / WebSockets / SSE connections on `url`. */ -SFUNC fio_s *fio_http_connect(const char *url, - fio_http_s *h, - fio_http_settings_s settings); -/** Connects to HTTP / WebSockets / SSE connections on `url`. */ -#define fio_http_connect(url, h, ...) \ - fio_http_connect(url, h, (fio_http_settings_s){__VA_ARGS__}) +/** Gets the body (payload) length associated with the HTTP handle. */ +SFUNC size_t fio_http_body_length(fio_http_s *h) { return h->body.len; } -/** Returns the HTTP settings associated with the HTTP object, if any. */ -SFUNC fio_http_settings_s *fio_http_settings(fio_http_s *); +/** + * If the body is stored in a temporary file, returns the file's handle. + * + * Otherwise returns -1. + */ +SFUNC int fio_http_body_fd(fio_http_s *h) { return h->body.fd; } -/* ***************************************************************************** -WebSocket Helpers - HTTP Upgraded Connections -***************************************************************************** */ +/** Adjusts the body's reading position. Negative values start at the end. */ +SFUNC size_t fio_http_body_seek(fio_http_s *h, ssize_t pos) { + if (pos < 0) { + pos += h->body.len; + if (pos < 0) + pos = 0; + } + if ((size_t)pos >= h->body.len) + pos = h->body.len; + h->body.pos = pos; + return pos; +} -/** Writes a WebSocket message. Fails if connection wasn't upgraded yet. */ -SFUNC int fio_http_websocket_write(fio_http_s *h, - const void *buf, - size_t len, - uint8_t is_text); +/** Reads up to `length` of data from the body, returns nothing on EOF. */ +SFUNC fio_str_info_s fio_http_body_read(fio_http_s *h, size_t length) { + fio_str_info_s r = {0}; + if (h->body.pos == h->body.len) + return r; + if (h->body.pos + length > h->body.len) + length = h->body.len - h->body.pos; + r = ((h->body.fd == -1) ? fio___http_body_read_buf + : fio___http_body_read_fd)(h, length); + return r; +} /** - * Sets a specific on_message callback for this connection. + * Reads from the body until finding `token`, reaching `limit` or EOF. * - * Returns -1 on error (i.e., upgrade still in negotiation). + * Note: `limit` is ignored if zero or larger than remaining data. */ -SFUNC int fio_http_on_message_set(fio_http_s *h, - void (*on_message)(fio_http_s *, - fio_buf_info_s, - uint8_t)); +SFUNC fio_str_info_s fio_http_body_read_until(fio_http_s *h, + char token, + size_t limit) { + fio_str_info_s r = {0}; + if (h->body.pos == h->body.len) + return r; + if (!limit || (h->body.pos + limit) > h->body.len) + limit = h->body.len - h->body.pos; + r = ((h->body.fd == -1) ? fio___http_body_read_until_buf + : fio___http_body_read_until_fd)(h, token, limit); + return r; +} -/** Optional WebSocket subscription callback. */ -SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT(fio_msg_s *msg); -/** Optional WebSocket subscription callback - all messages are UTF-8 valid. */ -SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT(fio_msg_s *msg); -/** Optional WebSocket subscription callback - messages may be non-UTF-8. */ -SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY(fio_msg_s *msg); +/** Allocates a body (payload) of (at least) the `expected_length`. */ +SFUNC void fio_http_body_expect(fio_http_s *h, size_t expected_length) { + ((h->body.fd == -1) ? fio___http_body_expect_buf + : fio___http_body_expect_fd)(h, expected_length); +} + +/** Writes `data` to the body (payload) associated with the HTTP handle. */ +SFUNC void fio_http_body_write(fio_http_s *h, const void *data, size_t len) { + if (!data || !len) + return; + ((h->body.fd == -1) ? fio___http_body_write_buf + : fio___http_body_write_fd)(h, data, len); +} /* ***************************************************************************** -EventSource (SSE) Helpers - HTTP Upgraded Connections +A Response Payload ***************************************************************************** */ -/** Named arguments for fio_http_sse_write. */ -typedef struct { - /** The message's `id` data (if any). */ - fio_buf_info_s id; - /** The message's `event` data (if any). */ - fio_buf_info_s event; - /** The message's `data` data (if any). */ - fio_buf_info_s data; -} fio_http_sse_write_args_s; +/** ETag Helper */ +FIO_IFUNC int fio___http_response_etag_if_none_match(fio_http_s *h); -/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ -SFUNC int fio_http_sse_write(fio_http_s *h, fio_http_sse_write_args_s args); +FIO_SFUNC int fio____http_write_done(fio_http_s *h, + fio_http_write_args_s *args) { + return -1; + (void)h, (void)args; +} -/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ -#define fio_http_sse_write(h, ...) \ - fio_http_sse_write((h), ((fio_http_sse_write_args_s){__VA_ARGS__})) +FIO_SFUNC int fio____http_write_upgraded(fio_http_s *h, + fio_http_write_args_s *args) { + h->controller->write_body(h, *args); + h->sent += args->len; + return 0; +} -/** Optional EventSource subscription callback - messages MUST be UTF-8. */ -SFUNC void FIO_HTTP_SSE_SUBSCRIBE_DIRECT(fio_msg_s *msg); +FIO_SFUNC int fio____http_write_start(fio_http_s *h, + fio_http_write_args_s *args) { + /* if response has an `etag` header matching `if-none-match`, skip */ + fio___http_hmap_s *hdrs = h->headers + (!!h->status); + if (h->status) { + if (args->len && fio___http_response_etag_if_none_match(h)) + return -1; + if (!args->len && args->finish && h->status >= 400) { + fio_http_send_error_response(h, h->status); + return 0; + } + /* validate Date header */ + fio___http_hmap_set2( + hdrs, + FIO_STR_INFO2((char *)"date", 4), + fio_http_date(fio_http_get_timestump() / FIO___HTTP_TIME_DIV), + 0); + } + /* test if streaming / single body response */ + if (!fio___http_hmap_get_ptr(hdrs, + FIO_STR_INFO2((char *)"content-length", 14))) { + if (args->finish) { + /* validate / set Content-Length (not streaming) */ + char ibuf[32]; + fio_str_info_s k = FIO_STR_INFO2((char *)"content-length", 14); + fio_str_info_s v = FIO_STR_INFO3(ibuf, 0, 32); + v.len = fio_digits10u(args->len); + fio_ltoa10u(v.buf, args->len, v.len); + fio___http_hmap_set2(hdrs, k, v, -1); + } else { + h->state |= FIO_HTTP_STATE_STREAMING; + } + } -/* ***************************************************************************** -Module Implementation - possibly externed functions. -***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) + /* start a response, unless status == 0 (which starts a request). */ + h->controller->send_headers(h); + return (h->writer = fio____http_write_cont)(h, args); +} -/* -REMEMBER: -======== +FIO_SFUNC int fio____http_write_cont(fio_http_s *h, + fio_http_write_args_s *args) { + if (args->buf || args->fd) { + h->controller->write_body(h, *args); + h->sent += args->len; + } + if (args->finish) { + h->state |= FIO_HTTP_STATE_FINISHED; + h->writer = (h->state & FIO_HTTP_STATE_UPGRADED) + ? fio____http_write_upgraded + : fio____http_write_done; + h->controller->on_finish(h); + } + return 0; +} -All memory allocations should use: -* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) -* FIO_MEM_FREE_(ptr, size) +void fio_http_write___(void); /* IDE Marker */ +/** + * Writes `data` to the response body associated with the HTTP handle after + * sending all headers (no further headers may be sent). + */ +SFUNC void fio_http_write FIO_NOOP(fio_http_s *h, fio_http_write_args_s args) { + if (!h || !h->controller) + goto handle_error; + if (h->writer(h, &args)) + goto handle_error; + return; -*/ +handle_error: + if (args.fd) + close(args.fd); + if (args.dealloc && args.buf) + args.dealloc((void *)args.buf); +} -/* ***************************************************************************** -HTTP Settings Validation -***************************************************************************** */ +/** ETag Helper */ +FIO_IFUNC int fio___http_response_etag_if_none_match(fio_http_s *h) { + if (!fio_http_etag_is_match(h)) + return 0; + h->status = 304; + fio___http_hmap_set2(HTTP_HDR_RESPONSE(h), + FIO_STR_INFO2((char *)"content-length", 14), + FIO_STR_INFO0, + -1); -static void fio___http_default_on_http_request(fio_http_s *h) { - fio_http_send_error_response(h, 404); -} -static void fio___http_default_noop(fio_http_s *h) { ((void)h); } -static int fio___http_default_authenticate(fio_http_s *h) { - ((void)h); + h->controller->send_headers(h); + h->state |= FIO_HTTP_STATE_FINISHED; + h->writer = fio____http_write_done; + h->controller->on_finish(h); return -1; } -// on_queue -static void fio___http_default_on_stop(struct fio_http_settings_s *settings) { - ((void)settings); -} +/* ***************************************************************************** +WebSocket / SSE Helpers +***************************************************************************** */ -static void fio___http_default_close(fio_http_s *h) { - fio_close(fio_http_io(h)); +/** Returns non-zero if request headers ask for a WebSockets Upgrade.*/ +SFUNC int fio_http_websocket_requested(fio_http_s *h) { + fio_str_info_s val = + fio_http_request_header(h, FIO_STR_INFO2((char *)"connection", 10), 0); + /* test for "Connection: Upgrade" (TODO? allow for multi-value?) */ + if (val.len < 7 || + !(((fio_buf2u32u(val.buf) | 0x20202020UL) == fio_buf2u32u("upgr")) || + ((fio_buf2u32u(val.buf + 3) | 0x20202020) == fio_buf2u32u("rade")))) + return 0; + /* test for "Upgrade: websocket" (TODO? allow for multi-value?) */ + val = fio_http_request_header(h, FIO_STR_INFO2((char *)"upgrade", 7), 0); + if (val.len < 7 || + !(((fio_buf2u64u(val.buf) | 0x2020202020202020ULL) == + fio_buf2u64u("websocke")) || + ((fio_buf2u32u(val.buf + 5) | 0x20202020UL) == fio_buf2u32u("cket")))) + return 0; + val = fio_http_request_header(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + 0); + if (val.len != 24) + return 0; + /* test for version value */ + val = fio_http_request_header( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + 0); + if (val.len != 2 || (val.buf[0] != '1' || val.buf[1] != '3')) + return -1; /* note the error value is still true, requested WebSocket... */ + return 1; } -/** Called when a WebSocket message is received. */ -static void fio___http_default_on_message(fio_http_s *h, - fio_buf_info_s msg, - uint8_t is_text) { - (void)h, (void)msg, (void)is_text; -} -/** Called when an EventSource event is received. */ -static void fio___http_default_on_eventsource(fio_http_s *h, - fio_buf_info_s id, - fio_buf_info_s event, - fio_buf_info_s data) { - (void)h, (void)id, (void)event, (void)data; +/** Sets response data to agree to a WebSockets Upgrade.*/ +SFUNC void fio_http_upgrade_websocket(fio_http_s *h) { + { /* validate WebSocket version */ + fio_str_info_s val = fio_http_request_header( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + 0); + if (val.len != 2 || (val.buf[0] != '1' || val.buf[1] != '3')) { + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + FIO_STR_INFO2((char *)"13", 2)); + fio_http_send_error_response(h, 400); + } + } + h->status = 101; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"connection", 10), + FIO_STR_INFO2((char *)"Upgrade", 7)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"upgrade", 7), + FIO_STR_INFO2((char *)"websocket", 9)); + { /* Sec-WebSocket-Accept */ + fio_str_info_s k = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + 0); + FIO_STR_INFO_TMP_VAR(accept_val, 63); + if (k.len != 24) + goto handshake_error; + fio_string_write(&accept_val, NULL, k.buf, k.len); + fio_string_write(&accept_val, + NULL, + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + 36); + fio_sha1_s sha = fio_sha1(accept_val.buf, accept_val.len); + fio_sha1_digest(&sha); + accept_val.len = 0; + fio_string_write_base64enc(&accept_val, + NULL, + fio_sha1_digest(&sha), + fio_sha1_len(), + 0); + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"sec-websocket-accept", 20), + accept_val); + } + { /* finish up */ + h->state |= FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_WEBSOCKET; + fio_http_write_args_s args = {.finish = 1}; + fio_http_write FIO_NOOP(h, args); + } + return; +handshake_error: + fio_http_send_error_response(h, 403); + return; } -/** Called when an EventSource event is received. */ -static void fio___http_default_on_eventsource_redirect(fio_http_s *h, - fio_buf_info_s id, - fio_buf_info_s event, - fio_buf_info_s data); -/** Called when an EventSource reconnect event requests an ID. */ -static void fio___http_default_on_eventsource_reconnect(fio_http_s *h, - fio_buf_info_s id) { - (void)h, (void)id; +/** Sets request data to request a WebSockets Upgrade.*/ +SFUNC void fio_http_websocket_set_request(fio_http_s *h) { + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"connection", 10), + FIO_STR_INFO2((char *)"Upgrade", 7)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"pragma", 6), + FIO_STR_INFO2((char *)"no-cache", 8)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"cache-control", 13), + FIO_STR_INFO2((char *)"no-cache", 8)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"upgrade", 7), + FIO_STR_INFO2((char *)"websocket", 9)); + { + fio_http_request_header_set_if_missing( + h, + FIO_STR_INFO2((char *)"origin", 6), + fio_http_request_header(h, FIO_STR_INFO2((char *)"host", 4), 0)); + } + fio_http_request_header_set( + h, + FIO_STR_INFO2((char *)"sec-websocket-version", 21), + FIO_STR_INFO2((char *)"13", 2)); + + { + uint64_t tmp[2] = {fio_rand64(), fio_rand64()}; + FIO_STR_INFO_TMP_VAR(key, 64); + fio_string_write_base64enc(&key, NULL, tmp, 16, 0); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + key); + } + /* sec-websocket-extensions ? */ + /* send request? */ } -static void http_settings_validate(fio_http_settings_s *s, int is_client) { - if (!s->pre_http_body) - s->pre_http_body = fio___http_default_noop; +/** Returns non-zero if the response accepts a WebSocket upgrade request. */ +SFUNC int fio_http_websocket_accepted(fio_http_s *h) { + if (h->status != 101) + return 0; + if (!fio_http_websocket_requested(h)) + return 0; + fio_str_info_s tst = + fio_http_response_header(h, FIO_STR_INFO2((char *)"connection", 10), 0); + if (tst.len < 7 || + (fio_buf2u64_le(tst.buf) | (uint64_t)0x20202020202020FFULL) != + (fio_buf2u64_le("upgrade") | (uint64_t)0x20202020202020FFULL)) + return 0; + tst = fio_http_response_header(h, FIO_STR_INFO2((char *)"upgrade", 7), 0); + if (tst.len < 9 || (tst.buf[0] | 32) != 'w' || + (fio_buf2u64u(tst.buf + 1) | (uint64_t)0x2020202020202020ULL) != + fio_buf2u64u("ebsocket")) + return 0; + { /* Sec-WebSocket-Accept */ + tst = fio_http_response_header( + h, + FIO_STR_INFO2((char *)"sec-websocket-accept", 20), + 0); + if (!tst.len) + return 0; - if (!s->on_http) - s->on_http = is_client ? fio___http_default_noop - : fio___http_default_on_http_request; - if (!s->on_finish) - s->on_finish = fio___http_default_noop; - if (!s->on_stop) - s->on_stop = fio___http_default_on_stop; - if (!s->on_authenticate_sse) - s->on_authenticate_sse = is_client ? FIO_HTTP_AUTHENTICATE_ALLOW - : fio___http_default_authenticate; - if (!s->on_authenticate_websocket) - s->on_authenticate_websocket = is_client ? FIO_HTTP_AUTHENTICATE_ALLOW - : fio___http_default_authenticate; - if (!s->on_open) - s->on_open = fio___http_default_noop; - if (!s->on_open) - s->on_open = fio___http_default_noop; - if (!s->on_message) - s->on_message = fio___http_default_on_message; - if (!s->on_eventsource) - s->on_eventsource = (s->on_message == fio___http_default_on_message - ? fio___http_default_on_eventsource - : fio___http_default_on_eventsource_redirect); - if (!s->on_eventsource_reconnect) - s->on_eventsource_reconnect = fio___http_default_on_eventsource_reconnect; - if (!s->on_ready) - s->on_ready = fio___http_default_noop; - if (!s->on_shutdown) - s->on_shutdown = fio___http_default_noop; - if (!s->on_close) - s->on_close = fio___http_default_noop; - if (!s->max_header_size) - s->max_header_size = FIO_HTTP_DEFAULT_MAX_HEADER_SIZE; - if (!s->max_line_len) - s->max_line_len = FIO_HTTP_DEFAULT_MAX_LINE_LEN; - if (!s->max_body_size) - s->max_body_size = FIO_HTTP_DEFAULT_MAX_BODY_SIZE; - if (!s->ws_max_msg_size) - s->ws_max_msg_size = FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE; - if (!s->timeout) - s->timeout = FIO_HTTP_DEFAULT_TIMEOUT; - if (!s->ws_timeout) - s->ws_timeout = FIO_HTTP_DEFAULT_TIMEOUT_LONG; - if (!s->sse_timeout) - s->sse_timeout = s->ws_timeout; + fio_str_info_s k = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"sec-websocket-key", 17), + 0); + FIO_STR_INFO_TMP_VAR(accept_val, 63); + if (k.len != 24) + return 0; + fio_string_write(&accept_val, NULL, k.buf, k.len); + fio_string_write(&accept_val, + NULL, + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + 36); + fio_sha1_s sha = fio_sha1(accept_val.buf, accept_val.len); + fio_sha1_digest(&sha); + accept_val.len = 0; + fio_string_write_base64enc(&accept_val, + NULL, + fio_sha1_digest(&sha), + fio_sha1_len(), + 0); + if (!FIO_STR_INFO_IS_EQ(tst, accept_val)) { + FIO_LOG_DDEBUG2( + "(%d) sec-websocket-key invalid, WebSocket handshake failed.\n\t" + "%s != %s", + getpid(), + tst.buf, + accept_val.buf); + return 0; + } + } + h->state |= (FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_WEBSOCKET | + FIO_HTTP_STATE_FINISHED); + h->writer = fio____http_write_upgraded; + return 1; +} - if (s->max_header_size < s->max_line_len) - s->max_header_size = s->max_line_len; +/** Returns non-zero if request headers ask for an EventSource (SSE) Upgrade.*/ +SFUNC int fio_http_sse_requested(fio_http_s *h) { + fio_str_info_s val = + fio_http_request_header(h, FIO_STR_INFO2((char *)"accept", 6), 0); + if (val.len < 17) + return 0; + if ((val.buf[0] | 32) != 't') + return 0; + uint64_t t0 = fio_buf2u64u(val.buf + 1) | (uint64_t)0x2020202020202020ULL; + uint64_t t1 = fio_buf2u64u(val.buf + 9) | (uint64_t)0x2020202020202020ULL; + if ((t0 != fio_buf2u64u("ext/even")) || (t1 != fio_buf2u64u("t-stream"))) + return 0; /* note that '/' and '-' both have 32 (bit[5]) set */ + FIO_LOG_DDEBUG2("(%d) EventSource connection requested.", + fio_thread_getpid()); + return 1; +} - if (s->public_folder.buf) { - if (s->public_folder.len > 1 && - s->public_folder.buf[s->public_folder.len - 1] == '/' && - !(s->public_folder.len == 2 && s->public_folder.buf[0] == '~')) - --s->public_folder.len; - if (!fio_filename_is_folder(s->public_folder.buf)) { - FIO_LOG_ERROR( - "HTTP public folder is not a folder, setting ignored.\n\t%s", - s->public_folder.buf); - s->public_folder = ((fio_str_info_s){0}); - } +/** Returns non-zero if the response accepts an SSE request. */ +SFUNC int fio_http_sse_accepted(fio_http_s *h) { + if (!fio_http_sse_requested(h)) + return 0; + if (h->status != 200) + return 0; + fio_str_info_s val = + fio_http_request_header(h, FIO_STR_INFO2((char *)"accept", 6), 0); + for (size_t i = 0; i < 2; ++i) { + if (val.len < 17) + return 0; + if ((val.buf[0] | 32) != 't') + return 0; + uint64_t t0 = fio_buf2u64u(val.buf + 1) | (uint64_t)0x2020202020202020ULL; + uint64_t t1 = fio_buf2u64u(val.buf + 9) | (uint64_t)0x2020202020202020ULL; + if ((t0 != fio_buf2u64u("ext/even")) || (t1 != fio_buf2u64u("t-stream"))) + return 0; /* note that '/' and '-' both have 32 (bit[5]) set */ + val = fio_http_response_header(h, + FIO_STR_INFO2((char *)"content-type", 12), + 0); } + h->state |= + (FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_SSE | FIO_HTTP_STATE_FINISHED); + h->writer = fio____http_write_upgraded; + FIO_LOG_DDEBUG2("EventSource connection accepted."); + return 1; +} + +/** Sets response data to agree to an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_upgrade_sse(fio_http_s *h) { + if (h->state) + return; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-type", 12), + FIO_STR_INFO2((char *)"text/event-stream", 17)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"cache-control", 13), + FIO_STR_INFO2((char *)"no-store", 8)); + fio___http_hmap_remove(HTTP_HDR_RESPONSE(h), + FIO_STR_INFO2((char *)"content-length", 14), + NULL); + h->state |= + FIO_HTTP_STATE_FINISHED | FIO_HTTP_STATE_UPGRADED | FIO_HTTP_STATE_SSE; + h->controller->send_headers(h); + h->writer = fio____http_write_upgraded; + h->controller->on_finish(h); +} + +/** Sets request data to request an EventSource (SSE) Upgrade.*/ +SFUNC void fio_http_sse_set_request(fio_http_s *h) { + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"accept", 6), + FIO_STR_INFO2((char *)"text/event-stream", 17)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"connection", 10), + FIO_STR_INFO2((char *)"keep-alive", 10)); + fio_http_request_header_set(h, + FIO_STR_INFO2((char *)"cache-control", 13), + FIO_STR_INFO2((char *)"no-cache", 8)); } /* ***************************************************************************** -HTTP Protocols used by the HTTP module +Header Parsing Helpers - Implementation ***************************************************************************** */ -typedef enum fio___http_protocol_selector_e { - FIO___HTTP_PROTOCOL_ACCEPT = 0, - FIO___HTTP_PROTOCOL_HTTP1, - FIO___HTTP_PROTOCOL_HTTP2, - FIO___HTTP_PROTOCOL_WS, - FIO___HTTP_PROTOCOL_SSE, - FIO___HTTP_PROTOCOL_NONE -} fio___http_protocol_selector_e; - -/** Returns a facil.io protocol object with the proper protocol callbacks. */ -FIO_IFUNC fio_protocol_s fio___http_protocol_get(fio___http_protocol_selector_e, - int is_client); -/** Returns an http controller object with the proper protocol callbacks. */ -FIO_IFUNC fio_http_controller_s -fio___http_controller_get(fio___http_protocol_selector_e, int is_client); - -/* ***************************************************************************** -HTTP Protocol Container (vtable + settings storage) -***************************************************************************** */ -#define FIO___RECURSIVE_INCLUDE 1 +/** + * Assumes a Buffer of bytes containing length info and string data as such: + * + * [ 2 byte info: (type | (len << 2)) ] + * [ Optional 2 byte info: (len << 2) (if type was 1)] + * [ String of `len` bytes][ NUL byte (not counted in `len`)] + */ -typedef struct { - fio_http_settings_s settings; - void (*on_http_callback)(void *, void *); - fio_queue_s *queue; - struct { - fio_protocol_s protocol; - fio_http_controller_s controller; - } state[FIO___HTTP_PROTOCOL_NONE + 1]; - char public_folder_buf[]; -} fio___http_protocol_s; -#include FIO_INCLUDE_FILE +FIO_SFUNC int fio___http_header_parse_properties(fio_str_info_s *dst, + char *start, + char *const end) { + for (;;) { + char *nxt = (char *)FIO_MEMCHR(start, ';', end - start); + if (!nxt) + nxt = end; + char *eq = (char *)FIO_MEMCHR(start, '=', nxt - start); + if (!eq) + eq = nxt; + /* write value to dst */ + size_t len = eq - start; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; /* too long */ + fio_u2buf16u(dst->buf + dst->len, + ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_DATA)); + dst->len += 2; + if (len) + FIO_MEMCPY(dst->buf + dst->len, start, len); + dst->len += len; + dst->buf[dst->len++] = 0; -#define FIO_REF_NAME fio___http_protocol -#define FIO_REF_FLEX_TYPE char -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO_REF_DESTROY(o) \ - do { \ - if (o.settings.tls) \ - fio_tls_free(o.settings.tls); \ - if (o.settings.on_stop) \ - o.settings.on_stop(&o.settings); \ - } while (0) -#include FIO_INCLUDE_FILE + eq += (eq[0] == '='); + eq += (eq[0] == ' ' || eq[0] == '\t'); + len = nxt - eq; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; /* too long */ + fio_u2buf16u(dst->buf + dst->len, + ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_DATA)); + dst->len += 2; + if (len) + FIO_MEMCPY(dst->buf + dst->len, eq, len); + dst->len += len; + dst->buf[dst->len++] = 0; -FIO_SFUNC void fio___http_on_http_direct(void *h_, void *ignr); -FIO_SFUNC void fio___http_on_http_with_public_folder(void *h_, void *ignr); -FIO_SFUNC void fio___http_on_http_client(void *h_, void *ignr); -/* move init code here*/ -FIO_IFUNC fio___http_protocol_s *fio___http_protocol_init( - fio___http_protocol_s *p, - const char *url, - fio_http_settings_s s, - bool is_client) { - int should_free_tls = !s.tls; - FIO_ASSERT_ALLOC(p); - for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE + 1; ++i) { - p->state[i].protocol = - fio___http_protocol_get((fio___http_protocol_selector_e)i, is_client); - p->state[i].controller = - fio___http_controller_get((fio___http_protocol_selector_e)i, is_client); - } - for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE; ++i) - p->state[i].protocol.timeout = (unsigned)s.ws_timeout * 1000U; - p->state[FIO___HTTP_PROTOCOL_SSE].protocol.timeout = - (unsigned)s.sse_timeout * 1000U; - p->state[FIO___HTTP_PROTOCOL_ACCEPT].protocol.timeout = - (unsigned)s.timeout * 1000U; - p->state[FIO___HTTP_PROTOCOL_HTTP1].protocol.timeout = - (unsigned)s.timeout * 1000U; - p->state[FIO___HTTP_PROTOCOL_NONE].protocol.timeout = - (unsigned)s.timeout * 1000U; - if (url) { - fio_url_s u = fio_url_parse(url, strlen(url)); - s.tls = fio_tls_from_url(s.tls, u); - if (s.tls) { - s.tls = fio_tls_dup(s.tls); - /* fio_tls_alpn_add(s.tls, "h2", fio___http_on_select_h2); // not yet */ - // fio_tls_alpn_add(s.tls, "http/1.1", fio___http_on_select_h1); - fio_io_functions_s tmp_fn = fio_tls_default_io_functions(NULL); - if (!s.tls_io_func) - s.tls_io_func = &tmp_fn; - for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE + 1; ++i) - p->state[i].protocol.io_functions = *s.tls_io_func; - if (should_free_tls) - fio_tls_free(s.tls); - } + if (nxt == end) + return 0; + nxt += (*nxt == ';'); + while (*nxt == ' ' || *nxt == '\t') + ++nxt; + start = nxt; } - p->settings = s; - p->on_http_callback = is_client ? fio___http_on_http_client - : (p->settings.public_folder.len) - ? fio___http_on_http_with_public_folder - : fio___http_on_http_direct; - p->settings.public_folder.buf = p->public_folder_buf; - p->queue = fio_srv_queue(); - - if (s.public_folder.len) - FIO_MEMCPY(p->public_folder_buf, s.public_folder.buf, s.public_folder.len); - return p; + return 0; } -/* ***************************************************************************** -HTTP Connection Container -***************************************************************************** */ - -struct fio___http_connection_http_s { - void (*on_http_callback)(void *, void *); - void (*on_http)(fio_http_s *h); - void (*on_finish)(fio_http_s *h); - fio_http1_parser_s parser; - uint32_t max_header; -}; -struct fio___http_connection_ws_s { - void (*on_message)(fio_http_s *h, fio_buf_info_s msg, uint8_t is_text); - void (*on_ready)(fio_http_s *h); - fio_websocket_parser_s parser; - char *msg; - uint16_t code; -}; -struct fio___http_connection_sse_s { - void (*on_message)(fio_http_s *h, - fio_buf_info_s id, - fio_buf_info_s event, - fio_buf_info_s data); - void (*on_ready)(fio_http_s *h); - fio_buf_info_s id; - fio_buf_info_s event; - char *data; -}; -/** Connection objects for managing HTTP / WebSocket connection state. */ -typedef struct { - fio_s *io; - fio_http_s *h; - fio_http_settings_s *settings; - fio_queue_s *queue; - void *udata; - union { - struct fio___http_connection_http_s http; - struct fio___http_connection_ws_s ws; - struct fio___http_connection_sse_s sse; - } state; - uint32_t len; - uint32_t capa; - uint8_t log; - uint8_t suspend; - uint8_t is_client; - char buf[]; -} fio___http_connection_s; +FIO_IFUNC int fio___http_header_parse(fio___http_hmap_s *map, + fio_str_info_s *dst, + fio_str_info_s header_name) { + fio___http_sary_s *a = + fio___http_hmap_node2val_ptr(fio___http_hmap_get_ptr(map, header_name)); + if (!a) + return -1; + dst->len = 0; + if (dst->capa < 3) + return -1; + dst->buf[dst->len++] = 0; /* first byte is a pretend NUL */ + FIO_ARRAY_EACH(fio___http_sary, a, pos) { + fio_buf_info_s i = fio_bstr_buf(*pos); + if (!i.len) + continue; + char *const end = i.buf + i.len; + char *sep; + do { + sep = (char *)FIO_MEMCHR(i.buf, ',', end - i.buf); + if (!sep) + sep = end; + char *prop = (char *)FIO_MEMCHR(i.buf, ';', sep - i.buf); + if (!prop) + prop = sep; + size_t len = prop - i.buf; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; /* too long */ + fio_u2buf16u(dst->buf + dst->len, (len << 2)); + dst->len += 2; + FIO_MEMCPY(dst->buf + dst->len, i.buf, len); + dst->len += len; + dst->buf[dst->len++] = 0; + if (prop != sep) { /* parse properties */ + ++prop; + len = sep - prop; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; + const size_t old_len = dst->len; + dst->len += 2; + if (fio___http_header_parse_properties(dst, prop, sep)) + return -1; + len = dst->len - old_len; + if ((len & (~(size_t)0x3FFF)) | (dst->len + len + 3 > dst->capa)) + return -1; + fio_u2buf16u( + dst->buf + old_len, + ((len << 2) | FIO___HTTP_PARSED_HEADER_PROPERTY_BLOCK_LEN)); + } + sep += (*sep == ','); + while (*sep == ' ' || *sep == '\t') + ++sep; + i.buf = sep; + } while (sep < end); + } + if (dst->len + 2 > dst->capa) + return -1; + /* last u16 must be zero (end marker) */ + dst->buf[dst->len++] = 0; + dst->buf[dst->len++] = 0; + return 0; +} -#define FIO_REF_NAME fio___http_connection -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO_REF_FLEX_TYPE char -#define FIO_REF_DESTROY(o) \ - do { \ - fio___http_protocol_free( \ - FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, o.settings)); \ - } while (0) -#include FIO_INCLUDE_FILE +SFUNC int fio_http_response_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name) { + return fio___http_header_parse(HTTP_HDR_RESPONSE(h), buf_parsed, header_name); +} -#undef FIO___RECURSIVE_INCLUDE +SFUNC int fio_http_request_header_parse(fio_http_s *h, + fio_str_info_s *buf_parsed, + fio_str_info_s header_name) { + return fio___http_header_parse(HTTP_HDR_REQUEST(h), buf_parsed, header_name); +} /* ***************************************************************************** -Revisit defaults +Error Handling ***************************************************************************** */ -/** Called when an EventSource event is received. */ -static void fio___http_default_on_eventsource_redirect(fio_http_s *h, - fio_buf_info_s id, - fio_buf_info_s event, - fio_buf_info_s data) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - c->settings->on_message(h, data, 1); - (void)h, (void)id, (void)event, (void)data; +/** Sends the requested error message and finishes the response. */ +SFUNC int fio_http_send_error_response(fio_http_s *h, size_t status) { + if (!h || h->writer != fio____http_write_start) + return -1; + if (!status || status > 1000) + status = 404; + h->status = (uint32_t)status; + FIO_STR_INFO_TMP_VAR(filename, 127); + /* read static error code file */ + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_UNUM(status), + FIO_STRING_WRITE_STR2(".html", 5)); + char *body = fio_bstr_readfile(NULL, filename.buf, 0, 0); + fio_http_write_args_s args = {.buf = body, + .len = fio_bstr_len(body), + .dealloc = (void (*)(void *))fio_bstr_free, + .finish = 1}; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-type", 12), + body ? FIO_STR_INFO2((char *)"text/html", 9) + : FIO_STR_INFO2((char *)"text/plain", 10)); + if (!body) { /* write a short error response (plain text fallback) */ + fio_str_info_s status_str = fio_http_status2str(status); + filename.len = 0; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("Error ", 6), + FIO_STRING_WRITE_UNUM(status), + FIO_STRING_WRITE_STR2(": ", 2), + FIO_STRING_WRITE_STR_INFO(status_str), + FIO_STRING_WRITE_STR2(".", 1)); + args.buf = filename.buf; + args.len = filename.len; + args.copy = 1; + args.dealloc = NULL; + } + fio_http_write FIO_NOOP(h, args); + return 0; } /* ***************************************************************************** -HTTP Request handling / handling +HTTP Logging ***************************************************************************** */ -FIO_SFUNC void fio___http_perform_user_callback(void *cb_, void *h_) { - union { - void (*fn)(fio_http_s *); - void *ptr; - } cb = {.ptr = cb_}; - fio_http_s *h = (fio_http_s *)h_; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (FIO_LIKELY(fio_srv_is_open(c->io))) - cb.fn(h); - fio_http_free(h); -} - -FIO_SFUNC void fio___http_perform_user_upgrade_callback_websocket(void *cb_, - void *h_) { - union { - int (*fn)(fio_http_s *); - void *ptr; - } cb = {.ptr = cb_}; - fio_http_s *h = (fio_http_s *)h_; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - struct fio___http_connection_http_s old = c->state.http; - if (cb.fn(h)) - goto refuse_upgrade; - if (c->h) /* request after WebSocket Upgrade? an attack vector? */ - goto refuse_upgrade; -#if HAVE_ZLIB && 0 /* TODO: logs and fix extension handling logic */ - FIO_HTTP_HEADER_EACH_VALUE(/* TODO: setup WebSocket extension */ - h, - 1, - FIO_STR_INFO2((char *)"sec-websocket-extensions", - 24), - val) { - FIO_LOG_DDEBUG2("WebSocket extension requested: %.*s", - (int)val.len, - val.buf); - if (!FIO_STR_INFO_IS_EQ(val, - FIO_STR_INFO2((char *)"permessage-deflate", 18))) - continue; - size_t client_bits = 0, server_bits = 0; - FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(val, p) { - FIO_LOG_DDEBUG2("\t %.*s: %.*s", - (int)p.name.len, - p.name.buf, - (int)p.value.len, - p.value.buf); - if (FIO_STR_INFO_IS_EQ(p.name, - FIO_STR_INFO2((char *)"client_max_window_bits", - 22))) { /* used by chrome */ - char *iptr = p.value.buf; - client_bits = iptr ? fio_atol10u(&iptr) : 0; - if (client_bits < 8 || client_bits > 15) - client_bits = (size_t)-1; - } - if (FIO_STR_INFO_IS_EQ( - p.name, - FIO_STR_INFO2((char *)"server_max_window_bits", 22))) { - char *iptr = p.value.buf; - server_bits = iptr ? fio_atol10u(&iptr) : 0; - if (server_bits < 8 || server_bits > 15) - server_bits = (size_t)-1; - } - } - if (client_bits) - ; /* TODO */ - if (server_bits) - ; /* TODO */ - break; - } /* HAVE_ZLIB */ +SFUNC void fio___http_write_pid(fio_str_info_s *dest) { + static int last_pid = 0; + static char buf[64]; + static size_t len = 0; +#ifdef H___FIO_SERVER___H + int pid = fio_srv_pid(); +#else + int pid = fio_thread_getpid(); #endif - fio_http_upgrade_websocket(h); + if (last_pid != pid) + goto rewrite; +copy: + if (len) + FIO_MEMCPY(dest->buf + dest->len, buf, len); + dest->len += len; return; +rewrite: + len = 0; + buf[len++] = '['; + if (pid > 0) { + size_t d = fio_digits10u((uint64_t)pid); + fio_ltoa10u(buf + 1, (uint64_t)pid, d); + len += d; + } + buf[len++] = ']'; + last_pid = pid; + goto copy; +} +/** Logs an HTTP (response) to STDOUT. */ +SFUNC void fio_http_write_log(fio_http_s *h) { + FIO_STR_INFO_TMP_VAR(buf, 1023); + intptr_t bytes_sent = h->sent; + uint64_t time_start, time_end, time_proxy = 0; + time_start = h->received_at; + time_end = fio_http_get_timestump(); + fio_str_info_s date = fio_http_log_time(time_end / FIO___HTTP_TIME_DIV); + fio_string_write_s to_write[16] = { + FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->method)), + FIO_STRING_WRITE_STR2((const char *)" ", 1), + FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->path)), + FIO_STRING_WRITE_STR2((const char *)" ", 1), + FIO_STRING_WRITE_STR_INFO(fio_keystr_info(&h->version)), + FIO_STRING_WRITE_STR2((const char *)"\" ", 2), + FIO_STRING_WRITE_NUM(h->status), + FIO_STRING_WRITE_STR2(" ", 1), + ((bytes_sent > 0) ? (FIO_STRING_WRITE_UNUM(bytes_sent)) + : (FIO_STRING_WRITE_STR2((const char *)"---", 3))), + FIO_STRING_WRITE_STR2((const char *)" ", 1), + FIO_STRING_WRITE_NUM(time_end - time_start), + FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT "\r\n"), 4), + }; + if (FIO_HTTP_LOG_X_REQUEST_START) { + /* log request wait time using x-request-start header */ + fio_str_info_s xstart = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"x-request-start", 15), + 0); + unsigned step = + (xstart.len > 1 && (xstart.buf[0] | 32) == 't' && xstart.buf[1] == '='); + step <<= 1; + xstart.buf += step; + xstart.len -= step; + time_proxy = fio_atol(&xstart.buf); + time_proxy *= (FIO___HTTP_TIME_DIV / 1000); /* assumes info in ms */ + time_proxy = time_start - time_proxy; + if (time_proxy < (512 * FIO___HTTP_TIME_DIV)) { /* was ms? */ + to_write[11] = + FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT " (wait "), + 9); + to_write[12] = FIO_STRING_WRITE_NUM(time_proxy); + to_write[13] = + FIO_STRING_WRITE_STR2((const char *)(FIO___HTTP_TIME_UNIT ")\r\n"), + 5); + } + } -refuse_upgrade: - c->state.http = old; - if (fio_http_send_error_response(h, 403)) - fio_undup(c->io); - fio_http_free(h); + /* Write log line to buffer */ + fio___http_write_pid(&buf); + buf.buf[buf.len++] = ' '; + fio_http_from(&buf, h); + FIO_MEMCPY(buf.buf + buf.len, " - - ", 5); + FIO_MEMCPY(buf.buf + buf.len + 5, date.buf, date.len); + buf.len += date.len + 6; + buf.buf[buf.len++] = ' '; + buf.buf[buf.len++] = '\"'; + fio_string_write2 FIO_NOOP(&buf, NULL, to_write); + + if (buf.buf[buf.len - 1] != '\n') + buf.buf[buf.len++] = '\n'; /* log was truncated, data too long */ + + /* Write log line to STDOUT */ + fwrite(buf.buf, 1, buf.len, stdout); + h->received_at = time_end; +} + +/* ***************************************************************************** +ETag helper +***************************************************************************** */ + +/** Returns true (1) if the ETag response matches an if-none-match request. */ +SFUNC int fio_http_etag_is_match(fio_http_s *h) { + fio_str_info_s method = fio_keystr_info(&h->method); + if ((method.len < 3) | (method.len > 4)) + return 0; + if (!(((method.buf[0] | 32) == 'g') & ((method.buf[1] | 32) == 'e') & + ((method.buf[2] | 32) == 't')) && + !(((method.buf[0] | 32) == 'h') & ((method.buf[1] | 32) == 'e') & + ((method.buf[2] | 32) == 'a') & ((method.buf[3] | 32) == 'd'))) + return 0; + fio_str_info_s etag = fio___http_hmap_get2(HTTP_HDR_RESPONSE(h), + FIO_STR_INFO2((char *)"etag", 4), + 0); + if (!etag.len) + return 0; + fio_str_info_s cond = + fio___http_hmap_get2(HTTP_HDR_REQUEST(h), + FIO_STR_INFO2((char *)"if-none-match", 13), + 0); + if (!cond.len) + return 0; + char *end = cond.buf + cond.len; + for (;;) { + cond.buf += (cond.buf[0] == ','); + while (cond.buf[0] == ' ') + ++cond.buf; + if (cond.buf > end || (size_t)(end - cond.buf) < (size_t)etag.len) + return 0; + if (FIO_MEMCMP(cond.buf, etag.buf, etag.len)) { + cond.buf = (char *)FIO_MEMCHR(cond.buf, ',', end - cond.buf); + if (!cond.buf) + return 0; + continue; + } + return 1; + } } -FIO_SFUNC void fio___http_perform_user_upgrade_callback_sse(void *cb_, - void *h_) { - union { - int (*fn)(fio_http_s *); - void *ptr; - } cb = {.ptr = cb_}; - fio_http_s *h = (fio_http_s *)h_; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (cb.fn(h)) - goto refuse_upgrade; - if (c->h) /* request after eventsource? an attack vector? */ - goto refuse_upgrade; - fio_http_upgrade_sse(h); - return; +/* ***************************************************************************** +Param Parsing (TODO! - parse query, parse mime/multipart parse text/json) +***************************************************************************** */ -refuse_upgrade: - if (fio_http_send_error_response(h, 403)) - fio_undup(c->io); - fio_http_free(h); -} +/* ***************************************************************************** -FIO_IFUNC int fio___http_on_http_test4upgrade(fio_http_s *h, - fio___http_connection_s *c) { - union { - int (*fn)(fio_http_s *); - void *ptr; - } cb; - if (fio_http_websocket_requested(h)) - goto websocket_requested; - if (fio_http_sse_requested(h)) - goto sse_requested; - return 0; -websocket_requested: - cb.fn = c->settings->on_authenticate_websocket; - fio_queue_push(c->queue, - fio___http_perform_user_upgrade_callback_websocket, - cb.ptr, - (void *)h); - return -1; -sse_requested: - cb.fn = c->settings->on_authenticate_sse; - fio_queue_push(c->queue, - fio___http_perform_user_upgrade_callback_sse, - cb.ptr, - (void *)h); - return -1; + TODO WIP Marker!!! -#if 0 -http2_requested: - // Connection: Upgrade, HTTP2-Settings - // Upgrade: h2c - // HTTP2-Settings: - return 0; /* allowed to ignore upgrade request */ -#endif -} -FIO_SFUNC void fio___http_on_http_direct(void *h_, void *ignr) { - fio_http_s *h = (fio_http_s *)h_; - fio_http_status_set(h, 200); - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (fio___http_on_http_test4upgrade(h, c)) - return; - union { - void (*fn)(fio_http_s *); - void *ptr; - } cb = {.fn = c->state.http.on_http}; - fio_queue_push(c->queue, fio___http_perform_user_callback, cb.ptr, (void *)h); - (void)ignr; -} +***************************************************************************** */ -FIO_SFUNC void fio___http_on_http_with_public_folder(void *h_, void *ignr) { - fio_http_s *h = (fio_http_s *)h_; - fio_http_status_set(h, 200); - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (fio___http_on_http_test4upgrade(h, c)) - return; - if ((fio_http_method(h).len != 4 || (fio_buf2u32u(fio_http_method(h).buf) | - 0x20202020UL) != fio_buf2u32u("post")) && - !fio_http_static_file_response(h, - c->settings->public_folder, - fio_http_path(h), - c->settings->max_age)) { - fio_http_free(h); - return; - } - union { - void (*fn)(fio_http_s *); - void *ptr; - } cb = {.fn = c->state.http.on_http}; - fio_queue_push(c->queue, fio___http_perform_user_callback, cb.ptr, (void *)h); - (void)ignr; -} +/* ***************************************************************************** +Static file helper +***************************************************************************** */ -FIO_SFUNC void fio___http_perform_user_callback_client(void *cb_, void *h_) { - fio_http_s *h = (fio_http_s *)h_; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - fio___http_perform_user_callback(cb_, h_); - fio_undup(c->io); -} +/** + * Attempts to send a static file from the `root` folder. On success the + * response is complete and 0 is returned. Otherwise returns -1. + */ +SFUNC int fio_http_static_file_response(fio_http_s *h, + fio_str_info_s rt, + fio_str_info_s fnm, + size_t max_age) { + int fd = -1; + size_t file_length = 0; + /* combine public folder with path to get file name */ + fio_str_info_s mime_type = {0}; + FIO_STR_INFO_TMP_VAR(etag, 31); + FIO_STR_INFO_TMP_VAR(filename, 4095); + { /* test for HEAD and OPTIONS requests */ + fio_str_info_s m = fio_keystr_info(&h->method); + if ((m.len == 7 && (fio_buf2u64u(m.buf) | 0x2020202020202020ULL) == + (fio_buf2u64u("options") | 0x2020202020202020ULL))) + goto file_not_found; + } + rt.len -= ((rt.len > 0) && (fnm.len > 0 && fnm.buf[0] == '/') && + (rt.buf[rt.len - 1] == '/' || + rt.buf[rt.len - 1] == FIO_FOLDER_SEPARATOR)); + fio_string_write(&filename, NULL, rt.buf, rt.len); + fio_string_write_url_dec(&filename, NULL, fnm.buf, fnm.len); + if (fio_filename_is_unsafe_url(filename.buf)) + goto file_not_found; -FIO_SFUNC void fio___http_on_http_client(void *h_, void *ignr) { - fio_http_s *h = (fio_http_s *)h_; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - size_t pr = FIO___HTTP_PROTOCOL_WS; - union { - void (*fn)(fio_http_s *); - void *ptr; - } cb = {.fn = c->state.http.on_http}; + { /* Test for incomplete file name */ + size_t file_type = fio_filename_type(filename.buf); +#if defined(S_IFDIR) && defined(FIO_HTTP_DEFAULT_INDEX_FILENAME) + if (file_type == S_IFDIR) { + filename.len -= (filename.buf[filename.len - 1] == '/' || + filename.buf[filename.len - 1] == FIO_FOLDER_SEPARATOR); +#if FIO_HTTP_STATIC_FILE_COMPLETION + fio_string_write(&filename, + NULL, + "/" FIO_HTTP_DEFAULT_INDEX_FILENAME, + sizeof(FIO_HTTP_DEFAULT_INDEX_FILENAME)); + file_type = 0; +#else + fio_string_write( + &filename, + NULL, /* note that sizeof will count NUL, so we skip 1 char: */ + "/" FIO_HTTP_DEFAULT_INDEX_FILENAME ".html", + sizeof(FIO_HTTP_DEFAULT_INDEX_FILENAME ".html")); + file_type = fio_filename_type(filename.buf); +#endif /* FIO_HTTP_STATIC_FILE_COMPLETION */ + } +#endif /* S_IFDIR */ +#if FIO_HTTP_STATIC_FILE_COMPLETION + const fio_buf_info_s extensions[] = {FIO_BUF_INFO1((char *)".html"), + FIO_BUF_INFO1((char *)".htm"), + FIO_BUF_INFO1((char *)".txt"), + FIO_BUF_INFO1((char *)".md"), + FIO_BUF_INFO0}; + const fio_buf_info_s *pext = extensions; + while (!file_type) { + fio_string_write(&filename, NULL, pext->buf, pext->len); + file_type = fio_filename_type(filename.buf); + if (file_type) + break; + filename.len -= pext->len; + ++pext; + if (!pext->buf) + goto file_not_found; + } + switch (file_type) { + case S_IFREG: break; +#ifdef S_IFLNK + case S_IFLNK: break; +#endif + default: goto file_not_found; + } +#else /* FIO_HTTP_STATIC_FILE_COMPLETION */ + if (!file_type) + goto file_not_found; +#endif /* FIO_HTTP_STATIC_FILE_COMPLETION */ + } + { + /* find mime type if registered */ + char *end = filename.buf + filename.len; + char *ext = end; + do { + --ext; + } while (ext[0] != '.' && ext[0] != '/'); + if ((ext++)[0] == '.') { + mime_type = fio_http_mimetype(ext, end - ext); + if (!mime_type.len) + FIO_LOG_WARNING("missing mime-type for extension %s (not registered).", + ext); + } + } + { + fio_str_info_s ac = + fio_http_request_header(h, + FIO_STR_INFO2((char *)"accept-encoding", 15), + 0); + if (!ac.len) + goto accept_encoding_header_test_done; + struct { + char *value; + fio_buf_info_s ext; + } options[] = {{(char *)"gzip", FIO_BUF_INFO2((char *)".gz", 3)}, + {(char *)"br", FIO_BUF_INFO2((char *)".br", 3)}, + {(char *)"deflate", FIO_BUF_INFO2((char *)".zip", 4)}, + {NULL}}; + for (size_t i = 0; options[i].value; ++i) { + if (!strstr(ac.buf, options[i].value)) + continue; + fio_string_write(&filename, NULL, options[i].ext.buf, options[i].ext.len); + if (!fio_filename_type(filename.buf)) { + filename.len -= options[i].ext.len; + filename.buf[filename.len] = 0; + continue; + } + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"vary", 4), + FIO_STR_INFO2((char *)"accept-encoding", 15)); + fio_http_response_header_set( + h, + FIO_STR_INFO2((char *)"content-encoding", 16), + FIO_STR_INFO1(options[i].value)); + break; + } + } - /* TODO! review WS and SSE responses. */ - if (fio_http_websocket_accepted(h)) - goto websocket_accepted; - if (fio_http_sse_accepted(h)) - goto sse_accepted; - fio_queue_push(c->queue, - fio___http_perform_user_callback_client, - cb.ptr, - (void *)h); - return; - (void)ignr; +accept_encoding_header_test_done: + /* attempt to open file */ + fd = fio_filename_open(filename.buf, O_RDONLY); + if (fd == -1) + goto file_not_found; -sse_accepted: - pr = FIO___HTTP_PROTOCOL_SSE; + { /* test / validate etag */ + struct stat stt; + if (fstat(fd, &stt)) + goto file_not_found; + uint64_t etag_hash = fio_risky_hash(&stt, sizeof(stt), 0); + fio_string_write_hex(&etag, NULL, etag_hash); + fio_http_response_header_set(h, FIO_STR_INFO2((char *)"etag", 4), etag); + filename.len = 0; + filename.len = fio_time2rfc7231(filename.buf, stt.st_mtime); + fio_http_response_header_set(h, + FIO_STR_INFO1((char *)"last-modified"), + filename); + if (max_age) { + filename.len = 0; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("max-age=", 8), + FIO_STRING_WRITE_UNUM(max_age)); + fio_http_response_header_set(h, + FIO_STR_INFO1((char *)"cache-control"), + filename); + } + file_length = stt.st_size; + filename.capa = 0; + if (fio___http_response_etag_if_none_match(h)) + return 0; + } + /* test for range requests. */ + { + /* test / validate range requests */ + fio_str_info_s rng = + fio_http_request_header(h, FIO_STR_INFO2((char *)"range", 5), 0); + if (!rng.len) + goto range_request_review_finished; + { + fio_str_info_s ifrng = + fio_http_request_header(h, FIO_STR_INFO2((char *)"if-range", 8), 0); + if (ifrng.len && !FIO_STR_INFO_IS_EQ(ifrng, etag)) + goto range_request_review_finished; + } + if (rng.len < 7 || fio_buf2u32u(rng.buf) != fio_buf2u32u("byte") || + fio_buf2u16u(rng.buf + 4) != fio_buf2u16u("s=")) + goto range_request_review_finished; + char *ipos = rng.buf + 6; + size_t start_range = fio_atol10u(&ipos); + if (ipos == rng.buf + 6) + start_range = (size_t)-1; + if (*ipos != '-') + goto range_request_review_finished; + ++ipos; + size_t end_range = fio_atol10u(&ipos); + if (end_range > file_length) + goto range_request_review_finished; + if (!end_range) + end_range = file_length - 1; + if (start_range == (size_t)-1) { + start_range = file_length - end_range; + end_range = file_length - 1; + } + if (start_range > end_range || end_range > file_length) + goto invalid_range; + if (!start_range && end_range + 1 == file_length) + goto range_request_review_finished; + /* update response headers and info */ + h->status = 206; + filename.len = 0; + filename.capa = 1024; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("bytes ", 6), + FIO_STRING_WRITE_UNUM(start_range), + FIO_STRING_WRITE_STR2("-", 1), + FIO_STRING_WRITE_UNUM((end_range)), + FIO_STRING_WRITE_STR2("/", 1), + FIO_STRING_WRITE_UNUM(file_length)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-range", 13), + filename); + file_length = (end_range - start_range) + 1; + filename.capa = start_range; + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"etag", 4), + FIO_STR_INFO2(NULL, 0)); + } -websocket_accepted: - c->h = h; /* was set to NULL in `on_http_complete` */ - fio_http_controller_set( - c->h, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[pr] - .controller)); - fio_protocol_set( - c->io, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[pr] - .protocol)); +range_request_review_finished: + /* allow interrupted downloads to resume */ + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"accept-ranges", 13), + FIO_STR_INFO2((char *)"bytes", 5)); + /* test for HEAD requests */ + { + fio_str_info_s m = fio_keystr_info(&h->method); + if ((m.len == 4 && (fio_buf2u32u(m.buf) | 0x20202020UL) == + (fio_buf2u32u("head") | 0x20202020UL))) + goto head_request; + } - FIO_LOG_DDEBUG2("(%d) Client %s upgrade complete for fd %d", - fio_srv_pid(), - (fio_http_is_websocket(h) ? "WebSocket" : "SSE"), - fio_fd_get(c->io)); + /* finish up (set mime type and send file) */ + if (mime_type.len) + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-type", 12), + mime_type); + { /* send response (avoid macro for C++ compatibility) */ + fio_http_write_args_s args = { + .len = file_length, + .offset = filename.capa, /* now holds starting offset */ + .fd = fd, + .finish = 1}; + fio_http_write FIO_NOOP(h, args); + } + return 0; - fio_undup(c->io); /* fio_dup called by fio_http1_on_complete */ - c->suspend = 0; - fio_srv_unsuspend(c->io); -} +file_not_found: + if (fd != -1) + close(fd); + return -1; -/* ***************************************************************************** -ALPN Helpers -***************************************************************************** */ +head_request: + /* TODO! HEAD responses should close?. */ + if (fd != -1) + close(fd); + { + fio_http_write_args_s args = {.finish = 1}; + fio_http_write FIO_NOOP(h, args); + } + return 0; -FIO_SFUNC void fio___http_on_select_h1(fio_s *io) { - FIO_LOG_DDEBUG2("TLS ALPN HTTP/1.1 selected for %p", io); - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - fio_protocol_set( - io, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[FIO___HTTP_PROTOCOL_HTTP1] - .protocol)); -} -FIO_SFUNC void fio___http_on_select_h2(fio_s *io) { - FIO_LOG_ERROR("TLS ALPN HTTP/2 not supported for %p", io); - (void)io; +invalid_range: + filename.len = 0; + filename.capa = 1024; + fio_string_write2(&filename, + NULL, + FIO_STRING_WRITE_STR2("bytes */", 8), + FIO_STRING_WRITE_UNUM(file_length)); + fio_http_response_header_set(h, + FIO_STR_INFO2((char *)"content-range", 13), + filename); + return fio_http_send_error_response(h, 416); } /* ***************************************************************************** -HTTP Listen +Status Strings ***************************************************************************** */ -static void fio___http_listen_on_start(fio_protocol_s *protocol, void *u) { - (void)u; - fio___http_protocol_s *p = (fio___http_protocol_s *)protocol; - p->queue = ((p->settings.queue && p->settings.queue->q) ? p->settings.queue->q - : fio_srv_queue()); -} - -static void fio___http_listen_on_stop(fio_protocol_s *p, void *u) { - (void)u; - fio___http_protocol_free( - FIO_PTR_FROM_FIELD(fio___http_protocol_s, - state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, - p)); -} - -void fio_http_listen___(void); /* IDE marker */ -SFUNC void *fio_http_listen FIO_NOOP(const char *url, fio_http_settings_s s) { - http_settings_validate(&s, 0); - fio___http_protocol_s *p = fio___http_protocol_new(s.public_folder.len + 1); - fio___http_protocol_init(p, url, s, 0); - void *listener = - fio_srv_listen(.url = url, - .protocol = &p->state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, - .tls = s.tls, - .on_start = fio___http_listen_on_start, - .on_stop = fio___http_listen_on_stop, - .queue_for_accept = p->settings.queue); - return listener; +/** Returns a human readable string related to the HTTP status number. */ +SFUNC fio_str_info_s fio_http_status2str(size_t status) { + fio_str_info_s r = {0}; +#define HTTP_RETURN_STATUS(str) \ + do { \ + r.len = FIO_STRLEN(str); \ + r.buf = (char *)str; \ + return r; \ + } while (0); + switch (status) { + // clang-format off + case 100: HTTP_RETURN_STATUS("Continue"); + case 101: HTTP_RETURN_STATUS("Switching Protocols"); + case 102: HTTP_RETURN_STATUS("Processing"); + case 103: HTTP_RETURN_STATUS("Early Hints"); + case 110: HTTP_RETURN_STATUS("Response is Stale"); /* caching code*/ + case 111: HTTP_RETURN_STATUS("Re-validation Failed"); /* caching code*/ + case 112: HTTP_RETURN_STATUS("Disconnected Operation"); /* caching code*/ + case 113: HTTP_RETURN_STATUS("Heuristic Expiration"); /* caching code*/ + case 199: HTTP_RETURN_STATUS("Miscellaneous Warning"); /* caching code*/ + case 200: HTTP_RETURN_STATUS("OK"); + case 201: HTTP_RETURN_STATUS("Created"); + case 202: HTTP_RETURN_STATUS("Accepted"); + case 203: HTTP_RETURN_STATUS("Non-Authoritative Information"); + case 204: HTTP_RETURN_STATUS("No Content"); + case 205: HTTP_RETURN_STATUS("Reset Content"); + case 206: HTTP_RETURN_STATUS("Partial Content"); + case 207: HTTP_RETURN_STATUS("Multi-Status"); + case 208: HTTP_RETURN_STATUS("Already Reported"); + case 214: HTTP_RETURN_STATUS("Transformation Applied"); /* caching code*/ + case 218: HTTP_RETURN_STATUS("This is fine (Apache Web Server)"); /* unofficial */ + case 226: HTTP_RETURN_STATUS("IM Used"); + case 299: HTTP_RETURN_STATUS("Miscellaneous Persistent Warning"); /* caching code*/ + case 300: HTTP_RETURN_STATUS("Multiple Choices"); + case 301: HTTP_RETURN_STATUS("Moved Permanently"); + case 302: HTTP_RETURN_STATUS("Found"); + case 303: HTTP_RETURN_STATUS("See Other"); + case 304: HTTP_RETURN_STATUS("Not Modified"); + case 305: HTTP_RETURN_STATUS("Use Proxy"); + case 307: HTTP_RETURN_STATUS("Temporary Redirect"); + case 308: HTTP_RETURN_STATUS("Permanent Redirect"); + case 400: HTTP_RETURN_STATUS("Bad Request"); + case 401: HTTP_RETURN_STATUS("Unauthorized"); + case 402: HTTP_RETURN_STATUS("Payment Required"); + case 403: HTTP_RETURN_STATUS("Forbidden"); + case 404: HTTP_RETURN_STATUS("Not Found"); + case 405: HTTP_RETURN_STATUS("Method Not Allowed"); + case 406: HTTP_RETURN_STATUS("Not Acceptable"); + case 407: HTTP_RETURN_STATUS("Proxy Authentication Required"); + case 408: HTTP_RETURN_STATUS("Request Timeout"); + case 409: HTTP_RETURN_STATUS("Conflict"); + case 410: HTTP_RETURN_STATUS("Gone"); + case 411: HTTP_RETURN_STATUS("Length Required"); + case 412: HTTP_RETURN_STATUS("Precondition Failed"); + case 413: HTTP_RETURN_STATUS("Content Too Large"); + case 414: HTTP_RETURN_STATUS("URI Too Long"); + case 415: HTTP_RETURN_STATUS("Unsupported Media Type"); + case 416: HTTP_RETURN_STATUS("Range Not Satisfiable"); + case 417: HTTP_RETURN_STATUS("Expectation Failed"); + case 418: HTTP_RETURN_STATUS("I am a Teapot"); /* April Fool's Day, 1998 */ + case 419: HTTP_RETURN_STATUS("Page Expired (Laravel Framework)"); /* unofficial */ + case 420: HTTP_RETURN_STATUS("Enhance Your Calm (Twitter) - Method Failure (Spring Framework)"); /* unofficial */ + case 421: HTTP_RETURN_STATUS("Misdirected Request"); + case 422: HTTP_RETURN_STATUS("Unprocessable Content"); + case 423: HTTP_RETURN_STATUS("Locked"); + case 424: HTTP_RETURN_STATUS("Failed Dependency"); + case 425: HTTP_RETURN_STATUS("Too Early"); + case 426: HTTP_RETURN_STATUS("Upgrade Required"); + case 427: HTTP_RETURN_STATUS("Unassigned"); + case 428: HTTP_RETURN_STATUS("Precondition Required"); + case 429: HTTP_RETURN_STATUS("Too Many Requests"); + case 430: HTTP_RETURN_STATUS("Request Header Fields Too Large (Shopify)"); /* unofficial */ + case 431: HTTP_RETURN_STATUS("Request Header Fields Too Large"); + case 444: HTTP_RETURN_STATUS("No Response"); /* nginx code */ + case 450: HTTP_RETURN_STATUS("Blocked by Windows Parental Controls (Microsoft)"); /* unofficial */ + case 451: HTTP_RETURN_STATUS("Unavailable For Legal Reasons"); + case 494: HTTP_RETURN_STATUS("Request header too large"); /* nginx code */ + case 495: HTTP_RETURN_STATUS("SSL Certificate Error"); /* nginx code */ + case 496: HTTP_RETURN_STATUS("SSL Certificate Required"); /* nginx code */ + case 497: HTTP_RETURN_STATUS("HTTP Request Sent to HTTPS Port"); /* nginx code */ + case 498: HTTP_RETURN_STATUS("Invalid Token (Esri)"); /* unofficial */ + case 499: HTTP_RETURN_STATUS("Client Closed Request"); /* nginx code */ + case 500: HTTP_RETURN_STATUS("Internal Server Error"); + case 501: HTTP_RETURN_STATUS("Not Implemented"); + case 502: HTTP_RETURN_STATUS("Bad Gateway"); + case 503: HTTP_RETURN_STATUS("Service Unavailable"); + case 504: HTTP_RETURN_STATUS("Gateway Timeout"); + case 505: HTTP_RETURN_STATUS("HTTP Version Not Supported"); + case 506: HTTP_RETURN_STATUS("Variant Also Negotiates"); + case 507: HTTP_RETURN_STATUS("Insufficient Storage"); + case 508: HTTP_RETURN_STATUS("Loop Detected"); + case 509: HTTP_RETURN_STATUS("Bandwidth Limit Exceeded (Apache Web Server/cPanel)"); /* unofficial */ + case 510: HTTP_RETURN_STATUS("Not Extended"); + case 511: HTTP_RETURN_STATUS("Network Authentication Required"); + case 529: HTTP_RETURN_STATUS("Site is overloaded (Qualys)"); /* unofficial */ + case 530: HTTP_RETURN_STATUS("Site is frozen (Pantheon web)"); /* unofficial */ + case 598: HTTP_RETURN_STATUS("Network read timeout error"); /* unofficial */ + // clang-format on + } + HTTP_RETURN_STATUS("Unknown"); +#undef HTTP_RETURN_STATUS } /* ***************************************************************************** -HTTP Connect +MIME File Type Helpers ***************************************************************************** */ -static void fio___http_connect_on_failed(fio_protocol_s *p, void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - fio_http_free(c->h); - c->h = NULL; - fio___http_connection_free(c); - (void)p; -} +typedef struct { + uint64_t ext; + uint16_t len; + char mime[118]; /* all together 128 bytes per node */ +} fio___http_mime_info_s; -void fio_http_connect___(void); /* IDE Marker */ -/** Connects to HTTP / WebSockets / SSE connections on `url`. */ -SFUNC fio_s *fio_http_connect FIO_NOOP(const char *url, - fio_http_s *h, - fio_http_settings_s s) { - FIO_STR_INFO_TMP_VAR(origin, 4096); - http_settings_validate(&s, 1); - fio_url_s u = (fio_url_s){0}; - if (url) - u = fio_url_parse(url, strlen(url)); +#define FIO___HTTP_MIME_IS_VALID(o) ((o)->ext != 0) +#define FIO___HTTP_MIME_CMP(a, b) ((a)->ext == (b)->ext) +#define FIO___HTTP_MIME_HASH(o) fio_risky_num(((o)->ext), 0) - if (!h) - h = fio_http_new(); - if (!fio_http_path(h).len) - fio_http_path_set(h, - u.path.len ? FIO_BUF2STR_INFO(u.path) - : FIO_STR_INFO2((char *)"/", 1)); - if (!fio_http_query(h).len && u.query.len) - fio_http_query_set(h, FIO_BUF2STR_INFO(u.query)); - if (!fio_http_method(h).len) - fio_http_method_set(h, FIO_STR_INFO2((char *)"GET", 3)); - if (u.host.len) { - fio_http_request_header_set_if_missing(h, - FIO_STR_INFO2((char *)"host", 4), - FIO_BUF2STR_INFO(u.host)); - /* Origin header */ - fio_string_write2( - &origin, - NULL, - FIO_STRING_WRITE_STR2("https", (size_t)(4 + fio_url_is_tls(u).tls)), - FIO_STRING_WRITE_STR2("://", 3U), - FIO_STRING_WRITE_STR_INFO(u.host), - FIO_STRING_WRITE_STR2(":", (size_t)(!!u.port.len)), - FIO_STRING_WRITE_STR_INFO(u.port)); - } +#undef FIO_TYPEDEF_IMAP_REALLOC +#define FIO_TYPEDEF_IMAP_REALLOC(ptr, old_size, new_size, copy_len) \ + realloc(ptr, new_size) +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE 0 +#undef FIO_TYPEDEF_IMAP_FREE +#define FIO_TYPEDEF_IMAP_FREE(ptr, len) free(ptr) - /* test for ws:// or wss:// - WebSocket scheme */ - if ((u.scheme.len == 2 || - (u.scheme.len == 3 && ((u.scheme.buf[2] | 0x20) == 's'))) && - (fio_buf2u16u(u.scheme.buf) | 0x2020) == fio_buf2u16u("ws")) { - fio_http_request_header_set_if_missing(h, - FIO_STR_INFO2((char *)"origin", 6), - origin); - fio_http_websocket_set_request(h); - } - /* test for sse:// or sses:// - Server Sent Events scheme */ - else if ((u.scheme.len == 3 || - (u.scheme.len == 4 && ((u.scheme.buf[3] | 0x20) == 's'))) && - (fio_buf2u32u(u.scheme.buf) | fio_buf2u32u("\x20\x20\x20\xFF")) == - fio_buf2u32u("sse\xFF")) { - fio_http_request_header_set_if_missing(h, - FIO_STR_INFO2((char *)"origin", 6), - origin); - fio_http_sse_set_request(h); - } +FIO_TYPEDEF_IMAP_ARRAY(fio___http_mime_map, + fio___http_mime_info_s, + uint32_t, + FIO___HTTP_MIME_HASH, + FIO___HTTP_MIME_CMP, + FIO___HTTP_MIME_IS_VALID) - /* TODO: test for and attempt to re-use connection */ - // if (fio_http_cdata(h)) { } +static fio___http_mime_map_s FIO___HTTP_MIMETYPES; +#undef FIO___HTTP_MIME_IS_VALID +#undef FIO___HTTP_MIME_CMP +#undef FIO___HTTP_MIME_HASH - fio___http_protocol_s *p = fio___http_protocol_new(u.host.len); - fio___http_protocol_init(p, url, s, 1); - fio___http_connection_s *c = - fio___http_connection_new(p->settings.max_line_len); - FIO_ASSERT_ALLOC(c); - *c = (fio___http_connection_s){ - .io = NULL, - .h = h, - .settings = &(p->settings), - .queue = p->queue, - .udata = p->settings.udata, - .state.http = - { - .on_http_callback = p->on_http_callback, - .on_http = p->settings.on_http, - .on_finish = p->settings.on_finish, - .max_header = p->settings.max_header_size, - }, - .capa = p->settings.max_line_len, - .log = p->settings.log, - .is_client = 1, - }; - fio_http_controller_set(h, &p->state[FIO___HTTP_PROTOCOL_HTTP1].controller); - fio_http_udata_set(h, c->udata); - fio_http_cdata_set(h, fio___http_connection_dup(c)); - return fio_srv_connect(url, - .protocol = - &p->state[FIO___HTTP_PROTOCOL_HTTP1].protocol, - .on_failed = fio___http_connect_on_failed, - .udata = c, - .tls = s.tls, - .timeout = s.connect_timeout); -} +#undef FIO_TYPEDEF_IMAP_REALLOC +#define FIO_TYPEDEF_IMAP_REALLOC FIO_MEM_REALLOC +#undef FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE +#define FIO_TYPEDEF_IMAP_REALLOC_IS_SAFE FIO_MEM_REALLOC_IS_SAFE +#undef FIO_TYPEDEF_IMAP_FREE +#define FIO_TYPEDEF_IMAP_FREE FIO_MEM_FREE -/* ***************************************************************************** -HTTP/1.1 Request / Response Completed -***************************************************************************** */ +/** Registers a Mime-Type to be associated with the file extension. */ +SFUNC int fio_http_mimetype_register(char *file_ext, + size_t file_ext_len, + fio_str_info_s mime_type) { + fio___http_mime_info_s tmp, *old; + if (file_ext_len > 7 || mime_type.len > 117) + return -1; + tmp.ext = 0; + FIO_MEMCPY(&tmp.ext, file_ext, file_ext_len); + if (!mime_type.len) + goto remove_mime; + FIO_MEMCPY(&tmp.mime, mime_type.buf, mime_type.len); + tmp.len = mime_type.len; + tmp.mime[mime_type.len] = 0; + old = fio___http_mime_map_get(&FIO___HTTP_MIMETYPES, tmp); + if (old && old->len == tmp.len && !FIO_MEMCMP(old->mime, tmp.mime, tmp.len)) { + FIO_LOG_WARNING("mime-type collision: %.*s was %s, now %s", + (int)file_ext_len, + file_ext, + old->mime, + tmp.mime); + } + fio___http_mime_map_set(&FIO___HTTP_MIMETYPES, tmp, 1); + return 0; -/** called when either a request or a response was received. */ -static void fio_http1_on_complete(void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - fio_dup(c->io); /* make sure the IO and its data are valid in callback */ - fio_srv_suspend(c->io); - fio_http_s *h = c->h; - c->h = NULL; - c->suspend = 1; - // fio_srv_defer(c->state.http.on_http_callback, h, NULL); - fio_queue_push(fio_srv_queue(), c->state.http.on_http_callback, h); +remove_mime: + return fio___http_mime_map_remove(&FIO___HTTP_MIMETYPES, tmp); } -/* ***************************************************************************** -HTTP/1.1 Parser callbacks -***************************************************************************** */ - -FIO_IFUNC void fio___http_request_too_big(fio___http_connection_s *c) { - fio_http_s *h = c->h; - fio_dup(c->io); /* sending the response will result in fio_undup */ - fio_srv_suspend(c->io); - c->h = NULL; - c->suspend = 1; - if (fio_http_send_error_response(h, 413)) - fio_undup(c->io); /* response not sent, we need to fio_undup */ - fio_http_free(h); +/** Finds the Mime-Type associated with the file extension (if registered). */ +SFUNC fio_str_info_s fio_http_mimetype(char *file_ext, size_t file_ext_len) { + fio_str_info_s r = {0}; + fio___http_mime_info_s tmp, *val; + tmp.ext = 0; + FIO_MEMCPY(&tmp.ext, file_ext, file_ext_len); + val = fio___http_mime_map_get(&FIO___HTTP_MIMETYPES, tmp); + if (!val) + return r; + r.len = val->len; + r.buf = val->mime; + return r; } -FIO_IFUNC void fio_http1_attach_handle(fio___http_connection_s *c) { - c->h = fio_http_new(); - FIO_ASSERT_ALLOC(c->h); - fio_http_controller_set( - c->h, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings)) - ->state[FIO___HTTP_PROTOCOL_HTTP1] - .controller); - fio_http_udata_set(c->h, c->udata); - fio_http_cdata_set(c->h, fio___http_connection_dup(c)); -} +#define REGISTER_MIME(ext, type) \ + fio_http_mimetype_register((char *)ext, \ + sizeof(ext) - 1, \ + FIO_STR_INFO2((char *)type, sizeof(type) - 1)) -/** called when a request method is parsed. */ -static int fio_http1_on_method(fio_buf_info_s method, void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - if (c->h) - return -1; - fio_http1_attach_handle(c); - fio_http_method_set(c->h, FIO_BUF2STR_INFO(method)); - return 0; -} -/** called when a response status is parsed. the status_str is the string - * without the prefixed numerical status indicator.*/ -static int fio_http1_on_status(size_t istatus, - fio_buf_info_s status, - void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - fio_http_clear_response(c->h, istatus != 301 && istatus != 302); - fio_http_status_set(c->h, istatus); - return 0; - (void)status; -} -/** called when a request URL is parsed. */ -static int fio_http1_on_url(fio_buf_info_s url, void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - fio_url_s u = fio_url_parse(url.buf, url.len); - if (!u.path.len || u.path.buf[0] != '/') - return -1; - fio_http_path_set(c->h, FIO_BUF2STR_INFO(u.path)); - if (u.query.len) - fio_http_query_set(c->h, FIO_BUF2STR_INFO(u.query)); - if (u.host.len) - (!(c->h) ? fio_http_request_header_set - : fio_http_response_header_set)(c->h, - FIO_STR_INFO1((char *)"host"), - FIO_BUF2STR_INFO(u.host)); - return 0; -} -/** called when a the HTTP/1.x version is parsed. */ -static int fio_http1_on_version(fio_buf_info_s version, void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - FIO_ASSERT_DEBUG(c->h, "on_version called without a pre-existing handle!"); - if (!c->h) - return -1; - fio_http_version_set(c->h, FIO_BUF2STR_INFO(version)); - return 0; -} -/** called when a header is parsed. */ -static int fio_http1_on_header(fio_buf_info_s name, - fio_buf_info_s value, - void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - if (!c->h) - return 0; /* ignore possible post-error response headers */ - (!fio_http_status(c->h) - ? fio_http_request_header_add - : fio_http_response_header_add)(c->h, - FIO_BUF2STR_INFO(name), - FIO_BUF2STR_INFO(value)); - return 0; -} -/** called when the special content-length header is parsed. */ -static int fio_http1_on_header_content_length(fio_buf_info_s name, - fio_buf_info_s value, - size_t content_length, - void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - fio_http_s *h = c->h; - if (!h) - return 0; - if (content_length > c->settings->max_body_size) - goto too_big; - if (content_length) - fio_http_body_expect(c->h, content_length); -#if FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER - (!(h->status) ? fio_http_request_header_add - : fio_http_response_header_add)(h, - FIO_BUF2STR_INFO(name), - FIO_BUF2STR_INFO(value)); -#endif - return 0; -too_big: - fio___http_request_too_big(c); - return 0; /* should we disconnect (return -1), or not? */ - (void)name, (void)value; -} -/** called when `Expect` arrives and may require a 100 continue response. */ -static int fio_http1_on_expect(void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - const fio_buf_info_s response = - FIO_BUF_INFO1((char *)"HTTP/1.1 100 Continue\r\n\r\n"); - fio_http_s *h = c->h; - if (!h) - return 1; - c->h = NULL; - /* TODO: test for body size violation and deny request if payload too big. */ - if (FIO_HTTP1_EXPECTED_CHUNKED != fio_http1_expected(&c->state.http.parser) && - c->settings->max_body_size > fio_http1_expected(&c->state.http.parser)) - goto payload_too_big; - c->settings->pre_http_body(h); - if (fio_http_status(h)) - goto response_sent; - c->h = h; - fio_write2(c->io, .buf = response.buf, .len = response.len, .copy = 0); - return 0; /* TODO?: improve support for `expect` headers? */ -payload_too_big: - fio_dup(c->io); - if (fio_http_send_error_response(h, 413)) - fio_undup(c->io); /* response not sent, we need to fio_undup */ - /* fall through */ -response_sent: - // c->h = NULL; - fio_http_free(h); - return 1; +/** Registers known mime-types that aren't often used by Web Servers. */ +FIO_SFUNC void fio_http_mime_register_essential(void) { + /* clang-format off */ + REGISTER_MIME("3ds", "image/x-3ds"); + REGISTER_MIME("3g2", "video/3gpp"); + REGISTER_MIME("3gp", "video/3gpp"); + REGISTER_MIME("7z", "application/x-7z-compressed"); + REGISTER_MIME("aac", "audio/aac"); + REGISTER_MIME("abw", "application/x-abiword"); + REGISTER_MIME("aif", "audio/x-aiff"); + REGISTER_MIME("aifc", "audio/x-aiff"); + REGISTER_MIME("aiff", "audio/x-aiff"); + REGISTER_MIME("arc", "application/x-freearc"); + REGISTER_MIME("atom", "application/atom+xml"); + REGISTER_MIME("avi", "video/x-msvideo"); + REGISTER_MIME("avif", "image/avif"); + REGISTER_MIME("azw", "application/vnd.amazon.ebook"); + REGISTER_MIME("bin", "application/octet-stream"); + REGISTER_MIME("bmp", "image/bmp"); + REGISTER_MIME("bz", "application/x-bzip"); + REGISTER_MIME("bz2", "application/x-bzip2"); + REGISTER_MIME("cda", "application/x-cdf"); + REGISTER_MIME("csh", "application/x-csh"); + REGISTER_MIME("css", "text/css"); + REGISTER_MIME("csv", "text/csv"); + REGISTER_MIME("dmg", "application/x-apple-diskimage"); + REGISTER_MIME("doc", "application/msword"); + REGISTER_MIME("docx", "application/" "vnd.openxmlformats-officedocument.wordprocessingml.document"); + REGISTER_MIME("eot", "application/vnd.ms-fontobject"); + REGISTER_MIME("epub", "application/epub+zip"); + REGISTER_MIME("gif", "image/gif"); + REGISTER_MIME("gz", "application/gzip"); + REGISTER_MIME("htm", "text/html"); + REGISTER_MIME("html", "text/html"); + REGISTER_MIME("ico", "image/vnd.microsoft.icon"); + REGISTER_MIME("ics", "text/calendar"); + REGISTER_MIME("iso", "application/x-iso9660-image"); + REGISTER_MIME("jar", "application/java-archive"); + REGISTER_MIME("jpe", "image/jpeg"); + REGISTER_MIME("jpeg", "image/jpeg"); + REGISTER_MIME("jpg", "image/jpeg"); + REGISTER_MIME("jpgm", "video/jpm"); + REGISTER_MIME("jpgv", "video/jpeg"); + REGISTER_MIME("jpm", "video/jpm"); + REGISTER_MIME("js", "application/javascript"); + REGISTER_MIME("json", "application/json"); + REGISTER_MIME("jsonld", "application/ld+json"); + REGISTER_MIME("jsonml", "application/jsonml+json"); + REGISTER_MIME("md", "text/markdown"); + REGISTER_MIME("mid", "audio/midi"); + REGISTER_MIME("midi", "audio/midi"); + REGISTER_MIME("mjs", "text/javascript"); + REGISTER_MIME("mp3", "audio/mpeg"); + REGISTER_MIME("mp4", "video/mp4"); + REGISTER_MIME("m4v", "video/mp4"); + REGISTER_MIME("mpeg", "video/mpeg"); + REGISTER_MIME("mpkg", "application/vnd.apple.installer+xml"); + REGISTER_MIME("odp", "application/vnd.oasis.opendocument.presentation"); + REGISTER_MIME("ods", "application/vnd.oasis.opendocument.spreadsheet"); + REGISTER_MIME("odt", "application/vnd.oasis.opendocument.text"); + REGISTER_MIME("oga", "audio/ogg"); + REGISTER_MIME("ogv", "video/ogg"); + REGISTER_MIME("ogx", "application/ogg"); + REGISTER_MIME("opus", "audio/opus"); + REGISTER_MIME("otf", "font/otf"); + REGISTER_MIME("pdf", "application/pdf"); + REGISTER_MIME("php", "application/x-httpd-php"); + REGISTER_MIME("png", "image/png"); + REGISTER_MIME("ppt", "application/vnd.ms-powerpoint"); + REGISTER_MIME("pptx","application/""vnd.openxmlformats-officedocument.presentationml.presentation"); + REGISTER_MIME("rar", "application/vnd.rar"); + REGISTER_MIME("rtf", "application/rtf"); + REGISTER_MIME("sh", "application/x-sh"); + REGISTER_MIME("svg", "image/svg+xml"); + REGISTER_MIME("svgz", "image/svg+xml"); + REGISTER_MIME("tar", "application/x-tar"); + REGISTER_MIME("tif", "image/tiff"); + REGISTER_MIME("tiff", "image/tiff"); + REGISTER_MIME("ts", "video/mp2t"); + REGISTER_MIME("ttf", "font/ttf"); + REGISTER_MIME("txt", "text/plain"); + REGISTER_MIME("vsd", "application/vnd.visio"); + REGISTER_MIME("wav", "audio/wav"); + REGISTER_MIME("weba", "audio/webm"); + REGISTER_MIME("webm", "video/webm"); + REGISTER_MIME("webp", "image/webp"); + REGISTER_MIME("woff", "font/woff"); + REGISTER_MIME("woff2", "font/woff2"); + REGISTER_MIME("xhtml", "application/xhtml+xml"); + REGISTER_MIME("xls", "application/vnd.ms-excel"); + REGISTER_MIME("xlsx","application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + REGISTER_MIME("xml", "application/xml"); + REGISTER_MIME("xul", "application/vnd.mozilla.xul+xml"); + REGISTER_MIME("zip", "application/zip"); + /* clang-format on */ } -/** called when a body chunk is parsed. */ -static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - if (!c->h) - return -1; /* close connection if a large payload is unstoppable */ - if (c->is_client && - (fio_http_status(c->h) == 301 || fio_http_status(c->h) == 302)) - return 0; /* don't overwrite client payload on redirect */ - if (chunk.len + fio_http_body_length(c->h) > c->settings->max_body_size) - goto too_big; - fio_http_body_write(c->h, chunk.buf, chunk.len); - return 0; -too_big: - fio___http_request_too_big(c); - return 0; -} +#undef REGISTER_MIME /* ***************************************************************************** -HTTP/1.1 Accepting new connections (tests for special HTTP/2 pre-knowledge) +Constructor / Destructor ***************************************************************************** */ -/** Called when an IO is attached to a protocol. */ -FIO_SFUNC void fio___http_on_attach_accept(fio_s *io) { - - fio___http_protocol_s *p = - FIO_PTR_FROM_FIELD(fio___http_protocol_s, - state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, - fio_protocol_get(io)); - fio___http_protocol_dup(p); - // p->queue = fio_srv_queue(); - - const uint32_t capa = p->settings.max_line_len; - fio___http_connection_s *c = fio___http_connection_new(capa); - FIO_ASSERT_ALLOC(c); - *c = (fio___http_connection_s){ - .io = io, - .settings = &(p->settings), - .queue = - ((p->settings.queue && p->settings.queue->q) ? p->settings.queue->q - : fio_srv_queue()), - .udata = p->settings.udata, - .state.http = - { - .on_http_callback = p->on_http_callback, - .on_http = p->settings.on_http, - .on_finish = p->settings.on_finish, - .max_header = p->settings.max_header_size, - }, - .capa = capa, - .log = p->settings.log, - }; - fio_udata_set(io, (void *)c); - FIO_LOG_DDEBUG2("(%d) HTTP accepted a new connection (%p)", - (int)fio_thread_getpid(), - c->io); -#if 0 /* skip pre-knowledge test? */ - fio_protocol_set( - io, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[FIO___HTTP_PROTOCOL_HTTP1] - .protocol)); +FIO_SFUNC void fio___http_cleanup(void *ignr_) { + (void)ignr_; +#if FIO_HTTP_CACHE_LIMIT + for (size_t i = 0; i < 2; ++i) { + const char *names[] = {"cookie names", "header values"}; + FIO_LOG_DEBUG2( + "freeing %zu strings from %s cache (capacity was: %zu)", + fio___http_str_cache_count(&FIO___HTTP_STRING_CACHE[i].cache), + names[i], + fio___http_str_cache_capa(&FIO___HTTP_STRING_CACHE[i].cache)); +#ifdef FIO_LOG_LEVEL_DEBUG + if (FIO_LOG_LEVEL_DEBUG == FIO_LOG_LEVEL) { + FIO_MAP_EACH(fio___http_str_cache, + (&FIO___HTTP_STRING_CACHE[i].cache), + pos) { + fprintf(stderr, "\t \"%s\" (%zu bytes)\n", pos.key.buf, pos.key.len); + } + } #endif -} - -/** Called when a data is available. */ -FIO_SFUNC void fio___http1_accept_on_data(fio_s *io) { - const fio_buf_info_s prior_knowledge = FIO_BUF_INFO2( - (char *)"\x50\x52\x49\x20\x2a\x20\x48\x54\x54\x50\x2f\x32\x2e\x30" - "\x0d\x0a\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a", - 24); - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - fio_protocol_s *phttp_new; - size_t r = fio_read(io, c->buf + c->len, c->capa - c->len); - if (!r) /* nothing happened */ - return; - c->len = (uint32_t)r; - if (prior_knowledge.buf[0] != c->buf[0] || - FIO_MEMCMP( - prior_knowledge.buf, - c->buf, - (c->len > prior_knowledge.len ? prior_knowledge.len : c->len))) { - /* no prior knowledge, switch to HTTP/1.1 */ - phttp_new = - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[FIO___HTTP_PROTOCOL_HTTP1] - .protocol); - fio_protocol_set(io, phttp_new); - return; + fio___http_str_cache_destroy(&FIO___HTTP_STRING_CACHE[i].cache); + FIO___LOCK_DESTROY(FIO___HTTP_STRING_CACHE[i].lock); + (void)names; /* if unused */ } - if (c->len < prior_knowledge.len) /* wait for more data */ - return; - - if (c->len > prior_knowledge.len) - FIO_MEMMOVE(c->buf, - c->buf + prior_knowledge.len, - c->len - prior_knowledge.len); - c->len -= prior_knowledge.len; - phttp_new = &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[FIO___HTTP_PROTOCOL_HTTP2] - .protocol); - - fio_protocol_set(io, phttp_new); +#endif /* FIO_HTTP_CACHE_LIMIT */ + FIO_LOG_DEBUG2("(%d) HTTP MIME hash storage count/capa: %zu / %zu", + fio_getpid(), + FIO___HTTP_MIMETYPES.count, + fio___http_mime_map_capa(&FIO___HTTP_MIMETYPES)); + fio___http_mime_map_destroy(&FIO___HTTP_MIMETYPES); } -FIO_SFUNC void fio___http_on_close(void *udata) { - FIO_LOG_DDEBUG2("(%d) HTTP connection closed for %p", - (int)fio_thread_getpid(), - udata); - fio___http_connection_s *c = (fio___http_connection_s *)udata; - c->io = NULL; - fio_http_free(c->h); - fio___http_connection_free(c); +FIO_CONSTRUCTOR(fio___http_str_cache_static_builder) { + fio___http_str_cached_init(); + fio_state_callback_add(FIO_CALL_AT_EXIT, fio___http_cleanup, NULL); + fio_http_mime_register_essential(); } /* ***************************************************************************** -HTTP/1.1 Protocol +Cleanup ***************************************************************************** */ +#undef FIO___HTTP_TIME_DIV +#undef FIO___HTTP_TIME_UNIT -FIO_SFUNC int fio___http1_process_data(fio_s *io, fio___http_connection_s *c) { - (void)io, (void)c; - size_t consumed = fio_http1_parse(&c->state.http.parser, - FIO_BUF_INFO2(c->buf, c->len), - (void *)c); - if (!consumed) - return -1; - if (consumed == FIO_HTTP1_PARSER_ERROR) - goto http1_error; - c->len -= consumed; - if (c->len) - FIO_MEMMOVE(c->buf, c->buf + consumed, c->len); - if (c->suspend) - return -1; - return 0; +#endif /* FIO_EXTERN_COMPLETE */ -http1_error: - FIO_LOG_DDEBUG2("HTTP/1.1 parser error! disconnecting client at %d", - fio_fd_get(io)); - if (c->h) { - fio_http_s *h = c->h; - c->h = NULL; - if (!c->is_client) { - fio_dup(c->io); - if (fio_http_send_error_response(h, 400)) - fio_undup(c->io); - } - fio_http_free(h); - } - fio_close(io); - return -1; -} +#undef FIO_HTTP_HANDLE +#endif /* FIO_HTTP_HANDLE */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_HTTP1_PARSER /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** -// /** Called when a data is available. */ -FIO_SFUNC void fio___http1_on_data(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - size_t r; - for (;;) { - if (c->capa == c->len) - return; - if (!(r = fio_read(io, c->buf + c->len, c->capa - c->len))) - return; - c->len += r; - if (fio___http1_process_data(io, c)) - return; - } -} -// /** Called when an IO is attached to a protocol. */ -FIO_SFUNC void fio___http1_on_attach(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - if (c->len) - fio___http1_process_data(io, c); - return; -} -/* ***************************************************************************** -HTTP/1.1 Client Protocol -***************************************************************************** */ -/** Iterates through all cookies. A non-zero return will stop iteration. */ -FIO_SFUNC int fio_http1___write_client_cookie_callback(fio_http_s *h, - fio_str_info_s name, - fio_str_info_s value, - void *udata) { - fio_str_info_s *buf = (fio_str_info_s *)udata; - fio_string_write2(buf, - FIO_STRING_REALLOC, - FIO_STRING_WRITE_STR2("cookie:", 7), - FIO_STRING_WRITE_STR_INFO(name), - FIO_STRING_WRITE_STR2("=", 1), - FIO_STRING_WRITE_STR_INFO(value), - FIO_STRING_WRITE_STR2("\r\n", 2)); - return 0; - (void)h; -} + HTTP/1.1 Parser -/** called by the HTTP handle for each header. */ -FIO_SFUNC int fio_http1___write_header_callback(fio_http_s *h, - fio_str_info_s name, - fio_str_info_s value, - void *out_) { - (void)h; - /* manually copy, as this is an "all or nothing" copy (no truncation) */ - fio_str_info_s *out = (fio_str_info_s *)out_; - return fio_string_write2(out, - FIO_STRING_REALLOC, - FIO_STRING_WRITE_STR2(name.buf, name.len), - FIO_STRING_WRITE_STR2(":", 1), - FIO_STRING_WRITE_STR2(value.buf, value.len), - FIO_STRING_WRITE_STR2("\r\n", 2)); -} -FIO_SFUNC void fio___http1_send_request(fio_http_s *h) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (!c->io || !fio_srv_is_open(c->io)) - return; - fio_str_info_s buf = FIO_STR_INFO2(NULL, 0); - /* set Content-Length (client is never streaming) */ - if (fio_http_body_length(h)) { - char ibuf[32]; - fio_str_info_s k = FIO_STR_INFO2((char *)"content-length", 14); - fio_str_info_s v = FIO_STR_INFO3(ibuf, 0, 32); - v.len = fio_digits10u(fio_http_body_length(h)); - fio_ltoa10u(v.buf, fio_http_body_length(h), v.len); - fio_http_request_header_set(h, k, v); - } - { /* set sensible defaults for common headers (Accept, User-Agent) */ - fio_http_request_header_set_if_missing(h, - FIO_STR_INFO1((char *)"accept"), - FIO_STR_INFO1((char *)"*/*")); - fio_http_request_header_set_if_missing( - h, - FIO_STR_INFO1((char *)"user-agent"), - FIO_STR_INFO1((char *)"facil.io/" FIO_VERSION_STRING)); - } - { /* write status string */ - fio_str_info_s method = fio_http_method(h); - fio_str_info_s path = fio_http_path(h); - fio_str_info_s version = fio_http_version(h); - if (!path.len) - path = FIO_STR_INFO1((char *)"/"); - if ((version.len - 1) > 15) - version = FIO_STR_INFO1((char *)"HTTP/1.1"); - fio_string_write2(&buf, - FIO_STRING_REALLOC, - FIO_STRING_WRITE_STR_INFO(method), - FIO_STRING_WRITE_STR2(" ", 1), - FIO_STRING_WRITE_STR_INFO(path), - FIO_STRING_WRITE_STR2(" ", 1), - FIO_STRING_WRITE_STR_INFO(version), - FIO_STRING_WRITE_STR2("\r\n", 2)); - } - /* write headers */ - fio_http_request_header_each(h, fio_http1___write_header_callback, &buf); - /* write cookies */ - fio_http_cookie_each(h, fio_http1___write_client_cookie_callback, &buf); - fio_string_write(&buf, FIO_STRING_REALLOC, "\r\n", 2); - /* send data (moves memory ownership) */ - fio_write2(c->io, - .buf = buf.buf, - .len = buf.len, - .dealloc = FIO_STRING_FREE, - .copy = 0); - /* make sure we listen to incoming data */ - c->suspend = 0; - fio_srv_unsuspend(c->io); - /* Write Body */ - if (!fio_http_body_length(h)) - return; - fio_http_body_seek(h, 0); - if (fio_http_body_fd(h) == -1) { - buf = fio_http_body_read(h, (size_t)-1); - fio_write2(c->io, - .buf = (char *)fio_http_dup(h), - .len = buf.len, - .offset = (size_t)((char *)h - buf.buf), - .dealloc = (void (*)(void *))fio_http_free); - } else { - fio_write2(c->io, - .fd = fio_http_body_fd(h), - .len = fio_http_body_length(h), - .copy = 1); - } -} -FIO_SFUNC void fio___http1_on_attach_client(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - // c->io = fio_dup(io); - c->io = io; - fio___http1_send_request(c->h); - if (c->len) - fio___http1_process_data(io, c); - return; -} +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_HTTP1_PARSER) && !defined(H___FIO_HTTP1_PARSER___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) /* ***************************************************************************** -HTTP/1 Controller +The HTTP/1.1 provides static functions only, always as part or implementation. ***************************************************************************** */ -FIO_SFUNC int fio___http_controller_get_fd(fio_http_s *h) { - return fio_fd_get(fio_http_io(h)); -} - -/** Informs the controller that request / response headers must be sent. */ -FIO_SFUNC void fio___http_controller_http1_send_headers(fio_http_s *h) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (!c->io || !fio_srv_is_open(c->io)) - return; - fio_str_info_s buf = FIO_STR_INFO2(NULL, 0); - { /* write status string */ - fio_str_info_s ver = fio_http_version(h); - fio_str_info_s status = fio_http_status2str(fio_http_status(h)); - if (ver.len > 15) { - FIO_LOG_ERROR("HTTP/1.1 client version string too long!"); - ver = FIO_STR_INFO1((char *)"HTTP/1.1"); - } - fio_string_write2(&buf, - FIO_STRING_REALLOC, - FIO_STRING_WRITE_STR2(ver.buf, ver.len), - FIO_STRING_WRITE_STR2(" ", 1), - FIO_STRING_WRITE_NUM(fio_http_status(h)), - FIO_STRING_WRITE_STR2(" ", 1), - FIO_STRING_WRITE_STR2(status.buf, status.len), - FIO_STRING_WRITE_STR2("\r\n", 2)); - } +#define H___FIO_HTTP1_PARSER___H - /* write headers */ - fio_http_response_header_each(h, fio_http1___write_header_callback, &buf); - /* write cookies */ - fio_http_set_cookie_each(h, fio_http1___write_header_callback, &buf); - /* add streaming headers? */ - if (fio_http_is_streaming(h)) - fio_string_write(&buf, - FIO_STRING_REALLOC, - "transfer-encoding: chunked\r\n", - 28); - fio_string_write(&buf, FIO_STRING_REALLOC, "\r\n", 2); - /* send data (move memory ownership) */ - fio_write2(c->io, - .buf = buf.buf, - .len = buf.len, - .dealloc = FIO_STRING_FREE, - .copy = 0); -} -/** called by the HTTP handle for each body chunk (or to finish a response. */ -FIO_SFUNC void fio___http_controller_http1_write_body( - fio_http_s *h, - fio_http_write_args_s args) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (!c->io || !fio_srv_is_open(c->io)) - goto no_write_err; - if (fio_http_is_streaming(h)) - goto stream_chunk; - fio_write2(c->io, - .buf = (void *)args.buf, - .fd = args.fd, - .len = args.len, - .offset = args.offset, - .dealloc = args.dealloc, - .copy = (uint8_t)args.copy); - return; +/* ***************************************************************************** +HTTP/1.x Parser API +***************************************************************************** */ -stream_chunk: - if (args.len) { /* print chunk header */ - char buf[24]; - fio_str_info_s i = FIO_STR_INFO3(buf, 0, 24); - fio_string_write_hex(&i, NULL, args.len); - fio_string_write(&i, NULL, "\r\n", 2); - fio_write2(c->io, .buf = (void *)i.buf, .len = i.len, .copy = 1); - } else { - FIO_LOG_ERROR("HTTP1 streaming requires a correctly pre-determined " - "length per chunk."); - } - fio_write2(c->io, - .buf = (void *)args.buf, - .fd = args.fd, - .len = args.len, - .offset = args.offset, - .dealloc = args.dealloc, - .copy = (uint8_t)args.copy); - /* print chunk trailer */ - { - fio_buf_info_s trailer = FIO_BUF_INFO2((char *)"\r\n", 2); - fio_write2(c->io, .buf = trailer.buf, .len = trailer.len, .copy = 1); - } - return; -no_write_err: - if (args.buf) { - if (args.dealloc) - args.dealloc((void *)args.buf); - } else if (args.fd != -1) { - close(args.fd); - } -} +/** The HTTP/1.1 parser type */ +typedef struct fio_http1_parser_s fio_http1_parser_s; +/** Initialization value for the parser */ +#define FIO_HTTP1_PARSER_INIT ((fio_http1_parser_s){0}) -FIO_SFUNC void fio___http_controller_http1_on_finish_task(void *c_, - void *upgraded) { - fio___http_connection_s *c = (fio___http_connection_s *)c_; - c->suspend = 0; - if (upgraded) - goto upgraded; +/** + * Parses HTTP/1.x data, calling any callbacks. + * + * Returns bytes consumed or `FIO_HTTP1_PARSER_ERROR` (`(size_t)-1`) on error. + */ +FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, + fio_buf_info_s buf, + void *udata); - if (fio_srv_is_open(c->io)) { - /* TODO: test for connection:close header and h->status values */ - fio___http1_process_data(c->io, c); - } - if (!c->suspend) - fio_srv_unsuspend(c->io); - fio_undup(c->io); - return; +/** Returns true if the parser is waiting to parse a new request/response .*/ +FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p); -upgraded: - if (c->h || !fio_srv_is_open(c->io)) - goto something_is_wrong; - c->h = (fio_http_s *)upgraded; - { - const size_t pr_i = fio_http_is_websocket(c->h) ? FIO___HTTP_PROTOCOL_WS - : FIO___HTTP_PROTOCOL_SSE; - fio_http_controller_set( - c->h, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[pr_i] - .controller)); - fio_protocol_set( - c->io, - &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) - ->state[pr_i] - .protocol)); - if (pr_i == FIO___HTTP_PROTOCOL_SSE) { - fio_str_info_s last_id = - fio_http_request_header(c->h, - FIO_STR_INFO2((char *)"last-event-id", 13), - 0); - if (last_id.buf) - c->settings->on_eventsource_reconnect(c->h, FIO_STR2BUF_INFO(last_id)); - } - } - fio_srv_unsuspend(c->io); - fio_undup(c->io); - return; +/** The error return value for fio_http1_parse. */ +#define FIO_HTTP1_PARSER_ERROR ((size_t)-1) -something_is_wrong: - if (fio_srv_is_open(c->io)) - FIO_LOG_DEBUG2("(%d) Connection upgrade went wrong for fd %d - closing", - fio_srv_pid(), - fio_fd_get(c->io)); - fio_protocol_set(c->io, NULL); /* make zombie, timeout will clear it. */ - fio_undup(c->io); - fio___http_connection_free(c); /* free HTTP connection element */ -} +/** Returns the number of bytes of payload still expected to be received. */ +FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p); +/** A return value for `fio_http1_expected` when chunked data is expected. */ +#define FIO_HTTP1_EXPECTED_CHUNKED ((size_t)(-1)) -/** called once a request / response had finished */ -FIO_SFUNC void fio___http_controller_http1_on_finish(fio_http_s *h) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (fio_http_is_streaming(h)) - fio_write2(c->io, .buf = (char *)"0\r\n\r\n", .len = 5, .copy = 1); - if (c->log) - fio_http_write_log(h); - if (fio_http_is_upgraded(h)) - goto upgraded; - /* once the function returns, `h` may be freed (auto-finish on free). - * so we must call this callback here (sync), no matter the thread */ - c->state.http.on_finish(h); - fio_srv_defer(fio___http_controller_http1_on_finish_task, (void *)(c), NULL); - return; +/* ***************************************************************************** +HTTP/1.x callbacks (to be implemented by parser user) +***************************************************************************** */ -upgraded: - fio_srv_defer(fio___http_controller_http1_on_finish_task, - (void *)(c), - (void *)h); -} +/** called when either a request or a response was received. */ +static void fio_http1_on_complete(void *udata); +/** called when a request method is parsed. */ +static int fio_http1_on_method(fio_buf_info_s method, void *udata); +/** called when a response status is parsed. the status_str is the string + * without the prefixed numerical status indicator.*/ +static int fio_http1_on_status(size_t istatus, + fio_buf_info_s status, + void *udata); +/** called when a request URL is parsed. */ +static int fio_http1_on_url(fio_buf_info_s path, void *udata); +/** called when a the HTTP/1.x version is parsed. */ +static int fio_http1_on_version(fio_buf_info_s version, void *udata); +/** called when a header is parsed. */ +static int fio_http1_on_header(fio_buf_info_s name, + fio_buf_info_s value, + void *udata); +/** called when the special content-length header is parsed. */ +static int fio_http1_on_header_content_length(fio_buf_info_s name, + fio_buf_info_s value, + size_t content_length, + void *udata); +/** called when `Expect` arrives and may require a 100 continue response. */ +static int fio_http1_on_expect(void *udata); +/** called when a body chunk is parsed. */ +static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata); /* ***************************************************************************** -HTTP/2 Protocol (disconnect, as HTTP/2 is unsupported) +Implementation Stage Helpers ***************************************************************************** */ -// /** Called when an IO is attached to a protocol. */ -// void (*on_attach)(fio_s *io); -// /** Called when a data is available. */ -// void (*on_data)(fio_s *io); -// /** called once all pending `fio_write` calls are finished. */ -// void (*on_ready)(fio_s *io); -// /** Called after the connection was closed, and pending tasks -// completed. -// */ void (*on_close)(void *udata); +/* parsing stage 0 - read first line (proxy?). */ +static int fio_http1___start(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 1 - read headers. */ +static int fio_http1___read_header(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 2 - read body. */ +static int fio_http1___read_body(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 2 - read chunked body. */ +static int fio_http1___read_body_chunked(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* parsing stage 1 - read headers. */ +static int fio_http1___read_trailer(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); +/* completed parsing. */ +static int fio_http1___finish(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); /* ***************************************************************************** -HTTP/2 Controller (TODO!) +HTTP Parser Type ***************************************************************************** */ -// /** Called when an HTTP handle is freed. */ -// void (*on_destroyed)(fio_http_s *h, void *cdata); -// /** Informs the controller that request / response headers must be -// sent. -// */ void (*send_headers)(fio_http_s *h); -// /** called by the HTTP handle for each body chunk (or to finish a -// response. -// */ void (*write_body)(fio_http_s *h, fio_http_write_args_s args); -// /** called once a request / response had finished */ -// void (*on_finish)(fio_http_s *h); +/** The HTTP/1.1 parser type implementation */ +struct fio_http1_parser_s { + int (*fn)(fio_http1_parser_s *, fio_buf_info_s *, void *); + size_t expected; +}; + +/** Returns true if the parser is waiting to parse a new request/response .*/ +FIO_IFUNC size_t fio_http1_parser_is_empty(fio_http1_parser_s *p) { + return !p->fn || p->fn == fio_http1___start; +} + +/** Returns the number of bytes of payload still expected to be received. */ +FIO_IFUNC size_t fio_http1_expected(fio_http1_parser_s *p) { + return p->expected; +} /* ***************************************************************************** -Authentication Helper +Main Parsing Loop ***************************************************************************** */ -/** Allows all clients to connect (bypasses authentication). */ -SFUNC int FIO_HTTP_AUTHENTICATE_ALLOW(fio_http_s *h) { - ((void)h); - return 0; +FIO_SFUNC size_t fio_http1_parse(fio_http1_parser_s *p, + fio_buf_info_s buf, + void *udata) { + int i = 0; + char *buf_start = buf.buf; + if (!buf.len) + return 0; + if (!p->fn) + p->fn = fio_http1___start; + while (!(i = p->fn(p, &buf, udata))) + ; + if (i < 0) + return FIO_HTTP1_PARSER_ERROR; + return buf.buf - buf_start; +} + +/* completed parsing. */ +static int fio_http1___finish(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + (void)buf; + *p = (fio_http1_parser_s){0}; + fio_http1_on_complete(udata); + return 1; } /* ***************************************************************************** -WebSocket Parser Callbacks +Reading the first line ***************************************************************************** */ -FIO_SFUNC int fio___websocket_process_data(fio_s *io, - fio___http_connection_s *c); +/* parsing stage 0 - read first line (TODO: proxy protocol support?). */ +static int fio_http1___start(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + /* find line start/end and test */ + fio_buf_info_s wrd[3]; + char *start = buf->buf; + char *tmp; + while ((start[0] == ' ' || start[0] == '\r' || start[0] == '\n') && + start < buf->buf + buf->len) /* skip white space */ + ++start; + if (start == buf->buf + buf->len) { + buf->buf = start; + return 1; + } + char *eol = (char *)FIO_MEMCHR(start, '\n', buf->len); + if (!eol) + return 1; + if (start + 13 > eol) /* test for minimal data GET HTTP/1 or ### HTTP/1 */ + return -1; -FIO_SFUNC void fio___websocket_on_message_finalize(void *c_, void *ignr_) { - fio___http_connection_s *c = (fio___http_connection_s *)c_; - c->suspend = 0; - if (c->len) - fio___websocket_process_data(c->io, c); - fio_srv_unsuspend(c->io); - fio_undup(c->io); - fio___http_connection_free(c); - (void)ignr_; -} + /* prep next stage */ + buf->len -= (eol - buf->buf) + 1; + buf->buf = eol + 1; + eol -= eol[-1] == '\r'; -FIO_SFUNC void fio___websocket_on_message_task(void *c_, void *is_text) { - fio___http_connection_s *c = (fio___http_connection_s *)c_; - c->state.ws.on_message(c->h, - fio_bstr_buf(c->state.ws.msg), - (uint8_t)(uintptr_t)is_text); - fio_bstr_free(c->state.ws.msg); - c->state.ws.msg = NULL; - fio_srv_defer(fio___websocket_on_message_finalize, c, NULL); + /* parse first line */ + /* request: method path version ; response: version code txt */ + if (!(tmp = (char *)FIO_MEMCHR(start, ' ', (size_t)(eol - start)))) + return -1; + wrd[0] = FIO_BUF_INFO2(start, (size_t)(tmp - start)); + start = tmp + 1; + if (!(tmp = (char *)FIO_MEMCHR(start, ' ', eol - start))) + return -1; + wrd[1] = FIO_BUF_INFO2(start, (size_t)(tmp - start)); + start = tmp + 1; + if (start >= eol) + return -1; + wrd[2] = FIO_BUF_INFO2(start, (size_t)(eol - start)); + if (fio_c2i(wrd[1].buf[0]) < 10) /* test if path or code */ + goto parse_response_line; + if (wrd[2].len > 14) + wrd[2].len = 14; + if (fio_http1_on_method(wrd[0], udata)) + return -1; + if (fio_http1_on_url(wrd[1], udata)) + return -1; + if (fio_http1_on_version(wrd[2], udata)) + return -1; + return (p->fn = fio_http1___read_header)(p, buf, udata); + +parse_response_line: + if (wrd[0].len > 14) + wrd[0].len = 14; + if (fio_http1_on_version(wrd[0], udata)) + return -1; + if (fio_http1_on_status(fio_atol10u(&wrd[1].buf), wrd[2], udata)) + return -1; + return (p->fn = fio_http1___read_header)(p, buf, udata); } -/** Called when a message frame was received. */ -FIO_SFUNC void fio_websocket_on_message(void *udata, - fio_buf_info_s msg, - unsigned char is_text) { - /* TODO: suspend IO and queue in async queue? */ - fio___http_connection_s *c = (fio___http_connection_s *)udata; - // c->state.ws.on_message(c->h, - // fio_bstr_buf(c->state.ws.msg), - // (uint8_t)(uintptr_t)is_text); - // fio_bstr_free(c->state.ws.msg); - // c->state.ws.msg = NULL; - // c->suspend = 0; - // fio___websocket_process_data(c->io, c); - // if (!c->suspend) - // fio_srv_unsuspend(c->io); - // return; /* TODO: FIXME! */ - fio_dup(c->io); - fio___http_connection_dup(c); - fio_srv_suspend(c->io); - c->suspend = 1; - fio_queue_push(c->queue, - fio___websocket_on_message_task, - udata, - (void *)(uintptr_t)is_text); - (void)msg; -} +/* ***************************************************************************** +Reading Headers +***************************************************************************** */ -/** - * Called when the parser needs to copy the message to an external buffer. - * - * MUST return the external buffer, as it may need to be unmasked. - * - * Partial message length may be equal to zero (`partial.len == 0`). - */ -FIO_SFUNC fio_buf_info_s fio_websocket_write_partial(void *udata, - fio_buf_info_s partial, - size_t more_expected) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - if (!c->state.ws.msg && more_expected) - c->state.ws.msg = fio_bstr_reserve(NULL, more_expected + partial.len); - c->state.ws.msg = fio_bstr_write(c->state.ws.msg, partial.buf, partial.len); - return fio_bstr_buf(c->state.ws.msg); -} +/* parsing stage 1 - read headers (after `expect` header). */ +static int fio_http1___read_header_post_expect(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata); -/** Called when the permessage-deflate extension requires decompression. */ -FIO_SFUNC fio_buf_info_s fio_websocket_decompress(void *udata, - fio_buf_info_s msg) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - FIO_LOG_ERROR("WebSocket permessage-deflate not yet implemented!"); - (void)c; - return msg; +/* handle headers before calling callback. */ +static inline int fio_http1___on_header(fio_http1_parser_s *p, + fio_buf_info_s name, + fio_buf_info_s value, + void *udata) { + /* test for special headers */ + switch (name.len) { + case 6: /* test for "expect" */ + if (value.len == 12 && fio_buf2u32u(name.buf) == fio_buf2u32u("expe") && + fio_buf2u32u(name.buf + 2) == fio_buf2u32u("pect") && + fio_buf2u64u(value.buf) == fio_buf2u64u("100-cont") && + fio_buf2u32u(value.buf + 8) == fio_buf2u32u("inue")) { /* Expect */ + p->fn = fio_http1___read_header_post_expect; + return 0; + } + break; + case 14: /* test for "content-length" */ + if (fio_buf2u64u(name.buf) == fio_buf2u64u("content-") && + fio_buf2u64u(name.buf + 6) == fio_buf2u64u("t-length")) { + char *tmp = value.buf; + uint64_t clen = fio_atol10u(&tmp); + if (tmp != value.buf + value.len) + return -1; + if (p->expected) + return 0 - (p->expected != clen); + p->expected = clen; + return 0 - + (fio_http1_on_header_content_length(name, value, clen, udata) == + -1); + } + break; + case 17: /* test for "transfer-encoding" (chunked?) */ + if (value.len >= 7 && (name.buf[16] == 'g') && + !((fio_buf2u64u(name.buf) ^ fio_buf2u64u("transfer")) | + (fio_buf2u64u(name.buf + 8) ^ fio_buf2u64u("-encodin")))) { + char *c_start = value.buf + value.len - 7; + if ((fio_buf2u32u(c_start) | 0x20202020UL) == fio_buf2u32u("chun") && + (fio_buf2u32u(c_start + 3) | 0x20202020UL) == fio_buf2u32u("nked")) { + if (p->expected && p->expected != FIO_HTTP1_EXPECTED_CHUNKED) + return -1; + p->expected = FIO_HTTP1_EXPECTED_CHUNKED; + /* endpoint does not need to know if the body was chunked or not */ + if (value.len == 7) + return 0; + if (c_start[-1] != ' ' && c_start[-1] != ',' && c_start[-1] != '\t') + return -1; + while ( + (c_start[-1] == ' ' || c_start[-1] == ',' || c_start[-1] == '\t') && + c_start > value.buf) + --c_start; + if (c_start == value.buf) + return 0; + value.len = c_start - value.buf; + } + } + break; + } + /* perform callback */ + return 0 - (fio_http1_on_header(name, value, udata) == -1); } -/** Called when a `ping` message was received. */ -FIO_SFUNC void fio_websocket_on_protocol_ping(void *udata, fio_buf_info_s msg) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - if (msg.len < 248) { - char buf[256]; - size_t len = - (c->is_client - ? fio_websocket_client_wrap - : fio_websocket_server_wrap)(buf, msg.buf, msg.len, 0x0A, 1, 1, 0); - fio_write2(c->io, .buf = buf, .len = len, .copy = 1); - } else { - char *pong = fio_bstr_reserve(NULL, msg.len + 11); - size_t len = (c->is_client ? fio_websocket_client_wrap - : fio_websocket_server_wrap)(pong, - msg.buf, - msg.len, - 0x0A, - 1, - 1, - 0); - pong = fio_bstr_len_set(pong, len); - fio_write2(c->io, - .buf = pong, - .len = len, - .dealloc = (void (*)(void *))fio_bstr_free); +/* handle trailers (chunked encoding only) before calling callback. */ +static inline int fio_http1___on_trailer(fio_http1_parser_s *p, + fio_buf_info_s name, + fio_buf_info_s value, + void *udata) { + (void)p; + fio_buf_info_s forbidden[] = { + FIO_BUF_INFO1((char *)"authorization"), + FIO_BUF_INFO1((char *)"cache-control"), + FIO_BUF_INFO1((char *)"content-encoding"), + FIO_BUF_INFO1((char *)"content-length"), + FIO_BUF_INFO1((char *)"content-range"), + FIO_BUF_INFO1((char *)"content-type"), + FIO_BUF_INFO1((char *)"expect"), + FIO_BUF_INFO1((char *)"host"), + FIO_BUF_INFO1((char *)"max-forwards"), + FIO_BUF_INFO1((char *)"set-cookie"), + FIO_BUF_INFO1((char *)"te"), + FIO_BUF_INFO1((char *)"trailer"), + FIO_BUF_INFO1((char *)"transfer-encoding"), + FIO_BUF_INFO2(NULL, 0), + }; /* known forbidden headers in trailer */ + for (size_t i = 0; forbidden[i].buf; ++i) { + if (FIO_BUF_INFO_IS_EQ(name, forbidden[i])) + return -1; } - fio_bstr_free(c->state.ws.msg); - c->state.ws.msg = NULL; + return fio_http1_on_header(name, value, udata); } -/** Called when a `pong` message was received. */ -FIO_SFUNC void fio_websocket_on_protocol_pong(void *udata, fio_buf_info_s msg) { -#if (DEBUG - 1 + 1) || (FIO_WEBSOCKET_STATS - 1 + 1) - { - char *pos = msg.buf; - static uint64_t longest = 0; - uint64_t ping_time = fio_srv_last_tick() - fio_atol16u(&pos); - if (ping_time < (1 << 16) && longest < ping_time) { - longest = ping_time; - FIO_LOG_INFO("WebSocket longest ping round-trip detected as: %zums", - (size_t)ping_time); - } - } -#endif - FIO_LOG_DDEBUG2("Pong (%zu): %s", msg.len, msg.buf); - (void)msg; /* do nothing */ - fio___http_connection_s *c = (fio___http_connection_s *)udata; - fio_bstr_free(c->state.ws.msg); - c->state.ws.msg = NULL; +/* returns either a lower case (ASCI) or the original char. */ +static uint8_t fio_http1_tolower(uint8_t c) { + if ((c - ((uint8_t)'A' - 1U)) < ((uint8_t)'Z' - (uint8_t)'A')) + c |= 32; + return c; } -/** Called when a `close` message was received. */ -FIO_SFUNC void fio_websocket_on_protocol_close(void *udata, - fio_buf_info_s msg) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - char buf[32]; - size_t len = fio_websocket_server_wrap(buf, NULL, 0, 0x08, 1, 1, 0); - fio_write(c->io, buf, len); - if (msg.len > 1) - c->state.ws.code = fio_buf2u16_be(msg.buf); - fio_close(c->io); - if (msg.len > 2) - FIO_LOG_DDEBUG2("WebSocket %p closed with error message: %s", - c->io, - msg.buf + 2); - (void)msg; +/* seeks to the ':' divisor while testing and converting to downcase. */ +static char *fio_http1___seek_header_div(char *p) { + /* this is the subset of the forbidden chars that allows UTF-8 headers */ + static const _Bool forbidden_name_chars[256] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + for (;;) { + *p = (char)fio_http1_tolower((uint8_t)(*p)); + ++p; + if (FIO_UNLIKELY(forbidden_name_chars[((uint8_t)(*p))])) + return p; + } } -/* ***************************************************************************** -WebSocket Protocol -***************************************************************************** */ +/* extract header name and value from a line and pass info to handler */ +static inline int fio_http1___read_header_line( + fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata, + int (*handler)(fio_http1_parser_s *, + fio_buf_info_s, + fio_buf_info_s, + void *)) { + for (;;) { + char *start = buf->buf; + char *eol = (char *)FIO_MEMCHR(start, '\n', buf->len); + char *div; + fio_buf_info_s name, value; + if (!eol) + return 1; -FIO_SFUNC int fio___websocket_process_data(fio_s *io, - fio___http_connection_s *c) { - (void)io, (void)c; - size_t consumed = fio_websocket_parse(&c->state.ws.parser, - FIO_BUF_INFO2(c->buf, c->len), - (void *)c); - if (!consumed) - return -1; - if (consumed == FIO_WEBSOCKET_PARSER_ERROR) - goto ws_error; - c->len -= consumed; - if (c->len) - FIO_MEMMOVE(c->buf, c->buf + consumed, c->len); - if (c->suspend) - return -1; - return 0; + buf->len -= (eol - buf->buf) + 1; + buf->buf = eol + 1; + eol -= (eol[-1] == '\r'); + if (FIO_UNLIKELY(eol == start)) + goto headers_finished; -ws_error: - FIO_LOG_DDEBUG2("WebSocket protocol error?"); - fio_websocket_on_protocol_close((void *)c, ((fio_buf_info_s){0})); - return -1; -} + div = fio_http1___seek_header_div(start); + if (div[0] != ':') + return -1; + name = FIO_BUF_INFO2(start, (size_t)(div - start)); + do { + ++div; + } while (*div == ' ' || *div == '\t'); -/** Called when a data is available. */ -FIO_SFUNC void fio___websocket_on_data(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - size_t r; - for (;;) { - if (c->capa == c->len) - return; - if (!(r = fio_read(io, c->buf + c->len, c->capa - c->len))) - return; - c->len += r; - if (fio___websocket_process_data(io, c)) - return; + if (div != eol) + while (eol[-1] == ' ' || eol[-1] == '\t') + --eol; + value = FIO_BUF_INFO2((div == eol) ? NULL : div, (size_t)(eol - div)); + int r = handler(p, name, value, udata); + if (FIO_UNLIKELY(r)) + return r; } -} -FIO_SFUNC void fio___websocket_on_ready(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - fio_http_s *h = c->h; - if (!h) - return; - c->state.ws.on_ready(h); -} +headers_finished: + if (p->fn == fio_http1___read_header_post_expect && p->expected && + fio_http1_on_expect(udata)) + goto expect_failed; + p->fn = (!p->expected) ? fio_http1___finish + : (!(p->expected + 1)) ? fio_http1___read_body_chunked + : fio_http1___read_body; + return p->fn(p, buf, udata); -FIO_SFUNC void fio___websocket_on_timeout(fio_s *io) { - char buf[32]; - char tm[20] = "0x00000000000000000"; - fio_ltoa16u(tm + 2, fio_srv_last_tick(), 16); - size_t len = fio_websocket_server_wrap(buf, tm, 18, 0x09, 1, 1, 0); - fio_write(io, buf, len); +expect_failed: + *p = (fio_http1_parser_s){0}; + return 1; } -FIO_SFUNC void fio___websocket_on_shutdown(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - c->settings->on_shutdown(c->h); - fio_websocket_on_protocol_close(c, ((fio_buf_info_s){0})); +/* parsing stage 1 - read headers. */ +static int fio_http1___read_header(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return fio_http1___read_header_line(p, buf, udata, fio_http1___on_header); } -/** Called when an IO is attached to a protocol. */ -FIO_SFUNC void fio___websocket_on_attach(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - fio_http_s *h = c->h; - c->state.ws = (struct fio___http_connection_ws_s){ - .on_message = c->settings->on_message, - .on_ready = c->settings->on_ready, - .parser = {.must_mask = !c->is_client}, - }; - c->settings->on_open(h); - fio___websocket_process_data(io, c); +/* parsing stage 1 - read headers (after `expect` header). */ +static int fio_http1___read_header_post_expect(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return fio_http1___read_header_line(p, buf, udata, fio_http1___on_header); } -/** Called after the connection was closed, and pending tasks completed. */ -FIO_SFUNC void fio___websocket_on_close(void *udata) { - FIO_LOG_DDEBUG2("(%d) WebSocket connection closed for %p", - (int)fio_thread_getpid(), - udata); - fio___http_connection_s *c = (fio___http_connection_s *)udata; - c->io = NULL; - fio_bstr_free(c->state.ws.msg); - if (c->h) { - fio_http_status_set(c->h, (size_t)(c->state.ws.code)); - c->settings->on_close(c->h); - c->settings->on_finish(c->h); - fio_http_free(c->h); - } - fio___http_connection_free(c); +/* parsing stage 1 - read headers. */ +static int fio_http1___read_trailer(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return fio_http1___read_header_line(p, buf, udata, fio_http1___on_trailer); } -/** - * Sets a specific on_message callback for this connection. - * - * Returns -1 on error (i.e., upgrade still in negotiation). - */ -SFUNC int fio_http_on_message_set(fio_http_s *h, - void (*on_message)(fio_http_s *, - fio_buf_info_s, - uint8_t)) { - if (!h) - return -1; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (!c) +/* ***************************************************************************** +Reading the Body +***************************************************************************** */ + +/* parsing stage 2 - read body - known content length. */ +static int fio_http1___read_body(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + if (!buf->len) + return 1; + if (buf->len >= p->expected) { + buf->len = p->expected; + if (fio_http1_on_body_chunk(*buf, udata)) + return -1; + buf->buf += buf->len; + return fio_http1___finish(p, buf, udata); + } + if (fio_http1_on_body_chunk(*buf, udata)) return -1; - if (!on_message) - on_message = c->settings->on_message; - c->state.ws.on_message = on_message; - return 0; + buf->buf += buf->len; + p->expected -= buf->len; + buf->len = 0; + return 1; } /* ***************************************************************************** -WebSocket Writing / Subscription Helpers +Reading the Body (chunked) ***************************************************************************** */ -FIO_IFUNC void fio___http_websocket_subscribe_imp(fio_msg_s *msg, - uint8_t is_text) { - fio___http_connection_s *c = - (fio___http_connection_s *)fio_udata_get(msg->io); - if (!c) - return; - fio_http_websocket_write(c->h, msg->message.buf, msg->message.len, is_text); -} - -/** Optional WebSocket subscription callback - all messages are UTF-8 valid. */ -SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT(fio_msg_s *msg) { - fio___http_websocket_subscribe_imp(msg, 1); -} -/** Optional WebSocket subscription callback - messages may be non-UTF-8. */ -SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY(fio_msg_s *msg) { - fio___http_websocket_subscribe_imp(msg, 0); +/* parsing stage 2 - read chunked body - read chunk data. */ +static int fio_http1___read_body_chunked_read(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + if (!buf->len) + return 1; + if (buf->len >= p->expected) { + if (fio_http1_on_body_chunk(FIO_BUF_INFO2(buf->buf, p->expected), udata)) + return -1; + buf->buf += p->expected; + buf->len -= p->expected; + p->fn = fio_http1___read_body_chunked; + return 0; + } + if (fio_http1_on_body_chunk(buf[0], udata)) + return -1; + p->expected -= buf->len; + buf->buf += buf->len; + return 1; } -/** Optional WebSocket subscription callback. */ -SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT(fio_msg_s *msg) { - ((msg->message.len < FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT) && - (fio_string_utf8_valid( - FIO_STR_INFO2((char *)msg->message.buf, msg->message.len))) - ? FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT - : FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY)(msg); -} +/* parsing stage 2 - read chunked body - read next chunk length. */ +static int fio_http1___read_body_chunked(fio_http1_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + (void)udata; + if (buf->len < 3) + return 1; + { /* remove possible extra EOL after chunk payload */ + size_t tmp = (buf->buf[0] == '\r'); + tmp += (buf->buf[tmp] == '\n'); + buf->len -= tmp; + buf->buf += tmp; + } -/* ***************************************************************************** -EventSource (SSE) Helpers - HTTP Upgraded Connections -***************************************************************************** */ + // if (!FIO_MEMCHR(buf->buf, '\n', buf->len)) /* prevent read overflow? */ + // return 1; -void fio_http_sse_write___(void); /* IDE Marker */ -/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ -SFUNC int fio_http_sse_write FIO_NOOP(fio_http_s *h, - fio_http_sse_write_args_s args) { - if (!args.data.len || !h || !fio_http_is_sse(h)) + char *eol = buf->buf; + size_t expected = fio_atol16u(&eol); /* may read overflow, tests after */ + if (eol == buf->buf) return -1; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (!c || !c->io) + eol += (eol[0] == '\r'); + if (eol >= buf->buf + buf->len) + return 1; /* read overflowed */ + if (eol[0] != '\n') return -1; - char *payload = - fio_bstr_reserve(NULL, args.id.len + args.event.len + args.data.len + 22); - if (args.id.len) - payload = fio_bstr_write2(payload, - FIO_STRING_WRITE_STR2("id:", 3), - FIO_STRING_WRITE_STR2(args.id.buf, args.id.len), - FIO_STRING_WRITE_STR2("\r\n", 2)); - if (args.event.len) - payload = - fio_bstr_write2(payload, - FIO_STRING_WRITE_STR2("event:", 6), - FIO_STRING_WRITE_STR2(args.event.buf, args.event.len), - FIO_STRING_WRITE_STR2("\r\n", 2)); - { /* separate lines (add "data:" at beginning of each new line) */ - char *pos; - while (args.data.len && - (pos = (char *)FIO_MEMCHR(args.data.buf, '\n', args.data.len))) { - const size_t len = (pos + 1) - args.data.buf; - pos -= (pos[-1] == '\r'); - payload = fio_bstr_write2( - payload, - FIO_STRING_WRITE_STR2("data:", 5), - FIO_STRING_WRITE_STR2(args.data.buf, (size_t)(pos - args.data.buf)), - FIO_STRING_WRITE_STR2("\r\n", 2)); - args.data.buf += len; - args.data.len -= len; - } + ++eol; + p->expected = expected; + if (p->expected) { + /* further data expected */ + buf->len -= eol - buf->buf; + buf->buf = eol; + return (p->fn = fio_http1___read_body_chunked_read)(p, buf, udata); } - /* write reminder */ - if (args.data.len) - payload = - fio_bstr_write2(payload, - FIO_STRING_WRITE_STR2("data:", 5), - FIO_STRING_WRITE_STR2(args.data.buf, args.data.len), - FIO_STRING_WRITE_STR2("\r\n", 2)); - /* event ends on empty line */ - payload = fio_bstr_write(payload, "\r\n", 2); - fio_write2(c->io, - .buf = payload, - .len = fio_bstr_len(payload), - .dealloc = (void (*)(void *))fio_bstr_free); - return 0; + if ((eol + 1 < buf->buf + buf->len) && (eol[0] == '\r' || eol[0] == '\n')) { + /* no trailers, finish now. */ + eol += (eol[0] == '\r'); + ++eol; + buf->len -= eol - buf->buf; + buf->buf = eol; + return fio_http1___finish(p, buf, udata); + } + /* possible trailers */ + buf->len -= eol - buf->buf; + buf->buf = eol; + return (p->fn = fio_http1___read_trailer)(p, buf, udata); } -/** Optional EventSource subscription callback - messages MUST be UTF-8. */ -SFUNC void FIO_HTTP_SSE_SUBSCRIBE_DIRECT(fio_msg_s *msg) { - fio___http_connection_s *c = - (fio___http_connection_s *)fio_udata_get(msg->io); - if (!c) - return; - FIO_STR_INFO_TMP_VAR(id_str, 64); - fio_string_write_hex(&id_str, NULL, msg->id); - fio_http_sse_write(c->h, - .id = FIO_STR2BUF_INFO(id_str), - .event = FIO_STR2BUF_INFO(msg->channel), - .data = FIO_STR2BUF_INFO(msg->message)); -} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#undef FIO_HTTP1_PARSER +#endif /* FIO_HTTP1_PARSER && FIO_EXTERN_COMPLETE*/ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_BITWISE /* Development inclusion - ignore line */ +#define FIO_RAND /* Development inclusion - ignore line */ +#define FIO_WEBSOCKET_PARSER /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + WebSocket Parser + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_WEBSOCKET_PARSER) && !defined(H___FIO_WEBSOCKET_PARSER___H) && \ + (defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN)) /* ***************************************************************************** -WebSocket Writing / Subscription Helpers +The parser provides static functions only, always as part or implementation. ***************************************************************************** */ +#define H___FIO_WEBSOCKET_PARSER___H -SFUNC int fio_http_websocket_write(fio_http_s *h, - const void *buf, - size_t len, - uint8_t is_text) { - if (!h || !fio_http_is_websocket(h)) - return -1; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (!c) - return -1; - is_text = (!!is_text); - is_text |= (!is_text) << 1; - uint8_t rsv = 0; - if (len < 512) { /* fast-path: no allocation, no compression */ - char tmp[520]; - size_t wlen = - (c->is_client - ? fio_websocket_client_wrap - : fio_websocket_server_wrap)(tmp, buf, len, is_text, 1, 1, rsv); - fio_write2(c->io, .buf = tmp, .len = wlen, .copy = 1); - return 0; - } -#if HAVE_ZLIB /* TODO: compress? */ - // if(c->state.ws.deflate) ; -#endif - char *payload = - fio_bstr_reserve(NULL, - fio_websocket_wrapped_len(len) + (c->is_client << 2)); - payload = fio_bstr_len_set( - payload, - (c->is_client - ? fio_websocket_client_wrap - : fio_websocket_server_wrap)(payload, buf, len, is_text, 1, 1, rsv)); - fio_write2(c->io, - .buf = payload, - .len = fio_bstr_len(payload), - .dealloc = (void (*)(void *))fio_bstr_free); - return 0 - !fio_srv_is_open(c->io); -} +/* ***************************************************************************** +WebSocket Parsing API +***************************************************************************** */ + +typedef struct fio_websocket_parser_s fio_websocket_parser_s; +/** + * Parses WebSocket data, calling any callbacks. + * + * Returns bytes consumed or `FIO_WEBSOCKET_PARSER_ERROR` (`(size_t)-1`) on + * error. + */ +FIO_SFUNC size_t fio_websocket_parse(fio_websocket_parser_s *p, + fio_buf_info_s buf, + void *udata); + +// FIO_SFUNC + +/** The parsers return value on error. */ +#define FIO_WEBSOCKET_PARSER_ERROR ((size_t)-1) /* ***************************************************************************** -WebSocket Controller +WebSocket Parsing Callbacks ***************************************************************************** */ -/* Called by the HTTP handle for each body chunk (or to finish a response). */ -FIO_SFUNC void fio___http_controller_ws_write_body(fio_http_s *h, - fio_http_write_args_s args) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (args.buf && args.len < FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT) { - unsigned char is_text = - !!fio_string_utf8_valid(FIO_STR_INFO2((char *)args.buf, args.len)); - fio_http_websocket_write(h, (void *)args.buf, args.len, is_text); - if (args.dealloc) - args.dealloc((void *)args.buf); - return; - } - char header[16]; - ((uint8_t *)header)[0] = 0 | 2 | 128; - if (args.len < 126) { - ((uint8_t *)header)[1] = args.len; - fio_write(c->io, header, 2); - } else if (args.len < (1UL << 16)) { - /* head is 4 bytes */ - ((uint8_t *)header)[1] = 126 | ((!!c->is_client) << 7); - fio_u2buf16_be(((uint8_t *)header + 2), args.len); - fio_write(c->io, header, 4); - } else { - /* Really Long Message */ - ((uint8_t *)header)[1] = 127 | ((!!c->is_client) << 7); - fio_u2buf64_be(((uint8_t *)header + 2), args.len); - fio_write(c->io, header, 10); - } - fio_write2(c->io, - .buf = (void *)args.buf, - .fd = args.fd, - .len = args.len, - .offset = args.offset, - .dealloc = args.dealloc, - .copy = (uint8_t)args.copy); -} +/** Called when a message frame was received. */ +FIO_SFUNC void fio_websocket_on_message(void *udata, + fio_buf_info_s msg, + unsigned char is_text); + +/** + * Called when the parser needs to copy the message to an external buffer. + * + * MUST return the external buffer, as it may need to be unmasked. + * + * Partial message length may be equal to zero (`partial.len == 0`). + */ +FIO_SFUNC fio_buf_info_s fio_websocket_write_partial(void *udata, + fio_buf_info_s partial, + size_t more_expected); + +/** Called when the permessage-deflate extension requires decompression. */ +FIO_SFUNC fio_buf_info_s fio_websocket_decompress(void *udata, + fio_buf_info_s msg); + +/** Called when a `ping` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_ping(void *udata, fio_buf_info_s msg); + +/** Called when a `pong` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_pong(void *udata, fio_buf_info_s msg); + +/** Called when a `close` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_close(void *udata, fio_buf_info_s msg); /* ***************************************************************************** -EventSource / SSE Protocol (TODO!) +WebSocket Formatting API ***************************************************************************** */ +/** + * Returns the length of the buffer required to wrap a message `len` long + * + * Client connections should add 4 to this number to accommodate for the mask. + */ +FIO_IFUNC uint64_t fio_websocket_wrapped_len(uint64_t len); -FIO_SFUNC void fio___sse_consume_data(fio___http_connection_s *c) { - /* TODO: Fix Me! parse and process SSE data */ - FIO_LOG_DEBUG2("SSE data processing:\n%.*s", (int)c->len, c->buf); - struct fio___http_connection_sse_s *sse = &c->state.sse; - const char *next_line = c->buf; - const char *stop = c->buf + c->len; - for (; next_line < stop;) { - char *line = (char *)next_line; - const char *eol = - (const char *)FIO_MEMCHR(next_line, '\n', stop - next_line); - if (!eol) - break; - next_line = eol + 1; - eol -= (eol > c->buf && eol[-1] == '\n'); - eol -= (eol > c->buf && eol[-1] == '\r'); - if (eol == line) { /* empty line, end of input? */ - if (sse->data || sse->event.buf || sse->id.buf) { - sse->on_message(c->h, sse->id, sse->event, fio_bstr_buf(sse->data)); - fio_bstr_free(sse->data); - sse->data = NULL; - sse->event = sse->id = FIO_BUF_INFO0; - } - continue; - } - if (line[0] == ':') /* comment */ - continue; - const size_t line_len = (size_t)(eol - line); - if (line_len > 2 && line[2] == ':') { /* id */ - const char *start = line + 3; - start += (start[0] == ' ' || start[0] == '\t'); - if ((line[0] |= 32) == 'i' && (line[1] |= 32) == 'd') - sse->id = FIO_BUF_INFO2((char *)start, (size_t)(eol - start)); +/** + * Wraps a WebSocket server message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * * client: set to 1 to use client mode (data masking). + * + * Further opcode values: + * * %x0 denotes a continuation frame + * * %x1 denotes a text frame + * * %x2 denotes a binary frame + * * %x3-7 are reserved for further non-control frames + * * %x8 denotes a connection close + * * %x9 denotes a ping + * * %xA denotes a pong + * * %xB-F are reserved for further control frames + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len)` + */ +FIO_SFUNC uint64_t fio_websocket_server_wrap(void *target, + const void *msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv); - } else if (line_len > 4 && line[4] == ':') { /* data */ - const char *start = line + 5; - start += (start[0] == ' ' || start[0] == '\t'); - if ((fio_buf2u32u(line) | 0x20202020U) == fio_buf2u32u("data")) { - if (fio_bstr_len(sse->data) + (size_t)(eol - start) > - c->settings->ws_max_msg_size) - goto breach; - sse->data = fio_bstr_write2( - sse->data, - FIO_STRING_WRITE_STR2("\r\n", ((size_t) !!sse->data << 1)), - FIO_STRING_WRITE_STR2(start, (size_t)(eol - start))); - } +/** + * Wraps a WebSocket client message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * * client: set to 1 to use client mode (data masking). + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len) + 4` + */ +FIO_SFUNC uint64_t fio_websocket_client_wrap(void *target, + const void *msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv); - } else if (line_len > 5 && line[5] == ':') { /* event */ - const char *start = line + 3; - start += (start[0] == ' ' || start[0] == '\t'); - if ((line[0] |= 32) == 'e' && - (fio_buf2u32u(line + 1) | 0x20202020U) == fio_buf2u32u("vent")) - sse->event = FIO_BUF_INFO2((char *)start, (size_t)(eol - start)); +/* ***************************************************************************** +API - Parsing (unwrapping) +***************************************************************************** */ - } else if (!FIO_MEMCHR(line, ':', line_len)) - goto error; - } - FIO_ASSERT(next_line <= stop, "overflow on next line read"); - if (next_line > stop) - next_line = stop; - c->len -= next_line - c->buf; - if (c->len) - FIO_MEMMOVE(c->buf, next_line, c->len); - return; +/* ***************************************************************************** -error: - FIO_LOG_ERROR("SSE incoming data malformed!"); - FIO_LOG_DEBUG2("data dump:\n%.*s", (int)c->len, c->buf); - fio_close(c->io); - return; + Implementation -breach: - FIO_LOG_SECURITY("SSE incoming data payload too large!"); - fio_close(c->io); -} +***************************************************************************** */ -/** Called when a data is available. */ -FIO_SFUNC void fio___sse_on_data(fio_s *io) { - FIO_LOG_DDEBUG2("(%d) Reading SSE data from socket", fio_srv_pid()); - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - size_t r; - for (;;) { - if (c->len + 2 > c->capa) - goto error; - if (!(r = fio_read(io, c->buf + c->len, c->capa - c->len))) - return; - c->len += r; - fio___sse_consume_data(c); - } -error: - FIO_LOG_ERROR("Incoming SSE data too long (HTTP line limit set at %zu)!", - c->capa); - fio_close(io); +/** returns the length of the buffer required to wrap a message `len` long */ +FIO_IFUNC uint64_t fio_websocket_wrapped_len(uint64_t len) { + return len + 2ULL + ((len > 125) << 1) + + ((0ULL - (len > ((1UL << 16) - 1))) & 6ULL); } -/** Called when an IO is attached to a protocol. */ -static void fio___sse_on_attach(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - fio_http_s *h = c->h; - c->state.sse = (struct fio___http_connection_sse_s){ - .on_message = c->settings->on_eventsource, - .on_ready = c->settings->on_ready, - }; - c->settings->on_open(h); - FIO_LOG_DDEBUG2("(%d) SSE attached; buffer length (unread): %zu", - fio_srv_pid(), - c->len); - if (c->len && c->is_client) - fio___sse_consume_data(c); -} +/* ***************************************************************************** +Message Wrapping +***************************************************************************** */ -FIO_SFUNC void fio___sse_on_timeout(fio_s *io) { - char buf[32] = ":ping 0x0000000000000000\r\n\r\n"; - fio_ltoa16u(buf + 8, fio_srv_last_tick(), 16); - buf[24] = '\r'; /* overwrite written NUL character */ - fio_write(io, buf, 28); +FIO_IFUNC uint64_t fio_websocket_header(void *target, + uint64_t message_len, + uint32_t mask, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv) { + ((uint8_t *)target)[0] = 0U | + /*fin*/ ((last & 1U) << 7) | + /* opcode */ ((16U - !!first) & (opcode & 15U)) | + /* rsv */ ((rsv & 7) << 4); + ((uint8_t *)target)[1] = ((!!mask) << 7U); + size_t mask_l = ((!!mask) << 2); + if (message_len < 126) { + ((uint8_t *)target)[1] |= message_len; + if (mask) + fio_u2buf32u(((uint8_t *)target + 2), mask); + return 2 + mask_l; + } else if (message_len < (1UL << 16)) { + /* head is 4 bytes */ + ((uint8_t *)target)[1] |= 126; + fio_u2buf16_be(((uint8_t *)target + 2), message_len); + if (mask) + fio_u2buf32u(((uint8_t *)target + 4), mask); + return 4 + mask_l; + } else { + /* Really Long Message */ + ((uint8_t *)target)[1] |= 127; + fio_u2buf64_be(((uint8_t *)target + 2), message_len); + if (mask) + fio_u2buf32u(((uint8_t *)target + 10), mask); + return 10 + mask_l; + } } -FIO_SFUNC void fio___sse_on_shutdown(fio_s *io) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_udata_get(io); - c->settings->on_shutdown(c->h); - // fio_websocket_on_protocol_close(c, ((fio_buf_info_s){0})); +/** + * Wraps a WebSocket server message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * * client: set to 1 to use client mode (data masking). + * + * Further opcode values: + * * %x0 denotes a continuation frame + * * %x1 denotes a text frame + * * %x2 denotes a binary frame + * * %x3-7 are reserved for further non-control frames + * * %x8 denotes a connection close + * * %x9 denotes a ping + * * %xA denotes a pong + * * %xB-F are reserved for further control frames + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len)` + */ +FIO_SFUNC uint64_t fio_websocket_server_wrap(void *restrict target, + const void *restrict msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv) { + uint64_t r = fio_websocket_header(target, len, 0, opcode, first, last, rsv); + FIO_MEMCPY(((uint8_t *)target) + r, msg, len); + r += len; + return r; } -/** Called after the connection was closed, and pending tasks completed. */ -FIO_SFUNC void fio___sse_on_close(void *udata) { - fio___http_connection_s *c = (fio___http_connection_s *)udata; - FIO_LOG_DDEBUG2("(%d) SSE connection closed for %p", fio_srv_pid(), c->io); - c->io = NULL; - fio_bstr_free(c->state.sse.data); - if (c->h) { - c->settings->on_close(c->h); - c->settings->on_finish(c->h); - fio_http_free(c->h); - } - fio___http_connection_free(c); +/** + * Wraps a WebSocket client message and writes it to the target buffer. + * + * The `first` and `last` flags can be used to support message fragmentation. + * + * * target: the target buffer to write to. + * * msg: the message to be wrapped. + * * len: the message length. + * * opcode: set to 1 for UTF-8 message, 2 for binary, etc'. + * * first: set to 1 if `msg` points the beginning of the message. + * * last: set to 1 if `msg + len` ends the message. + * + * Returns the number of bytes written. Always `websocket_wrapped_len(len) + + * 4` + */ +FIO_SFUNC uint64_t fio_websocket_client_wrap(void *restrict target, + const void *restrict msg, + uint64_t len, + unsigned char opcode, + unsigned char first, + unsigned char last, + unsigned char rsv) { + uint64_t mask = (fio_rand64() | 0x01020408ULL) & 0xFFFFFFFFULL; /* non-zero */ + mask |= mask << 32; + uint64_t r = fio_websocket_header(target, + len, + (uint32_t)mask, + opcode, + first, + last, + rsv); + fio_xmask_cpy((((char *)target) + r), (const char *)msg, len, mask); + r += len; + return r; } /* ***************************************************************************** -EventSource / SSE Controller (TODO!) +WebSocket Parser Type ***************************************************************************** */ -/* called by the HTTP handle for each body chunk (or to finish a response. */ -FIO_SFUNC void fio___http_controller_sse_write_body( - fio_http_s *h, - fio_http_write_args_s args) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - if (args.buf && args.len) { - fio_http_sse_write(c->h, .data = FIO_BUF_INFO2((char *)args.buf, args.len)); - } - if (args.dealloc && args.buf) - args.dealloc((void *)args.buf); - if (!args.buf && (unsigned)(args.fd + 1) > 1) - close(args.fd); -} +/** The WebSocket parser type implementation */ +struct fio_websocket_parser_s { + int (*fn)(fio_websocket_parser_s *, fio_buf_info_s *, void *); + uint64_t start_at; + uint64_t expect; + uint32_t mask; + uint8_t first; + uint8_t current; + uint8_t must_mask; +}; + /* ***************************************************************************** -Connection Lost +Frame Consumption ***************************************************************************** */ +FIO_SFUNC int fio___websocket_consume_header(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata); -FIO_SFUNC void fio___http_controller_on_destroyed_task(void *c_, void *ignr_) { - fio___http_connection_s *c = (fio___http_connection_s *)c_; - fio___http_connection_free(c); - (void)ignr_; -} - -FIO_SFUNC void fio___http_controller_http1_on_finish_client_task(void *c_, - void *h_) { - fio___http_connection_s *c = (fio___http_connection_s *)c_; - fio_http_s *h = (fio_http_s *)h_; - c->settings->on_finish(h); - fio_http_free(h); - fio___http_connection_free(c); +FIO_SFUNC int fio___websocket_consume_frame_partial(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + fio_websocket_write_partial(udata, *buf, (p->expect -= buf->len)); + buf->buf += buf->len; + buf->len = 0; + return 1; } -FIO_SFUNC void fio___http_controller_http1_on_finish_client(fio_http_s *h) { - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - /* on_finish should be called after the `on_close` or after on_http */ - if (!fio_http_is_upgraded(h)) { - /* on_finish always manually called here */ - fio_srv_defer(fio___http_controller_http1_on_finish_client_task, - (void *)fio___http_connection_dup(c), - (void *)fio_http_dup(h)); +FIO_SFUNC int fio___websocket_consume_frame_finish(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + fio_buf_info_s msg = FIO_BUF_INFO2(buf->buf, p->expect); + buf->buf += p->expect; + buf->len -= p->expect; + p->expect = 0; + msg = fio_websocket_write_partial(udata, msg, 0); + if (!msg.buf) /* protocol error response from callback */ + return -1; + fio_xmask(msg.buf + p->start_at, + msg.len - p->start_at, + (((uint64_t)p->mask) << 32) | (uint64_t)p->mask); + p->start_at += msg.len; + p->fn = fio___websocket_consume_header; + if (!(p->current & 128)) /* done? if not, consume next frame */ + return 0; + /* done */ + if (p->first & 64) { /* RSV1 set: decompress */ + msg = fio_websocket_decompress(udata, msg); + if (!msg.buf) + return -1; } -} - -/** Called when an HTTP handle is freed. */ -FIO_SFUNC void fio__http_controller_on_destroyed(fio_http_s *h) { - if (!(fio_http_is_upgraded(h) | fio_http_is_finished(h))) { - /* auto-finish if freed without finishing */ - if (!fio_http_status(h)) - fio_http_status_set(h, 500); /* ignored if headers already sent */ - fio_http_write_args_s args = {.finish = 1}; /* never sets upgrade flag */ - fio_http_write FIO_NOOP(h, args); + size_t cond = (p->first & 15); + *p = (fio_websocket_parser_s){.fn = fio___websocket_consume_header}; + switch (cond) { + case 0: return -1; /* continuation - error? */ + case 1: /* fall through */ /* text / data frame */ + case 2: fio_websocket_on_message(udata, msg, (cond & 1)); return 1; + case 8: fio_websocket_on_protocol_close(udata, msg); return 1; + case 9: fio_websocket_on_protocol_ping(udata, msg); return 1; + case 10: fio_websocket_on_protocol_pong(udata, msg); return 1; + default: + FIO_LOG_DDEBUG2("ERROR: WebSocket protocol error - unknown opcode %u\n", + (unsigned int)(p->first & 15)); + return -1; } - fio_queue_push(fio_srv_queue(), - fio___http_controller_on_destroyed_task, - fio_http_cdata(h)); -} - -/** Called when an HTTP handle is freed (no auto-finish, post upgrade). */ -FIO_SFUNC void fio__http_controller_on_destroyed2(fio_http_s *h) { - fio_queue_push(fio_srv_queue(), - fio___http_controller_on_destroyed_task, - fio_http_cdata(h)); + return 1; } -/** Called when an HTTP handle is freed. */ -FIO_SFUNC void fio__http_controller_on_destroyed_client(fio_http_s *h) { - fio_queue_push(fio_srv_queue(), - fio___http_controller_on_destroyed_task, - fio_http_cdata(h)); - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - c->state.http.on_finish(h); - c->h = NULL; - if (c->io) - fio_close(c->io); - fio_queue_push(fio_srv_queue(), fio___http_controller_on_destroyed_task, c); +FIO_SFUNC int fio___websocket_consume_frame(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + return (p->expect > buf->len + ? fio___websocket_consume_frame_partial + : fio___websocket_consume_frame_finish)(p, buf, udata); } /* ***************************************************************************** -The Protocols at play +Header Consumption ***************************************************************************** */ +FIO_SFUNC int fio___websocket_consume_header(fio_websocket_parser_s *p, + fio_buf_info_s *buf, + void *udata) { + if (buf->len < 2) + return 1; + const uint8_t mask_f = (((uint8_t *)buf->buf)[1] >> 7) & 1; + const uint8_t mask_l = (mask_f << 2); + const uint8_t info = (uint8_t)(buf->buf[0]); + uint8_t len_indicator = ((((uint8_t *)buf->buf)[1]) & 127U); + switch (len_indicator) { + case 126: + if (buf->len < 8UL) + return 1; + p->expect = fio_buf2u16_be(buf->buf + 2); + p->mask = (0ULL - mask_f) & fio_buf2u32u(buf->buf + 4); + buf->buf += 4 + mask_l; + buf->len -= 4 + mask_l; + break; -/** Returns a facil.io protocol object with the proper protocol callbacks. */ -FIO_IFUNC fio_protocol_s FIO_NOOP -fio___http_protocol_get(fio___http_protocol_selector_e s, int is_client) { - fio_protocol_s r = {0}; - (void)is_client, (void)s; - switch (s) { - case FIO___HTTP_PROTOCOL_ACCEPT: - r = (fio_protocol_s){.on_attach = fio___http_on_attach_accept, - .on_data = fio___http1_accept_on_data, - .on_close = fio___http_on_close}; - return r; - case FIO___HTTP_PROTOCOL_HTTP1: - if (is_client) { - r = (fio_protocol_s){.on_attach = fio___http1_on_attach_client, - .on_data = fio___http1_on_data, - .on_close = fio___http_on_close}; - } else { - r = (fio_protocol_s){.on_attach = fio___http1_on_attach, - .on_data = fio___http1_on_data, - .on_close = fio___http_on_close}; - } - return r; - case FIO___HTTP_PROTOCOL_HTTP2: - r = (fio_protocol_s){.on_close = fio___http_on_close}; - return r; - case FIO___HTTP_PROTOCOL_WS: - r = (fio_protocol_s){ - .on_attach = fio___websocket_on_attach, - .on_data = fio___websocket_on_data, - .on_ready = fio___websocket_on_ready, - .on_close = fio___websocket_on_close, - .on_shutdown = fio___websocket_on_shutdown, - .on_timeout = fio___websocket_on_timeout, - .on_pubsub = FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT, - }; - return r; - case FIO___HTTP_PROTOCOL_SSE: - r = (fio_protocol_s){ - .on_attach = fio___sse_on_attach, - .on_data = (is_client ? fio___sse_on_data : NULL), - .on_ready = fio___websocket_on_ready, - .on_close = fio___sse_on_close, - .on_shutdown = fio___sse_on_shutdown, - .on_timeout = fio___sse_on_timeout, - .on_pubsub = FIO_HTTP_SSE_SUBSCRIBE_DIRECT, - }; - return r; - case FIO___HTTP_PROTOCOL_NONE: /* fall through*/ - r = (fio_protocol_s){.on_close = fio___http_on_close}; - return r; - default: - FIO_LOG_ERROR("internal function `fio___http_protocol_get` called with " - "illegal arguments!"); - return r; - } -} + case 127: + if (buf->len < 14UL) + return 1; + p->expect = fio_buf2u64_be(buf->buf + 2); + if (p->expect & 0xFF00000000000000ULL) + return -1; /* really?! */ + p->mask = (0ULL - mask_f) & fio_buf2u32u(buf->buf + 10); + buf->buf += 10 + mask_l; + buf->len -= 10 + mask_l; + break; -/** Returns an http controller object with the proper protocol callbacks. */ -FIO_IFUNC fio_http_controller_s -fio___http_controller_get(fio___http_protocol_selector_e s, int is_client) { - fio_http_controller_s r = {0}; - (void)is_client, (void)s; - switch (s) { - case FIO___HTTP_PROTOCOL_ACCEPT: - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed, - .send_headers = fio___http_controller_http1_send_headers, - .write_body = fio___http_controller_http1_write_body, - .on_finish = fio___http_controller_http1_on_finish, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - return r; - case FIO___HTTP_PROTOCOL_HTTP1: - if (is_client) { - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed_client, - .on_finish = fio___http_controller_http1_on_finish_client, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - } else { - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed, - .send_headers = fio___http_controller_http1_send_headers, - .write_body = fio___http_controller_http1_write_body, - .on_finish = fio___http_controller_http1_on_finish, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - } - return r; - case FIO___HTTP_PROTOCOL_HTTP2: - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - return r; - case FIO___HTTP_PROTOCOL_WS: - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed2, - .write_body = fio___http_controller_ws_write_body, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - return r; - case FIO___HTTP_PROTOCOL_SSE: - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed2, - .write_body = fio___http_controller_sse_write_body, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - return r; - case FIO___HTTP_PROTOCOL_NONE: - r = (fio_http_controller_s){ - .on_destroyed = fio__http_controller_on_destroyed2, - .close_io = fio___http_default_close, - .get_fd = fio___http_controller_get_fd, - }; - return r; default: - FIO_LOG_ERROR("internal function `fio___http_controller_get` called with " - "illegal arguments!"); - return r; + if (buf->len < (2ULL + mask_l)) + return 1; + p->expect = len_indicator; + p->mask = mask_f ? fio_buf2u32u(buf->buf + 2) : 0; + buf->buf += 2 + mask_l; + buf->len -= 2 + mask_l; + break; } + if (p->first) { + p->current = info; + if ((info & 15)) /* continuation frame == 0 ; is it missing? */ + return -1; + } else { + p->first = p->current = info; + p->start_at = 0; + if (!(info & 15)) /* continuation frame == 0 ; where's the first? */ + return -1; + } + if (p->must_mask && !p->mask) + return -1; + return (p->fn = fio___websocket_consume_frame)(p, buf, udata); } - /* ***************************************************************************** -HTTP Helpers +Main Parsing Loop ***************************************************************************** */ -/** Returns the IO object associated with the HTTP object (request only). */ -SFUNC fio_s *fio_http_io(fio_http_s *h) { - if (!h) - return NULL; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - return c->io; +FIO_SFUNC size_t fio_websocket_parse(fio_websocket_parser_s *p, + fio_buf_info_s buf, + void *udata) { + int i = 0; + char *buf_start = buf.buf; + if (!buf.len) + return 0; + if (!p->fn) + p->fn = fio___websocket_consume_header; + while (!(i = p->fn(p, &buf, udata))) + ; + if (i < 0) + return FIO_WEBSOCKET_PARSER_ERROR; + return buf.buf - buf_start; } -/** Returns the HTTP settings associated with the HTTP object, if any. */ -SFUNC fio_http_settings_s *fio_http_settings(fio_http_s *h) { - if (!h) - return NULL; - fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); - return c->settings; -} +/* ***************************************************************************** +Reading the first line +***************************************************************************** */ /* ***************************************************************************** Cleanup ***************************************************************************** */ -#endif /* FIO_EXTERN_COMPLETE */ -#undef FIO_HTTP -#endif /* FIO_HTTP */ +#undef FIO_WEBSOCKET_PARSER +#endif /* FIO_WEBSOCKET_PARSER && FIO_EXTERN_COMPLETE */ /* ************************************************************************* */ #if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ #define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_FIOBJ /* Development inclusion - ignore line */ +#define FIO_HTTP /* Development inclusion - ignore line */ #include "./include.h" /* Development inclusion - ignore line */ #endif /* Development inclusion - ignore line */ /* ***************************************************************************** @@ -44684,2258 +44734,2479 @@ Cleanup + HTTP Implementation for FIO_SERVER - FIOBJ - soft (dynamic) types - - - -FIOBJ - dynamic types - -These are dynamic types that use pointer tagging for fast type identification. - -Pointer tagging on 64 bit systems allows for 3 bits at the lower bits. On most -32 bit systems this is also true due to allocator alignment. When in doubt, use -the provided custom allocator. - -To keep the 64bit memory address alignment on 32bit systems, a 32bit metadata -integer is added when a virtual function table is missing. This doesn't effect -memory consumption on 64 bit systems and uses 4 bytes on 32 bit systems. - -Note: this code is placed at the end of the STL file, since it leverages most of -the SLT features and could be affected by their inclusion. - Copyright and License: see header file (000 copyright.h) or top of file ***************************************************************************** */ -#if defined(FIO_FIOBJ) && !defined(H___FIO_FIOBJ___H) && \ +#if defined(FIO_HTTP) && !defined(H___FIO_HTTP___H) && \ !defined(FIO___RECURSIVE_INCLUDE) -#define H___FIO_FIOBJ___H -#define FIO___RECURSIVE_INCLUDE 99 /* 99 keeps EXTERN rules */ +#define H___FIO_HTTP___H /* ***************************************************************************** -FIOBJ compilation settings (type names and JSON nesting limits). - -Type Naming Macros for FIOBJ types. By default, results in: -- fiobj_true() (constant, cannot be changed except manually) -- fiobj_false() (constant, cannot be changed except manually) -- fiobj_null() -- fiobj_num_new() ... (etc') -- fiobj_float_new() ... (etc') -- fiobj_str_new() ... (etc') -- fiobj_array_new() ... (etc') -- fiobj_hash_new() ... (etc') +HTTP Setting Defaults ***************************************************************************** */ -#ifndef FIOBJ___NAME_NULL -#define FIOBJ___NAME_NULL null -#endif -#ifndef FIOBJ___NAME_NUMBER -#define FIOBJ___NAME_NUMBER num -#endif -#ifndef FIOBJ___NAME_FLOAT -#define FIOBJ___NAME_FLOAT float +#ifndef FIO_HTTP_DEFAULT_MAX_HEADER_SIZE +#define FIO_HTTP_DEFAULT_MAX_HEADER_SIZE 32768 /* (1UL << 15) */ #endif -#ifndef FIOBJ___NAME_STRING -#define FIOBJ___NAME_STRING str +#ifndef FIO_HTTP_DEFAULT_MAX_LINE_LEN +#define FIO_HTTP_DEFAULT_MAX_LINE_LEN 8192 /* (1UL << 13) */ #endif -#ifndef FIOBJ___NAME_ARRAY -#define FIOBJ___NAME_ARRAY array +#ifndef FIO_HTTP_DEFAULT_MAX_BODY_SIZE +#define FIO_HTTP_DEFAULT_MAX_BODY_SIZE 33554432 /* (1UL << 25) */ #endif -#ifndef FIOBJ___NAME_HASH -#define FIOBJ___NAME_HASH hash +#ifndef FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE +#define FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE 262144 /* (1UL << 18) */ #endif - -#ifndef FIOBJ_MAX_NESTING -/** - * Sets the limit on nesting level transversal by recursive functions. - * - * This effects JSON output / input and the `fiobj_each2` function since they - * are recursive. - * - * HOWEVER: this value will NOT effect the recursive `fiobj_free` which could - * (potentially) expload the stack if given melformed input such as cyclic data - * structures. - * - * Values should be less than 32K. - */ -#define FIOBJ_MAX_NESTING 512 +#ifndef FIO_HTTP_DEFAULT_TIMEOUT +#define FIO_HTTP_DEFAULT_TIMEOUT 50 #endif - -/* make sure roundtrips work */ -#ifndef JSON_MAX_DEPTH -#define JSON_MAX_DEPTH FIOBJ_MAX_NESTING +#ifndef FIO_HTTP_DEFAULT_TIMEOUT_LONG +#define FIO_HTTP_DEFAULT_TIMEOUT_LONG 50 #endif -#ifndef FIOBJ_JSON_APPEND -#define FIOBJ_JSON_APPEND 1 +#ifndef FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER +/** Adds a "content-length" header to the HTTP handle (usually redundant). */ +#define FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER 0 #endif -/* ***************************************************************************** -General Requirements / Macros -***************************************************************************** */ -#ifdef __cplusplus /* C++ doesn't allow declarations for static variables */ -#define FIOBJ_EXTERN_OBJ extern "C" FIO_WEAK -#define FIOBJ_EXTERN_OBJ_IMP extern "C" FIO_WEAK -#elif defined(FIO_EXTERN) -#define FIOBJ_EXTERN_OBJ extern -#define FIOBJ_EXTERN_OBJ_IMP FIO_WEAK -#else -#define FIOBJ_EXTERN_OBJ static __attribute__((unused)) -#define FIOBJ_EXTERN_OBJ_IMP static __attribute__((unused)) +#ifndef FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT +/** UTF-8 validity tests will be performed only for data shorter than this. */ +#define FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT ((1UL << 16) - 10UL) #endif -/* ***************************************************************************** -Debugging / Leak Detection -***************************************************************************** */ -#if defined(TEST) || defined(DEBUG) || defined(FIO_LEAK_COUNTER) -size_t FIO_WEAK FIOBJ_MARK_MEMORY_ALLOC_COUNTER; -size_t FIO_WEAK FIOBJ_MARK_MEMORY_FREE_COUNTER; -#define FIOBJ_MARK_MEMORY_ALLOC() \ - fio_atomic_add(&FIOBJ_MARK_MEMORY_ALLOC_COUNTER, 1) -#define FIOBJ_MARK_MEMORY_FREE() \ - fio_atomic_add(&FIOBJ_MARK_MEMORY_FREE_COUNTER, 1) -#define FIOBJ_MARK_MEMORY_PRINT() \ - FIO___LOG_PRINT_LEVEL( \ - ((FIOBJ_MARK_MEMORY_ALLOC_COUNTER == FIOBJ_MARK_MEMORY_FREE_COUNTER) \ - ? 4 /* FIO_LOG_LEVEL_INFO */ \ - : 3 /* FIO_LOG_LEVEL_WARNING */), \ - ((FIOBJ_MARK_MEMORY_ALLOC_COUNTER == FIOBJ_MARK_MEMORY_FREE_COUNTER) \ - ? "INFO: total remaining FIOBJ allocations: %zu (%zu - %zu)" \ - : "WARNING: LEAKED! FIOBJ allocations: %zu (%zu - %zu)"), \ - FIOBJ_MARK_MEMORY_ALLOC_COUNTER - FIOBJ_MARK_MEMORY_FREE_COUNTER, \ - FIOBJ_MARK_MEMORY_ALLOC_COUNTER, \ - FIOBJ_MARK_MEMORY_FREE_COUNTER) -#define FIOBJ_MARK_MEMORY_ENABLED 1 -#else - -#define FIOBJ_MARK_MEMORY_ALLOC_COUNTER 0 /* when testing unmarked FIOBJ */ -#define FIOBJ_MARK_MEMORY_FREE_COUNTER 0 /* when testing unmarked FIOBJ */ -#define FIOBJ_MARK_MEMORY_ALLOC() -#define FIOBJ_MARK_MEMORY_FREE() -#define FIOBJ_MARK_MEMORY_PRINT() -#define FIOBJ_MARK_MEMORY_ENABLED 0 +#ifndef FIO_WEBSOCKET_STATS +/* If true, logs longest WebSocket round-trips (using FIO_LOG_INFO). */ +#define FIO_WEBSOCKET_STATS 0 #endif /* ***************************************************************************** -The FIOBJ Type +HTTP Listen ***************************************************************************** */ +typedef struct fio_http_settings_s { + /** Called before body uploads, when a client sends an `Expect` header. */ + void (*pre_http_body)(fio_http_s *h); + /** Callback for HTTP requests (server) or responses (client). */ + void (*on_http)(fio_http_s *h); + /** Called when a request / response cycle is finished with no Upgrade. */ + void (*on_finish)(fio_http_s *h); + /** (optional) the callback to be performed when the HTTP service closes. */ + void (*on_stop)(struct fio_http_settings_s *settings); -/** Use the FIOBJ type for dynamic types. */ -typedef struct FIOBJ_s { - struct FIOBJ_s *compiler_validation_type; -} * FIOBJ; + /** Authenticate EventSource (SSE) requests, return non-zero to deny.*/ + int (*on_authenticate_sse)(fio_http_s *h); + /** Authenticate WebSockets Upgrade requests, return non-zero to deny.*/ + int (*on_authenticate_websocket)(fio_http_s *h); -/** FIOBJ type enum for common / primitive types. */ -typedef enum { - FIOBJ_T_NUMBER = 0x01, /* 0b001 3 bits taken for small numbers */ - FIOBJ_T_PRIMITIVE = 2, /* 0b010 a lonely second bit signifies a primitive */ - FIOBJ_T_STRING = 3, /* 0b011 */ - FIOBJ_T_ARRAY = 4, /* 0b100 */ - FIOBJ_T_HASH = 5, /* 0b101 */ - FIOBJ_T_FLOAT = 6, /* 0b110 */ - FIOBJ_T_OTHER = 7, /* 0b111 dynamic type - test content */ -} fiobj_class_en; + /** Called once a WebSocket / SSE connection upgrade is complete. */ + void (*on_open)(fio_http_s *h); -#define FIOBJ_T_NULL 2 /* 0b010 a lonely second bit signifies a primitive */ -#define FIOBJ_T_TRUE 18 /* 0b010 010 - primitive value */ -#define FIOBJ_T_FALSE 34 /* 0b100 010 - primitive value */ + /** Called when a WebSocket message is received. */ + void (*on_message)(fio_http_s *h, fio_buf_info_s msg, uint8_t is_text); + /** Called when an EventSource event is received. */ + void (*on_eventsource)(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); + /** Called when an EventSource reconnect event requests an ID. */ + void (*on_eventsource_reconnect)(fio_http_s *h, fio_buf_info_s id); -/** Use the macros to avoid future API changes. */ -#define FIOBJ_TYPE(o) fiobj_type(o) -/** Use the macros to avoid future API changes. */ -#define FIOBJ_TYPE_IS(o, type) (fiobj_type(o) == type) -/** Identifies an invalid type identifier (returned from FIOBJ_TYPE(o) */ -#define FIOBJ_T_INVALID 0 -/** Identifies an invalid object */ -#define FIOBJ_INVALID 0 -/** Tests if the object is (probably) a valid FIOBJ */ -#define FIOBJ_IS_INVALID(o) (((uintptr_t)(o)&7UL) == 0) -#define FIOBJ_IS_NULL(o) (FIOBJ_IS_INVALID(o) || ((o) == FIOBJ_T_NULL)) -#define FIOBJ_TYPE_CLASS(o) ((fiobj_class_en)(((uintptr_t)(o)) & 7UL)) -#define FIOBJ_PTR_TAG(o, klass) ((uintptr_t)(((uintptr_t)(o)) | (klass))) -#define FIOBJ_PTR_UNTAG(o) ((uintptr_t)(((uintptr_t)(o)) & (~7ULL))) -/** Returns an objects type. This isn't limited to known types. */ -FIO_IFUNC size_t fiobj_type(FIOBJ o); + /** Called for WebSocket / SSE connections when outgoing buffer is empty. */ + void (*on_ready)(fio_http_s *h); + /** Called for open WebSocket / SSE connections during shutting down. */ + void (*on_shutdown)(fio_http_s *h); + /** Called after a WebSocket / SSE connection is closed (for cleanup). */ + void (*on_close)(fio_http_s *h); -/* ***************************************************************************** -FIOBJ Memory Management -***************************************************************************** */ + /** Default opaque user data for HTTP handles (fio_http_s). */ + void *udata; + /** Optional SSL/TLS support. */ + fio_io_functions_s *tls_io_func; + /** Optional SSL/TLS support. */ + fio_io_tls_s *tls; + /** Optional HTTP task queue (for multi-threading HTTP responses) */ + fio_io_async_s *queue; + /** + * A public folder for file transfers - allows to circumvent any application + * layer logic and simply serve static files. + * + * Supports automatic `gz` pre-compressed alternatives. + */ + fio_str_info_s public_folder; + /** + * The max-age value (in seconds) for possibly caching static files from the + * public folder specified. + * + * Defaults to 0 (not sent). + */ + size_t max_age; + /** + * The maximum total of bytes for the overall size of the request string and + * headers, combined. + * + * Defaults to FIO_HTTP_DEFAULT_MAX_HEADER_SIZE bytes. + */ + uint32_t max_header_size; + /** + * The maximum number of bytes allowed per header / request line. + * + * Defaults to FIO_HTTP_DEFAULT_MAX_LINE_LEN bytes. + */ + uint32_t max_line_len; + /** + * The maximum size of an HTTP request's body (posting / downloading). + * + * Defaults to FIO_HTTP_DEFAULT_MAX_BODY_SIZE bytes. + */ + size_t max_body_size; + /** + * The maximum websocket message size/buffer (in bytes) for Websocket + * connections. Defaults to FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE bytes. + */ + size_t ws_max_msg_size; + /** reserved for future use. */ + intptr_t reserved1; + /** reserved for future use. */ + intptr_t reserved2; + /** + * An HTTP/1.x connection timeout. + * + * Defaults to FIO_HTTP_DEFAULT_TIMEOUT seconds. + * + * Note: the connection might be closed (by other side) before timeout occurs. + */ + uint8_t timeout; + /** + * Timeout for the WebSocket connections in seconds. Defaults to + * FIO_HTTP_DEFAULT_TIMEOUT_LONG seconds. + * + * A ping will be sent whenever the timeout is reached. + * + * Connections are only closed when a ping cannot be sent (the network layer + * fails). Pongs are ignored. + */ + uint8_t ws_timeout; + /** + * Timeout for EventSource (SSE) connections in seconds. Defaults to + * FIO_HTTP_DEFAULT_TIMEOUT_LONG seconds. + * + * A ping will be sent whenever the timeout is reached. + * + * Connections are only closed when a ping cannot be sent (the network layer + * fails). + */ + uint8_t sse_timeout; + /** Timeout for client connections (only relevant in client mode). */ + uint8_t connect_timeout; + /** Logging flag - set to TRUE to log HTTP requests. */ + uint8_t log; +} fio_http_settings_s; -/** Increases an object's reference count (or copies) and returns it. */ -FIO_IFUNC FIOBJ fiobj_dup(FIOBJ o); +/** Listens to HTTP / WebSockets / SSE connections on `url`. */ +SFUNC void *fio_http_listen(const char *url, fio_http_settings_s settings); -/** Decreases an object's reference count or frees it. */ -FIO_IFUNC void fiobj_free(FIOBJ o); +/** Listens to HTTP / WebSockets / SSE connections on `url`. */ +#define fio_http_listen(url, ...) \ + fio_http_listen(url, (fio_http_settings_s){__VA_ARGS__}) -/* ***************************************************************************** -FIOBJ Data / Info -***************************************************************************** */ +/** Allows all clients to connect (bypasses authentication). */ +SFUNC int FIO_HTTP_AUTHENTICATE_ALLOW(fio_http_s *h); -/** Compares two objects. */ -FIO_IFUNC unsigned char FIO_NAME_BL(fiobj, eq)(FIOBJ a, FIOBJ b); +/** Returns the IO object associated with the HTTP object (request only). */ +SFUNC fio_io_s *fio_http_io(fio_http_s *); -/** Returns a temporary String representation for any FIOBJ object. */ -FIO_IFUNC fio_str_info_s FIO_NAME2(fiobj, cstr)(FIOBJ o); +/** Macro helper for HTTP handle pub/sub subscriptions. */ +#define fio_http_subscribe(h, ...) \ + fio_subscribe(.io = fio_http_io(h), __VA_ARGS__) -/** Returns an integer representation for any FIOBJ object. */ -FIO_IFUNC intptr_t FIO_NAME2(fiobj, i)(FIOBJ o); +/** Connects to HTTP / WebSockets / SSE connections on `url`. */ +SFUNC fio_io_s *fio_http_connect(const char *url, + fio_http_s *h, + fio_http_settings_s settings); -/** Returns a float (double) representation for any FIOBJ object. */ -FIO_IFUNC double FIO_NAME2(fiobj, f)(FIOBJ o); +/** Connects to HTTP / WebSockets / SSE connections on `url`. */ +#define fio_http_connect(url, h, ...) \ + fio_http_connect(url, h, (fio_http_settings_s){__VA_ARGS__}) -/** Calculates an object's hash value for a specific hash map object. */ -FIO_IFUNC uint64_t FIO_NAME2(fiobj, hash)(FIOBJ object_key); +/** Returns the HTTP settings associated with the HTTP object, if any. */ +SFUNC fio_http_settings_s *fio_http_settings(fio_http_s *); /* ***************************************************************************** -FIOBJ Containers (iteration) +WebSocket Helpers - HTTP Upgraded Connections ***************************************************************************** */ -/** Iteration information structure passed to the callback. */ -typedef struct fiobj_each_s { - /** The being iterated. Once set, cannot be safely changed. */ - FIOBJ const parent; - /** The index to start at / the current object's index */ - uint64_t index; - /** The callback / task called for each index, may be updated mid-cycle. */ - int (*task)(struct fiobj_each_s *info); - /** The argument passed along to the task. */ - void *udata; - /** The value of the current object in the Array or Hash Map */ - FIOBJ value; - /* The key, if a Hash Map */ - FIOBJ key; -} fiobj_each_s; +/** Writes a WebSocket message. Fails if connection wasn't upgraded yet. */ +SFUNC int fio_http_websocket_write(fio_http_s *h, + const void *buf, + size_t len, + uint8_t is_text); /** - * Performs a task for each element held by the FIOBJ object. - * - * If `task` returns -1, the `each` loop will break (stop). + * Sets a specific on_message callback for this connection. * - * Returns the "stop" position - the number of elements processed + `start_at`. + * Returns -1 on error (i.e., upgrade still in negotiation). */ -FIO_SFUNC uint32_t fiobj_each1(FIOBJ o, - int (*task)(fiobj_each_s *info), - void *udata, - int32_t start_at); +SFUNC int fio_http_on_message_set(fio_http_s *h, + void (*on_message)(fio_http_s *, + fio_buf_info_s, + uint8_t)); -/** - * Performs a task for the object itself and each element held by the FIOBJ - * object or any of it's elements (a deep task). - * - * The order of performance is by order of appearance, as if all nesting levels - * were flattened. - * - * If `task` returns -1, the `each` loop will break (stop). - * - * Returns the number of elements processed. - */ -SFUNC uint32_t fiobj_each2(FIOBJ o, - int (*task)(fiobj_each_s *info), - void *udata); +/** Optional WebSocket subscription callback. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT(fio_msg_s *msg); +/** Optional WebSocket subscription callback - all messages are UTF-8 valid. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT(fio_msg_s *msg); +/** Optional WebSocket subscription callback - messages may be non-UTF-8. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY(fio_msg_s *msg); /* ***************************************************************************** -FIOBJ Primitives (NULL, True, False) +EventSource (SSE) Helpers - HTTP Upgraded Connections ***************************************************************************** */ -/** Returns the `true` primitive. */ -FIO_IFUNC FIOBJ fiobj_true(void) { return (FIOBJ)(FIOBJ_T_TRUE); } +/** Named arguments for fio_http_sse_write. */ +typedef struct { + /** The message's `id` data (if any). */ + fio_buf_info_s id; + /** The message's `event` data (if any). */ + fio_buf_info_s event; + /** The message's `data` data (if any). */ + fio_buf_info_s data; +} fio_http_sse_write_args_s; -/** Returns the `false` primitive. */ -FIO_IFUNC FIOBJ fiobj_false(void) { return (FIOBJ)(FIOBJ_T_FALSE); } +/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ +SFUNC int fio_http_sse_write(fio_http_s *h, fio_http_sse_write_args_s args); -/** Returns the `nil` / `null` primitive. */ -FIO_IFUNC FIOBJ FIO_NAME(fiobj, FIOBJ___NAME_NULL)(void) { - return (FIOBJ)(FIOBJ_T_NULL); -} +/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ +#define fio_http_sse_write(h, ...) \ + fio_http_sse_write((h), ((fio_http_sse_write_args_s){__VA_ARGS__})) + +/** Optional EventSource subscription callback - messages MUST be UTF-8. */ +SFUNC void FIO_HTTP_SSE_SUBSCRIBE_DIRECT(fio_msg_s *msg); /* ***************************************************************************** -FIOBJ Type - Extensibility (FIOBJ_T_OTHER) +Module Implementation - possibly externed functions. ***************************************************************************** */ +#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/** FIOBJ types can be extended using virtual function tables. */ -typedef struct { - /** - * MUST return a unique number to identify object type. - * - * Numbers (type IDs) under 100 are reserved. Numbers under 40 are illegal. - */ - size_t type_id; - /** Test for equality between two objects with the same `type_id` */ - unsigned char (*is_eq)(FIOBJ restrict a, FIOBJ restrict b); - /** Converts an object to a String */ - fio_str_info_s (*to_s)(FIOBJ o); - /** Converts an object to an integer */ - intptr_t (*to_i)(FIOBJ o); - /** Converts an object to a double */ - double (*to_f)(FIOBJ o); - /** Returns the number of exposed elements held by the object, if any. */ - uint32_t (*count)(FIOBJ o); - /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ - uint32_t (*each1)(FIOBJ o, - int (*task)(fiobj_each_s *e), - void *udata, - int32_t start_at); - /** - * Decreases the reference count and/or frees the object, calling `free2` for - * any nested objects. - */ - void (*free2)(FIOBJ o); -} FIOBJ_class_vtable_s; +/* +REMEMBER: +======== -FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___OBJECT_CLASS_VTBL; +All memory allocations should use: +* FIO_MEM_REALLOC_(ptr, old_size, new_size, copy_len) +* FIO_MEM_FREE_(ptr, size) -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO_REF_NAME fiobj_object -#define FIO_REF_TYPE void * -#define FIO_REF_METADATA const FIOBJ_class_vtable_s * -#define FIO_REF_METADATA_INIT(m) \ - do { \ - m = &FIOBJ___OBJECT_CLASS_VTBL; \ - FIOBJ_MARK_MEMORY_ALLOC(); \ - } while (0) -#define FIO_REF_METADATA_DESTROY(m) \ - do { \ - FIOBJ_MARK_MEMORY_FREE(); \ - } while (0) -#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) -#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) -#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) -#define FIO_PTR_TAG_TYPE FIOBJ -#include FIO_INCLUDE_FILE +*/ /* ***************************************************************************** -FIOBJ Integers +HTTP Settings Validation ***************************************************************************** */ -/** Creates a new Number object. */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(intptr_t i); - -/** Reads the number from a FIOBJ Number. */ -FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(FIOBJ i); - -/** Reads the number from a FIOBJ Number, fitting it in a double. */ -FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(FIOBJ i); +static void fio___http_default_on_http_request(fio_http_s *h) { + fio_http_send_error_response(h, 404); +} +static void fio___http_default_noop(fio_http_s *h) { ((void)h); } +static int fio___http_default_authenticate(fio_http_s *h) { + ((void)h); + return -1; +} -/** Returns a String representation of the number (in base 10). */ -SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), - cstr)(FIOBJ i); +// on_queue +static void fio___http_default_on_stop(struct fio_http_settings_s *settings) { + ((void)settings); +} -/** Frees a FIOBJ number. */ -FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), free)(FIOBJ i); +static void fio___http_default_close(fio_http_s *h) { + fio_io_close(fio_http_io(h)); +} -FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___NUMBER_CLASS_VTBL; +/** Called when a WebSocket message is received. */ +static void fio___http_default_on_message(fio_http_s *h, + fio_buf_info_s msg, + uint8_t is_text) { + (void)h, (void)msg, (void)is_text; +} +/** Called when an EventSource event is received. */ +static void fio___http_default_on_eventsource(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data) { + (void)h, (void)id, (void)event, (void)data; +} +/** Called when an EventSource event is received. */ +static void fio___http_default_on_eventsource_redirect(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); -/* ***************************************************************************** -FIOBJ Floats -***************************************************************************** */ +/** Called when an EventSource reconnect event requests an ID. */ +static void fio___http_default_on_eventsource_reconnect(fio_http_s *h, + fio_buf_info_s id) { + (void)h, (void)id; +} -/** Creates a new Float (double) object. */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(double i); +static void http_settings_validate(fio_http_settings_s *s, int is_client) { + if (!s->pre_http_body) + s->pre_http_body = fio___http_default_noop; -/** Reads the number from a FIOBJ Float rounding it to an integer. */ -FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(FIOBJ i); + if (!s->on_http) + s->on_http = is_client ? fio___http_default_noop + : fio___http_default_on_http_request; + if (!s->on_finish) + s->on_finish = fio___http_default_noop; + if (!s->on_stop) + s->on_stop = fio___http_default_on_stop; + if (!s->on_authenticate_sse) + s->on_authenticate_sse = is_client ? FIO_HTTP_AUTHENTICATE_ALLOW + : fio___http_default_authenticate; + if (!s->on_authenticate_websocket) + s->on_authenticate_websocket = is_client ? FIO_HTTP_AUTHENTICATE_ALLOW + : fio___http_default_authenticate; + if (!s->on_open) + s->on_open = fio___http_default_noop; + if (!s->on_open) + s->on_open = fio___http_default_noop; + if (!s->on_message) + s->on_message = fio___http_default_on_message; + if (!s->on_eventsource) + s->on_eventsource = (s->on_message == fio___http_default_on_message + ? fio___http_default_on_eventsource + : fio___http_default_on_eventsource_redirect); + if (!s->on_eventsource_reconnect) + s->on_eventsource_reconnect = fio___http_default_on_eventsource_reconnect; + if (!s->on_ready) + s->on_ready = fio___http_default_noop; + if (!s->on_shutdown) + s->on_shutdown = fio___http_default_noop; + if (!s->on_close) + s->on_close = fio___http_default_noop; + if (!s->max_header_size) + s->max_header_size = FIO_HTTP_DEFAULT_MAX_HEADER_SIZE; + if (!s->max_line_len) + s->max_line_len = FIO_HTTP_DEFAULT_MAX_LINE_LEN; + if (!s->max_body_size) + s->max_body_size = FIO_HTTP_DEFAULT_MAX_BODY_SIZE; + if (!s->ws_max_msg_size) + s->ws_max_msg_size = FIO_HTTP_DEFAULT_WS_MAX_MSG_SIZE; + if (!s->timeout) + s->timeout = FIO_HTTP_DEFAULT_TIMEOUT; + if (!s->ws_timeout) + s->ws_timeout = FIO_HTTP_DEFAULT_TIMEOUT_LONG; + if (!s->sse_timeout) + s->sse_timeout = s->ws_timeout; -/** Reads the value from a FIOBJ Float, as a double. */ -FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(FIOBJ i); + if (s->max_header_size < s->max_line_len) + s->max_header_size = s->max_line_len; -/** Returns a String representation of the float. */ -SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), - cstr)(FIOBJ i); + if (s->public_folder.buf) { + if (s->public_folder.len > 1 && + s->public_folder.buf[s->public_folder.len - 1] == '/' && + !(s->public_folder.len == 2 && s->public_folder.buf[0] == '~')) + --s->public_folder.len; + if (!fio_filename_is_folder(s->public_folder.buf)) { + FIO_LOG_ERROR( + "HTTP public folder is not a folder, setting ignored.\n\t%s", + s->public_folder.buf); + s->public_folder = ((fio_str_info_s){0}); + } + } +} -/** Frees a FIOBJ Float. */ -FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), free)(FIOBJ i); +/* ***************************************************************************** +HTTP Protocols used by the HTTP module +***************************************************************************** */ -FIOBJ_EXTERN_OBJ const FIOBJ_class_vtable_s FIOBJ___FLOAT_CLASS_VTBL; +typedef enum fio___http_protocol_selector_e { + FIO___HTTP_PROTOCOL_ACCEPT = 0, + FIO___HTTP_PROTOCOL_HTTP1, + FIO___HTTP_PROTOCOL_HTTP2, + FIO___HTTP_PROTOCOL_WS, + FIO___HTTP_PROTOCOL_SSE, + FIO___HTTP_PROTOCOL_NONE +} fio___http_protocol_selector_e; /* ***************************************************************************** -FIOBJ Strings +HTTP Protocol Container (vtable + settings storage) ***************************************************************************** */ +#define FIO___RECURSIVE_INCLUDE 1 -#define FIO_STR_NAME FIO_NAME(fiobj, FIOBJ___NAME_STRING) -#define FIO_STR_OPTIMIZE_EMBEDDED 1 -#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_STRING) -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO_REF_DESTROY(s) \ - do { \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), destroy) \ - ((FIOBJ)FIOBJ_PTR_TAG(&s, FIOBJ_T_STRING)); \ - FIOBJ_MARK_MEMORY_FREE(); \ - } while (0) -#define FIO_REF_INIT(s_) \ +typedef struct { + fio_http_settings_s settings; + void (*on_http_callback)(void *, void *); + fio_queue_s *queue; + struct { + fio_io_protocol_s protocol; + fio_http_controller_s controller; + } state[FIO___HTTP_PROTOCOL_NONE + 1]; + char public_folder_buf[]; +} fio___http_protocol_s; +#include FIO_INCLUDE_FILE + +#define FIO_REF_NAME fio___http_protocol +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_CONSTRUCTOR_ONLY 1 +#define FIO_REF_DESTROY(o) \ do { \ - s_ = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s))FIO_STR_INIT; \ - FIOBJ_MARK_MEMORY_ALLOC(); \ + if (o.settings.tls) \ + fio_io_tls_free(o.settings.tls); \ + if (o.settings.on_stop) \ + o.settings.on_stop(&o.settings); \ } while (0) - -#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ -#define FIO_REF_METADATA uint32_t -#endif -#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_STRING) -#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) -#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_STRING) -#define FIO_PTR_TAG_TYPE FIOBJ #include FIO_INCLUDE_FILE -/* Creates a new FIOBJ string object, copying the data to the new string. */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), - new_cstr)(const char *ptr, size_t len) { - FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); - FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(s, ptr, len); - return s; -} - -/* Creates a new FIOBJ string object with (at least) the requested capacity. */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), - new_buf)(size_t capa) { - FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); - FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), reserve)(s, capa); - return s; -} - -/* Creates a new FIOBJ string object, copying the origin (`fiobj2cstr`). */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), - new_copy)(FIOBJ original) { - FIOBJ s = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); - FIO_ASSERT_ALLOC(FIOBJ_PTR_UNTAG(s)); - fio_str_info_s i = FIO_NAME2(fiobj, cstr)(original); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(s, i.buf, i.len); - return s; -} - -/** Returns information about the string. Same as fiobj_str_info(). */ -FIO_IFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_STRING), - cstr)(FIOBJ s) { - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(s); -} - -/** - * Creates a temporary FIOBJ String object on the stack. - * - * String data might be allocated dynamically. - */ -#define FIOBJ_STR_TEMP_VAR(str_name) \ - struct { \ - uint64_t i1; \ - uint64_t i2; \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ - } FIO_NAME(str_name, __auto_mem_tmp) = {0x7f7f7f7f7f7f7f7fULL, \ - 0x7f7f7f7f7f7f7f7fULL, \ - FIO_STR_INIT}; \ - FIOBJ str_name = \ - (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ - FIOBJ_T_STRING); - -/** - * Creates a temporary FIOBJ String object on the stack, initialized with a - * static string. - * - * String data might be allocated dynamically. - */ -#define FIOBJ_STR_TEMP_VAR_STATIC(str_name, buf_, len_) \ - struct { \ - uint64_t i1; \ - uint64_t i2; \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ - } FIO_NAME(str_name, \ - __auto_mem_tmp) = {0x7f7f7f7f7f7f7f7fULL, \ - 0x7f7f7f7f7f7f7f7fULL, \ - FIO_STR_INIT_STATIC2((buf_), (len_))}; \ - FIOBJ str_name = \ - (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ - FIOBJ_T_STRING); - -/** - * Creates a temporary FIOBJ String object on the stack, initialized with a - * static string. - * - * String data might be allocated dynamically. - */ -#define FIOBJ_STR_TEMP_VAR_EXISTING(str_name, buf_, len_, capa_) \ - struct { \ - uint64_t i1; \ - uint64_t i2; \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), s) s; \ - } FIO_NAME(str_name, __auto_mem_tmp) = { \ - 0x7f7f7f7f7f7f7f7fULL, \ - 0x7f7f7f7f7f7f7f7fULL, \ - FIO_STR_INIT_EXISTING((buf_), (len_), (capa_))}; \ - FIOBJ str_name = \ - (FIOBJ)(((uintptr_t) & (FIO_NAME(str_name, __auto_mem_tmp).s)) | \ - FIOBJ_T_STRING); - -/** Resets a temporary FIOBJ String, freeing and any resources allocated. */ -#define FIOBJ_STR_TEMP_DESTROY(str_name) \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), destroy)(str_name); - +FIO_IFUNC fio___http_protocol_s *fio___http_protocol_init( + fio___http_protocol_s *p, + const char *url, + fio_http_settings_s s, + bool is_client); /* ***************************************************************************** -FIOBJ Arrays +HTTP Connection Container ***************************************************************************** */ -#define FIO_ARRAY_NAME FIO_NAME(fiobj, FIOBJ___NAME_ARRAY) -#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_ARRAY) +struct fio___http_connection_http_s { + void (*on_http_callback)(void *, void *); + void (*on_http)(fio_http_s *h); + void (*on_finish)(fio_http_s *h); + fio_http1_parser_s parser; + fio_str_info_s buf; + uint32_t max_header; +}; +struct fio___http_connection_ws_s { + void (*on_message)(fio_http_s *h, fio_buf_info_s msg, uint8_t is_text); + void (*on_ready)(fio_http_s *h); + fio_websocket_parser_s parser; + char *msg; + uint16_t code; +}; +struct fio___http_connection_sse_s { + void (*on_message)(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data); + void (*on_ready)(fio_http_s *h); + fio_buf_info_s id; + fio_buf_info_s event; + char *data; +}; + +/** Connection objects for managing HTTP / WebSocket connection state. */ +typedef struct { + fio_io_s *io; + fio_http_s *h; + fio_http_settings_s *settings; + fio_queue_s *queue; + void *udata; + union { + struct fio___http_connection_http_s http; + struct fio___http_connection_ws_s ws; + struct fio___http_connection_sse_s sse; + } state; + uint32_t len; + uint32_t capa; + uint8_t log; + uint8_t suspend; + uint8_t is_client; + char buf[]; +} fio___http_connection_s; + +#define FIO_REF_NAME fio___http_connection #define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO_REF_DESTROY(a) \ - do { \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), destroy) \ - ((FIOBJ)FIOBJ_PTR_TAG(&a, FIOBJ_T_ARRAY)); \ - FIOBJ_MARK_MEMORY_FREE(); \ - } while (0) -#define FIO_REF_INIT(a) \ - do { \ - a = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), s))FIO_ARRAY_INIT; \ - FIOBJ_MARK_MEMORY_ALLOC(); \ - } while (0) -#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ -#define FIO_REF_METADATA uint32_t -#endif -#define FIO_ARRAY_TYPE FIOBJ -#define FIO_ARRAY_TYPE_CMP(a, b) FIO_NAME_BL(fiobj, eq)((a), (b)) -#define FIO_ARRAY_TYPE_DESTROY(o) fiobj_free(o) -#define FIO_ARRAY_TYPE_CONCAT_COPY(dest, obj) \ +#define FIO_REF_FLEX_TYPE char +#define FIO_REF_DESTROY(o) \ do { \ - dest = fiobj_dup(obj); \ + fio___http_protocol_free( \ + FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, o.settings)); \ } while (0) -#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_ARRAY) -#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) -#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_ARRAY) -#define FIO_PTR_TAG_TYPE FIOBJ #include FIO_INCLUDE_FILE +#undef FIO___RECURSIVE_INCLUDE + /* ***************************************************************************** -FIOBJ Hash Maps +Revisit defaults ***************************************************************************** */ -#define FIO_REF_NAME FIO_NAME(fiobj, FIOBJ___NAME_HASH) -#define FIO_REF_CONSTRUCTOR_ONLY 1 -#define FIO_REF_DESTROY(a) \ - do { \ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), destroy) \ - ((FIOBJ)FIOBJ_PTR_TAG(&(a), FIOBJ_T_HASH)); \ - FIOBJ_MARK_MEMORY_FREE(); \ - } while (0) -#define FIO_REF_INIT(a) \ - do { \ - a = (FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), s))FIO_MAP_INIT; \ - FIOBJ_MARK_MEMORY_ALLOC(); \ - } while (0) -#if SIZE_T_MAX == 0xFFFFFFFF /* for 32bit system pointer alignment */ -#define FIO_REF_METADATA uint32_t -#endif -#define FIO_MAP_NAME FIO_NAME(fiobj, FIOBJ___NAME_HASH) -#define FIO_MAP_ORDERED 1 -#define FIO_MAP_KEY FIOBJ -#define FIO_MAP_KEY_CMP(a, b) FIO_NAME_BL(fiobj, eq)((a), (b)) -#define FIO_MAP_KEY_COPY(dest, o) (dest = fiobj_dup(o)) -#define FIO_MAP_KEY_DESTROY(o) fiobj_free(o) -#define FIO_MAP_VALUE FIOBJ -#define FIO_MAP_HASH_FN(o) FIO_NAME2(fiobj, hash)(o) -#define FIO_MAP_VALUE_DESTROY(o) fiobj_free(o) -#define FIO_MAP_VALUE_DISCARD(o) fiobj_free(o) -#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_HASH) -#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) -#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_HASH) -#define FIO_PTR_TAG_TYPE FIOBJ -/* TODO! auto-hash object value */ -#include FIO_INCLUDE_FILE -/** - * Sets a value in a hash map, allocating the key String and automatically - * calculating the hash value. - */ -FIO_IFUNC -FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - set2)(FIOBJ hash, const char *key, size_t len, FIOBJ value); - -/** - * Finds a value in the hash map, using a temporary String and automatically - * calculating the hash value. - */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - get2)(FIOBJ hash, const char *buf, size_t len); - -/** - * Removes a value in a hash map, using a temporary String and automatically - * calculating the hash value. - */ -FIO_IFUNC int FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - remove2)(FIOBJ hash, - const char *buf, - size_t len, - FIOBJ *old); +/** Called when an EventSource event is received. */ +static void fio___http_default_on_eventsource_redirect(fio_http_s *h, + fio_buf_info_s id, + fio_buf_info_s event, + fio_buf_info_s data) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + c->settings->on_message(h, data, 1); + (void)h, (void)id, (void)event, (void)data; +} /* ***************************************************************************** -FIOBJ JSON support +HTTP Request handling / handling ***************************************************************************** */ -/** - * Returns a JSON valid FIOBJ String, representing the object. - * - * If `dest` is an existing String, the formatted JSON data will be appended to - * the existing string. - */ -FIO_IFUNC FIOBJ FIO_NAME2(fiobj, json)(FIOBJ dest, FIOBJ o, uint8_t beautify); +FIO_SFUNC void fio___http_perform_user_callback(void *cb_, void *h_) { + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.ptr = cb_}; + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (FIO_LIKELY(fio_io_is_open(c->io))) + cb.fn(h); + fio_http_free(h); +} -/** - * Updates a Hash using JSON data. - * - * Parsing errors and non-dictionary object JSON data are silently ignored, - * attempting to update the Hash as much as possible before any errors - * encountered. - * - * Conflicting Hash data is overwritten (preferring the new over the old). - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - update_json)(FIOBJ hash, fio_str_info_s str); +FIO_SFUNC void fio___http_perform_user_upgrade_callback_websocket(void *cb_, + void *h_) { + union { + int (*fn)(fio_http_s *); + void *ptr; + } cb = {.ptr = cb_}; + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + struct fio___http_connection_http_s old = c->state.http; + if (cb.fn(h)) + goto refuse_upgrade; + if (c->h) /* request after WebSocket Upgrade? an attack vector? */ + goto refuse_upgrade; +#if HAVE_ZLIB && 0 /* TODO: logs and fix extension handling logic */ + FIO_HTTP_HEADER_EACH_VALUE(/* TODO: setup WebSocket extension */ + h, + 1, + FIO_STR_INFO2((char *)"sec-websocket-extensions", + 24), + val) { + FIO_LOG_DDEBUG2("WebSocket extension requested: %.*s", + (int)val.len, + val.buf); + if (!FIO_STR_INFO_IS_EQ(val, + FIO_STR_INFO2((char *)"permessage-deflate", 18))) + continue; + size_t client_bits = 0, server_bits = 0; + FIO_HTTP_HEADER_VALUE_EACH_PROPERTY(val, p) { + FIO_LOG_DDEBUG2("\t %.*s: %.*s", + (int)p.name.len, + p.name.buf, + (int)p.value.len, + p.value.buf); + if (FIO_STR_INFO_IS_EQ(p.name, + FIO_STR_INFO2((char *)"client_max_window_bits", + 22))) { /* used by chrome */ + char *iptr = p.value.buf; + client_bits = iptr ? fio_atol10u(&iptr) : 0; + if (client_bits < 8 || client_bits > 15) + client_bits = (size_t)-1; + } + if (FIO_STR_INFO_IS_EQ( + p.name, + FIO_STR_INFO2((char *)"server_max_window_bits", 22))) { + char *iptr = p.value.buf; + server_bits = iptr ? fio_atol10u(&iptr) : 0; + if (server_bits < 8 || server_bits > 15) + server_bits = (size_t)-1; + } + } + if (client_bits) + ; /* TODO */ + if (server_bits) + ; /* TODO */ + break; + } /* HAVE_ZLIB */ +#endif + fio_http_upgrade_websocket(h); + return; -/** Helper function, calls `fiobj_hash_update_json` with string information */ -FIO_IFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - update_json2)(FIOBJ hash, char *ptr, size_t len); +refuse_upgrade: + c->state.http = old; + if (fio_http_send_error_response(h, 403)) + fio_io_free(c->io); + fio_http_free(h); +} -/** - * Parses a C string for JSON data. If `consumed` is not NULL, the `size_t` - * variable will contain the number of bytes consumed before the parser stopped - * (due to either error or end of a valid JSON data segment). - * - * Returns a FIOBJ object matching the JSON valid C string `str`. - * - * If the parsing failed (no complete valid JSON data) `FIOBJ_INVALID` is - * returned. - */ -SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed); +FIO_SFUNC void fio___http_perform_user_upgrade_callback_sse(void *cb_, + void *h_) { + union { + int (*fn)(fio_http_s *); + void *ptr; + } cb = {.ptr = cb_}; + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (cb.fn(h)) + goto refuse_upgrade; + if (c->h) /* request after eventsource? an attack vector? */ + goto refuse_upgrade; + fio_http_upgrade_sse(h); + return; -/** Helper macro, calls `fiobj_json_parse` with string information */ -#define fiobj_json_parse2(data_, len_, consumed) \ - fiobj_json_parse(FIO_STR_INFO2(data_, len_), consumed) +refuse_upgrade: + if (fio_http_send_error_response(h, 403)) + fio_io_free(c->io); + fio_http_free(h); +} -/** - * Uses JavaScript style notation to find data in an object structure. - * - * For example, "[0].name" will return the "name" property of the first object - * in an array object. - * - * Returns a temporary reference to the object or FIOBJ_INVALID on an error. - * - * Use `fiobj_dup` to collect an actual reference to the returned object. - */ -SFUNC FIOBJ fiobj_json_find(FIOBJ object, fio_str_info_s notation); -/** - * Uses JavaScript style notation to find data in an object structure. - * - * For example, "[0].name" will return the "name" property of the first object - * in an array object. - * - * Returns a temporary reference to the object or FIOBJ_INVALID on an error. - * - * Use `fiobj_dup` to collect an actual reference to the returned object. - */ -#define fiobj_json_find2(object, str, length) \ - fiobj_json_find(object, FIO_STR_INFO2(str, length)) +FIO_IFUNC int fio___http_on_http_test4upgrade(fio_http_s *h, + fio___http_connection_s *c) { + union { + int (*fn)(fio_http_s *); + void *ptr; + } cb; + if (fio_http_websocket_requested(h)) + goto websocket_requested; + if (fio_http_sse_requested(h)) + goto sse_requested; + return 0; +websocket_requested: + cb.fn = c->settings->on_authenticate_websocket; + fio_queue_push(c->queue, + fio___http_perform_user_upgrade_callback_websocket, + cb.ptr, + (void *)h); + return -1; -/* ***************************************************************************** -FIOBJ Mustache support -***************************************************************************** */ +sse_requested: + cb.fn = c->settings->on_authenticate_sse; + fio_queue_push(c->queue, + fio___http_perform_user_upgrade_callback_sse, + cb.ptr, + (void *)h); + return -1; -/** - * Builds a Mustache template using a FIOBJ context (usually a Hash). - * - * Returns a FIOBJ String with the rendered template. May return `FIOBJ_INVALID` - * if nothing was written. - */ -FIO_IFUNC FIOBJ fiobj_mustache_build(fio_mustache_s *m, FIOBJ ctx); +#if 0 +http2_requested: + // Connection: Upgrade, HTTP2-Settings + // Upgrade: h2c + // HTTP2-Settings: + return 0; /* allowed to ignore upgrade request */ +#endif +} -/** - * Builds a Mustache template using a FIOBJ context (usually a Hash). - * - * Writes output to `dest` string (may be `FIOBJ_INVALID` / `NULL`). - * - * Returns `dest` (or a new String). May return `FIOBJ_INVALID` if nothing was - * written and `dest` was empty. - */ -FIO_IFUNC FIOBJ fiobj_mustache_build2(fio_mustache_s *m, FIOBJ dest, FIOBJ ctx); +FIO_SFUNC void fio___http_on_http_direct(void *h_, void *ignr) { + fio_http_s *h = (fio_http_s *)h_; + fio_http_status_set(h, 200); + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (fio___http_on_http_test4upgrade(h, c)) + return; + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.fn = c->state.http.on_http}; + fio_queue_push(c->queue, fio___http_perform_user_callback, cb.ptr, (void *)h); + (void)ignr; +} -/* ***************************************************************************** +FIO_SFUNC void fio___http_on_http_with_public_folder(void *h_, void *ignr) { + fio_http_s *h = (fio_http_s *)h_; + fio_http_status_set(h, 200); + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (fio___http_on_http_test4upgrade(h, c)) + return; + if ((fio_http_method(h).len != 4 || (fio_buf2u32u(fio_http_method(h).buf) | + 0x20202020UL) != fio_buf2u32u("post")) && + !fio_http_static_file_response(h, + c->settings->public_folder, + fio_http_path(h), + c->settings->max_age)) { + fio_http_free(h); + return; + } + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.fn = c->state.http.on_http}; + fio_queue_push(c->queue, fio___http_perform_user_callback, cb.ptr, (void *)h); + (void)ignr; +} +FIO_SFUNC void fio___http_perform_user_callback_client(void *cb_, void *h_) { + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + fio___http_perform_user_callback(cb_, h_); + fio_io_free(c->io); +} +FIO_SFUNC void fio___http_on_http_client(void *h_, void *ignr) { + fio_http_s *h = (fio_http_s *)h_; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + size_t pr = FIO___HTTP_PROTOCOL_WS; + union { + void (*fn)(fio_http_s *); + void *ptr; + } cb = {.fn = c->state.http.on_http}; + /* TODO! review WS and SSE responses. */ + if (fio_http_websocket_accepted(h)) + goto websocket_accepted; + if (fio_http_sse_accepted(h)) + goto sse_accepted; + fio_queue_push(c->queue, + fio___http_perform_user_callback_client, + cb.ptr, + (void *)h); + return; + (void)ignr; +sse_accepted: + pr = FIO___HTTP_PROTOCOL_SSE; +websocket_accepted: + c->h = h; /* was set to NULL in `on_http_complete` */ + fio_http_controller_set( + c->h, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr] + .controller)); + fio_io_protocol_set( + c->io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr] + .protocol)); + FIO_LOG_DDEBUG2("(%d) Client %s upgrade complete for fd %d", + fio_io_pid(), + (fio_http_is_websocket(h) ? "WebSocket" : "SSE"), + fio_io_fd(c->io)); -FIOBJ - Implementation - Inline / Macro like fucntions + fio_io_free(c->io); /* fio_dup called by fio_http1_on_complete */ + c->suspend = 0; + fio_io_unsuspend(c->io); +} +/* ***************************************************************************** +ALPN Helpers +***************************************************************************** */ +FIO_SFUNC void fio___http_on_select_h1(fio_io_s *io) { + FIO_LOG_DDEBUG2("TLS ALPN HTTP/1.1 selected for %p", io); + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_io_protocol_set( + io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .protocol)); +} +FIO_SFUNC void fio___http_on_select_h2(fio_io_s *io) { + FIO_LOG_ERROR("TLS ALPN HTTP/2 not supported for %p", io); + (void)io; +} +/* ***************************************************************************** +HTTP Listen +***************************************************************************** */ +static void fio___http_listen_on_start(fio_io_protocol_s *protocol, void *u) { + (void)u; + fio___http_protocol_s *p = (fio___http_protocol_s *)protocol; + p->queue = ((p->settings.queue && p->settings.queue->q) ? p->settings.queue->q + : fio_io_queue()); +} +static void fio___http_listen_on_stop(fio_io_protocol_s *p, void *u) { + (void)u; + fio___http_protocol_free( + FIO_PTR_FROM_FIELD(fio___http_protocol_s, + state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, + p)); +} +void fio_http_listen___(void); /* IDE marker */ +SFUNC void *fio_http_listen FIO_NOOP(const char *url, fio_http_settings_s s) { + http_settings_validate(&s, 0); + fio___http_protocol_s *p = fio___http_protocol_new(s.public_folder.len + 1); + fio___http_protocol_init(p, url, s, 0); + void *listener = + fio_io_listen(.url = url, + .protocol = &p->state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, + .tls = s.tls, + .on_start = fio___http_listen_on_start, + .on_stop = fio___http_listen_on_stop, + .queue_for_accept = p->settings.queue); + return listener; +} +/* ***************************************************************************** +HTTP Connect ***************************************************************************** */ +static void fio___http_connect_on_failed(fio_io_protocol_s *p, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_http_free(c->h); + c->h = NULL; + fio___http_connection_free(c); + (void)p; +} + +void fio_http_connect___(void); /* IDE Marker */ +/** Connects to HTTP / WebSockets / SSE connections on `url`. */ +SFUNC fio_io_s *fio_http_connect FIO_NOOP(const char *url, + fio_http_s *h, + fio_http_settings_s s) { + FIO_STR_INFO_TMP_VAR(origin, 4096); + http_settings_validate(&s, 1); + fio_url_s u = (fio_url_s){0}; + if (url) + u = fio_url_parse(url, strlen(url)); + + if (!h) + h = fio_http_new(); + if (!fio_http_path(h).len) + fio_http_path_set(h, + u.path.len ? FIO_BUF2STR_INFO(u.path) + : FIO_STR_INFO2((char *)"/", 1)); + if (!fio_http_query(h).len && u.query.len) + fio_http_query_set(h, FIO_BUF2STR_INFO(u.query)); + if (!fio_http_method(h).len) + fio_http_method_set(h, FIO_STR_INFO2((char *)"GET", 3)); + if (u.host.len) { + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO2((char *)"host", 4), + FIO_BUF2STR_INFO(u.host)); + /* Origin header */ + fio_string_write2( + &origin, + NULL, + FIO_STRING_WRITE_STR2("https", (size_t)(4 + fio_url_is_tls(u).tls)), + FIO_STRING_WRITE_STR2("://", 3U), + FIO_STRING_WRITE_STR_INFO(u.host), + FIO_STRING_WRITE_STR2(":", (size_t)(!!u.port.len)), + FIO_STRING_WRITE_STR_INFO(u.port)); + } + + /* test for ws:// or wss:// - WebSocket scheme */ + if ((u.scheme.len == 2 || + (u.scheme.len == 3 && ((u.scheme.buf[2] | 0x20) == 's'))) && + (fio_buf2u16u(u.scheme.buf) | 0x2020) == fio_buf2u16u("ws")) { + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO2((char *)"origin", 6), + origin); + fio_http_websocket_set_request(h); + } + /* test for sse:// or sses:// - Server Sent Events scheme */ + else if ((u.scheme.len == 3 || + (u.scheme.len == 4 && ((u.scheme.buf[3] | 0x20) == 's'))) && + (fio_buf2u32u(u.scheme.buf) | fio_buf2u32u("\x20\x20\x20\xFF")) == + fio_buf2u32u("sse\xFF")) { + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO2((char *)"origin", 6), + origin); + fio_http_sse_set_request(h); + } + + /* TODO: test for and attempt to re-use connection */ + // if (fio_http_cdata(h)) { } + + fio___http_protocol_s *p = fio___http_protocol_new(u.host.len); + fio___http_protocol_init(p, url, s, 1); + fio___http_connection_s *c = + fio___http_connection_new(p->settings.max_line_len); + FIO_ASSERT_ALLOC(c); + *c = (fio___http_connection_s){ + .io = NULL, + .h = h, + .settings = &(p->settings), + .queue = p->queue, + .udata = p->settings.udata, + .state.http = + { + .on_http_callback = p->on_http_callback, + .on_http = p->settings.on_http, + .on_finish = p->settings.on_finish, + .max_header = p->settings.max_header_size, + }, + .capa = p->settings.max_line_len, + .log = p->settings.log, + .is_client = 1, + }; + fio_http_controller_set(h, &p->state[FIO___HTTP_PROTOCOL_HTTP1].controller); + if (!fio_http_udata(h)) /* avoid overwriting existing `udata` if set */ + fio_http_udata_set(h, c->udata); + fio_http_cdata_set(h, fio___http_connection_dup(c)); + return fio_io_connect(url, + .protocol = + &p->state[FIO___HTTP_PROTOCOL_HTTP1].protocol, + .on_failed = fio___http_connect_on_failed, + .udata = c, + .tls = s.tls, + .timeout = s.connect_timeout); +} + /* ***************************************************************************** -The FIOBJ Type +HTTP/1.1 Request / Response Completed ***************************************************************************** */ -/** Returns an objects type. This isn't limited to known types. */ -FIO_IFUNC size_t fiobj_type(FIOBJ o) { - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: - switch ((uintptr_t)(o)) { - case FIOBJ_T_NULL: return FIOBJ_T_NULL; - case FIOBJ_T_TRUE: return FIOBJ_T_TRUE; - case FIOBJ_T_FALSE: return FIOBJ_T_FALSE; - }; - return FIOBJ_T_INVALID; - case FIOBJ_T_NUMBER: return FIOBJ_T_NUMBER; - case FIOBJ_T_FLOAT: return FIOBJ_T_FLOAT; - case FIOBJ_T_STRING: return FIOBJ_T_STRING; - case FIOBJ_T_ARRAY: return FIOBJ_T_ARRAY; - case FIOBJ_T_HASH: return FIOBJ_T_HASH; - case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->type_id; - } - if (!o) - return FIOBJ_T_NULL; - return FIOBJ_T_INVALID; +/** called when either a request or a response was received. */ +static void fio_http1_on_complete(void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_io_dup(c->io); /* make sure the IO and its data are valid in callback */ + fio_io_suspend(c->io); + fio_http_s *h = c->h; + c->h = NULL; + c->suspend = 1; + // fio_io_defer(c->state.http.on_http_callback, h, NULL); + fio_queue_push(fio_io_queue(), c->state.http.on_http_callback, h); } /* ***************************************************************************** -FIOBJ Memory Management +HTTP/1.1 Parser callbacks ***************************************************************************** */ -/** Increases an object's reference count (or copies) and returns it. */ -FIO_IFUNC FIOBJ fiobj_dup(FIOBJ o) { - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: /* fall through */ - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_FLOAT: /* fall through */ return o; - case FIOBJ_T_STRING: /* fall through */ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), dup)(o); - break; - case FIOBJ_T_ARRAY: /* fall through */ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), dup)(o); - break; - case FIOBJ_T_HASH: /* fall through */ - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), dup)(o); - break; - case FIOBJ_T_OTHER: /* fall through */ fiobj_object_dup(o); - } - return o; +FIO_IFUNC void fio___http_request_too_big(fio___http_connection_s *c) { + fio_http_s *h = c->h; + fio_io_dup(c->io); /* sending the response will result in fio_undup */ + fio_io_suspend(c->io); + c->h = NULL; + c->suspend = 1; + if (fio_http_send_error_response(h, 413)) + fio_io_free(c->io); /* response not sent, we need to fio_undup */ + fio_http_free(h); } -/** Decreases an object's reference count or frees it. */ -FIO_IFUNC void fiobj_free(FIOBJ o) { - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: /* fall through */ - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_FLOAT: return; - case FIOBJ_T_STRING: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), free)(o); - return; - case FIOBJ_T_ARRAY: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), free)(o); - return; - case FIOBJ_T_HASH: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), free)(o); - return; - case FIOBJ_T_OTHER: (*fiobj_object_metadata(o))->free2(o); return; - } +FIO_IFUNC void fio_http1_attach_handle(fio___http_connection_s *c) { + c->h = fio_http_new(); + FIO_ASSERT_ALLOC(c->h); + fio_http_controller_set( + c->h, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings)) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .controller); + fio_http_udata_set(c->h, c->udata); + fio_http_cdata_set(c->h, fio___http_connection_dup(c)); } -/* ***************************************************************************** -FIOBJ Data / Info -***************************************************************************** */ - -/** Internal: compares two nestable objects. */ -SFUNC unsigned char fiobj___test_eq_nested(FIOBJ restrict a, - FIOBJ restrict b, - size_t nesting); - -/** Compares two objects. */ -FIO_IFUNC unsigned char FIO_NAME_BL(fiobj, eq)(FIOBJ a, FIOBJ b) { - if (a == b) - return 1; - if (FIOBJ_TYPE_CLASS(a) != FIOBJ_TYPE_CLASS(b)) - return 0; - switch (FIOBJ_TYPE_CLASS(a)) { - case FIOBJ_T_PRIMITIVE: - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_FLOAT: /* fall through */ return a == b; - case FIOBJ_T_STRING: - return FIO_NAME_BL(FIO_NAME(fiobj, FIOBJ___NAME_STRING), eq)(a, b); - case FIOBJ_T_ARRAY: return fiobj___test_eq_nested(a, b, 0); - case FIOBJ_T_HASH: return fiobj___test_eq_nested(a, b, 0); - case FIOBJ_T_OTHER: - if ((*fiobj_object_metadata(a))->count(a) || - (*fiobj_object_metadata(b))->count(b)) { - if ((*fiobj_object_metadata(a))->count(a) != - (*fiobj_object_metadata(b))->count(b)) - return 0; - return fiobj___test_eq_nested(a, b, 0); - } - return (*fiobj_object_metadata(a))->type_id == - (*fiobj_object_metadata(b))->type_id && - (*fiobj_object_metadata(a))->is_eq(a, b); - } +/** called when a request method is parsed. */ +static int fio_http1_on_method(fio_buf_info_s method, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (c->h) + return -1; + fio_http1_attach_handle(c); + fio_http_method_set(c->h, FIO_BUF2STR_INFO(method)); return 0; } - -/** Returns a temporary String representation for any FIOBJ object. */ -FIO_IFUNC fio_str_info_s FIO_NAME2(fiobj, cstr)(FIOBJ o) { - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: - switch ((uintptr_t)(o)) { - case FIOBJ_T_NULL: return FIO_STR_INFO2((char *)"null", 4); - case FIOBJ_T_TRUE: return FIO_STR_INFO2((char *)"true", 4); - case FIOBJ_T_FALSE: return FIO_STR_INFO2((char *)"false", 5); - }; - return (fio_str_info_s){.buf = (char *)""}; - case FIOBJ_T_NUMBER: - return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), cstr)(o); - case FIOBJ_T_FLOAT: - return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), cstr)(o); - case FIOBJ_T_STRING: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); - case FIOBJ_T_ARRAY: /* fall through */ - return FIO_STR_INFO2((char *)"[...]", 5); - case FIOBJ_T_HASH: { - return FIO_STR_INFO2((char *)"{...}", 5); - } - case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_s(o); - } - /* a non-explicit NULL is an empty string. */ - return (fio_str_info_s){.buf = (char *)""}; +/** called when a response status is parsed. the status_str is the string + * without the prefixed numerical status indicator.*/ +static int fio_http1_on_status(size_t istatus, + fio_buf_info_s status, + void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_http_clear_response(c->h, istatus != 301 && istatus != 302); + fio_http_status_set(c->h, istatus); + return 0; + (void)status; } - -/** Returns an integer representation for any FIOBJ object. */ -FIO_IFUNC intptr_t FIO_NAME2(fiobj, i)(FIOBJ o) { - fio_str_info_s tmp; - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: - switch ((uintptr_t)(o)) { - case FIOBJ_T_NULL: return 0; - case FIOBJ_T_TRUE: return 1; - case FIOBJ_T_FALSE: return 0; - }; +/** called when a request URL is parsed. */ +static int fio_http1_on_url(fio_buf_info_s url, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_url_s u = fio_url_parse(url.buf, url.len); + if (!u.path.len || u.path.buf[0] != '/') return -1; - case FIOBJ_T_NUMBER: - return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(o); - case FIOBJ_T_FLOAT: - return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(o); - case FIOBJ_T_STRING: - tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); - if (!tmp.len) - return 0; - return fio_atol(&tmp.buf); - case FIOBJ_T_ARRAY: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); - case FIOBJ_T_HASH: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); - case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_i(o); - } - if (!o) + fio_http_path_set(c->h, FIO_BUF2STR_INFO(u.path)); + if (u.query.len) + fio_http_query_set(c->h, FIO_BUF2STR_INFO(u.query)); + if (u.host.len) + (!(c->h) ? fio_http_request_header_set + : fio_http_response_header_set)(c->h, + FIO_STR_INFO1((char *)"host"), + FIO_BUF2STR_INFO(u.host)); + return 0; +} +/** called when a the HTTP/1.x version is parsed. */ +static int fio_http1_on_version(fio_buf_info_s version, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + FIO_ASSERT_DEBUG(c->h, "on_version called without a pre-existing handle!"); + if (!c->h) + return -1; + fio_http_version_set(c->h, FIO_BUF2STR_INFO(version)); + return 0; +} +/** called when a header is parsed. */ +static int fio_http1_on_header(fio_buf_info_s name, + fio_buf_info_s value, + void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (!c->h) + return 0; /* ignore possible post-error response headers */ + (!fio_http_status(c->h) + ? fio_http_request_header_add + : fio_http_response_header_add)(c->h, + FIO_BUF2STR_INFO(name), + FIO_BUF2STR_INFO(value)); + return 0; +} +/** called when the special content-length header is parsed. */ +static int fio_http1_on_header_content_length(fio_buf_info_s name, + fio_buf_info_s value, + size_t content_length, + void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_http_s *h = c->h; + if (!h) return 0; - return -1; + if (content_length > c->settings->max_body_size) + goto too_big; + if (content_length) + fio_http_body_expect(c->h, content_length); +#if FIO_HTTP_SHOW_CONTENT_LENGTH_HEADER + (!(h->status) ? fio_http_request_header_add + : fio_http_response_header_add)(h, + FIO_BUF2STR_INFO(name), + FIO_BUF2STR_INFO(value)); +#endif + return 0; +too_big: + fio___http_request_too_big(c); + return 0; /* should we disconnect (return -1), or not? */ + (void)name, (void)value; +} +/** called when `Expect` arrives and may require a 100 continue response. */ +static int fio_http1_on_expect(void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + const fio_buf_info_s response = + FIO_BUF_INFO1((char *)"HTTP/1.1 100 Continue\r\n\r\n"); + fio_http_s *h = c->h; + if (!h) + return 1; + c->h = NULL; + /* TODO: test for body size violation and deny request if payload too big. */ + if (FIO_HTTP1_EXPECTED_CHUNKED != fio_http1_expected(&c->state.http.parser) && + c->settings->max_body_size > fio_http1_expected(&c->state.http.parser)) + goto payload_too_big; + c->settings->pre_http_body(h); + if (fio_http_status(h)) + goto response_sent; + c->h = h; + fio_io_write2(c->io, .buf = response.buf, .len = response.len, .copy = 0); + return 0; /* TODO?: improve support for `expect` headers? */ +payload_too_big: + fio_io_dup(c->io); + if (fio_http_send_error_response(h, 413)) + fio_io_free(c->io); /* response not sent, we need to fio_undup */ + /* fall through */ +response_sent: + // c->h = NULL; + fio_http_free(h); + return 1; } -/** Returns a float (double) representation for any FIOBJ object. */ -FIO_IFUNC double FIO_NAME2(fiobj, f)(FIOBJ o) { - fio_str_info_s tmp; - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: - switch ((uintptr_t)(o)) { - case FIOBJ_T_FALSE: /* fall through */ - case FIOBJ_T_NULL: return 0.0; - case FIOBJ_T_TRUE: return 1.0; - }; - return -1.0; - case FIOBJ_T_NUMBER: - return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(o); - case FIOBJ_T_FLOAT: - return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(o); - case FIOBJ_T_STRING: - tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), info)(o); - if (!tmp.len) - return 0; - return (double)fio_atof(&tmp.buf); - case FIOBJ_T_ARRAY: - return (double)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); - case FIOBJ_T_HASH: - return (double)FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); - case FIOBJ_T_OTHER: return (*fiobj_object_metadata(o))->to_f(o); - } - if (!o) - return 0.0; - return -1.0; +/** called when a body chunk is parsed. */ +static int fio_http1_on_body_chunk(fio_buf_info_s chunk, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (!c->h) + return -1; /* close connection if a large payload is unstoppable */ + if (c->is_client && + (fio_http_status(c->h) == 301 || fio_http_status(c->h) == 302)) + return 0; /* don't overwrite client payload on redirect */ + if (chunk.len + fio_http_body_length(c->h) > c->settings->max_body_size) + goto too_big; + fio_http_body_write(c->h, chunk.buf, chunk.len); + return 0; +too_big: + fio___http_request_too_big(c); + return 0; } /* ***************************************************************************** -FIOBJ Integers +HTTP/1.1 Accepting new connections (tests for special HTTP/2 pre-knowledge) ***************************************************************************** */ -#define FIO_REF_NAME fiobj___bignum -#define FIO_REF_TYPE intptr_t -#define FIO_REF_METADATA const FIOBJ_class_vtable_s * -#define FIO_REF_METADATA_INIT(m) \ - do { \ - m = &FIOBJ___NUMBER_CLASS_VTBL; \ - FIOBJ_MARK_MEMORY_ALLOC(); \ - } while (0) -#define FIO_REF_METADATA_DESTROY(m) \ - do { \ - FIOBJ_MARK_MEMORY_FREE(); \ - } while (0) -#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) -#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) -#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) -#define FIO_PTR_TAG_TYPE FIOBJ -#include FIO_INCLUDE_FILE - -/* Places a 61 or 29 bit signed integer in the leftmost bits of a word. */ -#define FIO_NUMBER_ENCODE(i) (((uintptr_t)(i) << 3) | FIOBJ_T_NUMBER) -/* Reads a 61 or 29 bit signed integer from the leftmost bits of a word. */ -#define FIO_NUMBER_DECODE(i) \ - ((intptr_t)(((uintptr_t)(i) >> 3) | \ - ((uintptr_t)0 - \ - (((uintptr_t)(i) >> 3) & \ - ((uintptr_t)1 << ((sizeof(uintptr_t) * 8) - 4)))))) +/** Called when an IO is attached to a protocol. */ +FIO_SFUNC void fio___http_on_attach_accept(fio_io_s *io) { -/** Creates a new Number object. */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), - new)(intptr_t i) { - FIOBJ o = (FIOBJ)FIO_NUMBER_ENCODE(i); - if (FIO_NUMBER_DECODE(o) == i) - return o; - o = fiobj___bignum_new2(); + fio___http_protocol_s *p = + FIO_PTR_FROM_FIELD(fio___http_protocol_s, + state[FIO___HTTP_PROTOCOL_ACCEPT].protocol, + fio_io_protocol(io)); + fio___http_protocol_dup(p); + // p->queue = fio_io_queue(); - FIO_PTR_MATH_RMASK(intptr_t, o, 3)[0] = i; - return o; + const uint32_t capa = p->settings.max_line_len; + fio___http_connection_s *c = fio___http_connection_new(capa); + FIO_ASSERT_ALLOC(c); + *c = (fio___http_connection_s){ + .io = io, + .settings = &(p->settings), + .queue = + ((p->settings.queue && p->settings.queue->q) ? p->settings.queue->q + : fio_io_queue()), + .udata = p->settings.udata, + .state.http = + { + .on_http_callback = p->on_http_callback, + .on_http = p->settings.on_http, + .on_finish = p->settings.on_finish, + .max_header = p->settings.max_header_size, + }, + .capa = capa, + .log = p->settings.log, + }; + fio_io_udata_set(io, (void *)c); + FIO_LOG_DDEBUG2("(%d) HTTP accepted a new connection (%p)", + (int)fio_thread_getpid(), + c->io); +#if 0 /* skip pre-knowledge test? */ + fio_io_protocol_set( + io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .protocol)); +#endif } -/** Reads the number from a FIOBJ number. */ -FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(FIOBJ i) { - if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_NUMBER) - return FIO_NUMBER_DECODE(i); - if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) - return FIO_PTR_MATH_RMASK(intptr_t, i, 3)[0]; - return 0; -} +/** Called when a data is available. */ +FIO_SFUNC void fio___http1_accept_on_data(fio_io_s *io) { + const fio_buf_info_s prior_knowledge = FIO_BUF_INFO2( + (char *)"\x50\x52\x49\x20\x2a\x20\x48\x54\x54\x50\x2f\x32\x2e\x30" + "\x0d\x0a\x0d\x0a\x53\x4d\x0d\x0a\x0d\x0a", + 24); + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_io_protocol_s *phttp_new; + size_t r = fio_io_read(io, c->buf + c->len, c->capa - c->len); + if (!r) /* nothing happened */ + return; + c->len = (uint32_t)r; + if (prior_knowledge.buf[0] != c->buf[0] || + FIO_MEMCMP( + prior_knowledge.buf, + c->buf, + (c->len > prior_knowledge.len ? prior_knowledge.len : c->len))) { + /* no prior knowledge, switch to HTTP/1.1 */ + phttp_new = + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP1] + .protocol); + fio_io_protocol_set(io, phttp_new); + return; + } + if (c->len < prior_knowledge.len) /* wait for more data */ + return; -/** Reads the number from a FIOBJ number, fitting it in a double. */ -FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f)(FIOBJ i) { - return (double)FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(i); -} + if (c->len > prior_knowledge.len) + FIO_MEMMOVE(c->buf, + c->buf + prior_knowledge.len, + c->len - prior_knowledge.len); + c->len -= prior_knowledge.len; + phttp_new = &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[FIO___HTTP_PROTOCOL_HTTP2] + .protocol); -/** Frees a FIOBJ number. */ -FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), free)(FIOBJ i) { - if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) - fiobj___bignum_free2(i); + fio_io_protocol_set(io, phttp_new); } -FIO_IFUNC unsigned char FIO_NAME_BL(fiobj___num, eq)(FIOBJ restrict a, - FIOBJ restrict b) { - /* it should be safe to assume that FIOBJ_TYPE_CLASS(i) != FIOBJ_T_NUMBER */ - return FIO_PTR_MATH_RMASK(intptr_t, a, 3)[0] == - FIO_PTR_MATH_RMASK(intptr_t, b, 3)[0]; - // return FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(a) == - // FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(b); +FIO_SFUNC void fio___http_on_close(void *buf, void *udata) { + FIO_LOG_DDEBUG2("(%d) HTTP connection closed for %p", + (int)fio_thread_getpid(), + udata); + fio___http_connection_s *c = (fio___http_connection_s *)udata; + c->io = NULL; + fio_http_free(c->h); + fio___http_connection_free(c); + (void)buf; } -#undef FIO_NUMBER_ENCODE -#undef FIO_NUMBER_DECODE - /* ***************************************************************************** -FIOBJ Floats +HTTP/1.1 Protocol ***************************************************************************** */ -#define FIO_REF_NAME fiobj___bigfloat -#define FIO_REF_TYPE double -#define FIO_REF_METADATA const FIOBJ_class_vtable_s * -#define FIO_REF_METADATA_INIT(m) \ - do { \ - m = &FIOBJ___FLOAT_CLASS_VTBL; \ - FIOBJ_MARK_MEMORY_ALLOC(); \ - } while (0) -#define FIO_REF_METADATA_DESTROY(m) \ - do { \ - FIOBJ_MARK_MEMORY_FREE(); \ - } while (0) -#define FIO_PTR_TAG(p) FIOBJ_PTR_TAG(p, FIOBJ_T_OTHER) -#define FIO_PTR_UNTAG(p) FIOBJ_PTR_UNTAG(p) -#define FIO_PTR_TAG_VALIDATE(p) (FIOBJ_TYPE_CLASS(p) == FIOBJ_T_OTHER) -#define FIO_PTR_TAG_TYPE FIOBJ -#include FIO_INCLUDE_FILE - -/** Creates a new Float object. */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(double i) { - FIOBJ ui; - if (sizeof(double) <= sizeof(FIOBJ)) { - union { - double d; - uintptr_t i; - } punned; - punned.i = 0; /* dead code, but leave it, just in case */ - punned.d = i; - if ((punned.i & 7) == 0) { - return (FIOBJ)(punned.i | FIOBJ_T_FLOAT); - } - } - ui = fiobj___bigfloat_new2(); - - FIO_PTR_MATH_RMASK(double, ui, 3)[0] = i; - return ui; -} +FIO_SFUNC int fio___http1_process_data(fio_io_s *io, + fio___http_connection_s *c) { + (void)io, (void)c; + size_t consumed = fio_http1_parse(&c->state.http.parser, + FIO_BUF_INFO2(c->buf, c->len), + (void *)c); + if (!consumed) + return -1; + if (consumed == FIO_HTTP1_PARSER_ERROR) + goto http1_error; + c->len -= consumed; + if (c->len) + FIO_MEMMOVE(c->buf, c->buf + consumed, c->len); + if (c->suspend) + return -1; + return 0; -/** Reads the integer part from a FIOBJ Float. */ -FIO_IFUNC intptr_t FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i)(FIOBJ i) { - return (intptr_t)floor(FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(i)); +http1_error: + FIO_LOG_DDEBUG2("HTTP/1.1 parser error! disconnecting client at %d", + fio_io_fd(io)); + if (c->h) { + fio_http_s *h = c->h; + c->h = NULL; + if (!c->is_client) { + fio_io_dup(c->io); + if (fio_http_send_error_response(h, 400)) + fio_io_free(c->io); + } + fio_http_free(h); + } + fio_io_close(io); + return -1; } -/** Reads the number from a FIOBJ number, fitting it in a double. */ -FIO_IFUNC double FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(FIOBJ i) { - if (sizeof(double) <= sizeof(FIOBJ) && FIOBJ_TYPE_CLASS(i) == FIOBJ_T_FLOAT) { - union { - double d; - uint64_t i; - } punned; - punned.d = 0; /* dead code, but leave it, just in case */ - punned.i = (uint64_t)(uintptr_t)i; - punned.i = ((uint64_t)(uintptr_t)i & (~(uintptr_t)7ULL)); - return punned.d; +// /** Called when a data is available. */ +FIO_SFUNC void fio___http1_on_data(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + size_t r; + for (;;) { + if (c->capa == c->len) + return; + if (!(r = fio_io_read(io, c->buf + c->len, c->capa - c->len))) + return; + c->len += r; + if (fio___http1_process_data(io, c)) + return; } - if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) - return FIO_PTR_MATH_RMASK(double, i, 3)[0]; - return 0.0; } -/** Frees a FIOBJ number. */ -FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), free)(FIOBJ i) { - if (FIOBJ_TYPE_CLASS(i) == FIOBJ_T_OTHER) - fiobj___bigfloat_free2(i); +// /** Called when an IO is attached to a protocol. */ +FIO_SFUNC void fio___http1_on_attach(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + if (c->len) + fio___http1_process_data(io, c); return; } /* ***************************************************************************** -FIOBJ Basic Iteration +HTTP/1.1 Client Protocol ***************************************************************************** */ -/** - * Performs a task for each element held by the FIOBJ object. - * - * If `task` returns -1, the `each` loop will break (stop). - * - * Returns the "stop" position - the number of elements processed + `start_at`. - */ -FIO_SFUNC uint32_t fiobj_each1(FIOBJ o, - int (*task)(fiobj_each_s *e), - void *udata, - int32_t start_at) { - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: /* fall through */ - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_STRING: /* fall through */ - case FIOBJ_T_FLOAT: return 0; - case FIOBJ_T_ARRAY: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), each)( - o, - (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), each_s *)))task, - udata, - start_at); - case FIOBJ_T_HASH: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each)( - o, - (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each_s *)))task, - udata, - start_at); - case FIOBJ_T_OTHER: - return (*fiobj_object_metadata(o))->each1(o, task, udata, start_at); - } +/** Iterates through all cookies. A non-zero return will stop iteration. */ +FIO_SFUNC int fio_http1___write_client_cookie_callback(fio_http_s *h, + fio_str_info_s name, + fio_str_info_s value, + void *udata) { + fio_str_info_s *buf = (fio_str_info_s *)udata; + fio_string_write2(buf, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR2("cookie:", 7), + FIO_STRING_WRITE_STR_INFO(name), + FIO_STRING_WRITE_STR2("=", 1), + FIO_STRING_WRITE_STR_INFO(value), + FIO_STRING_WRITE_STR2("\r\n", 2)); return 0; + (void)h; } -/* ***************************************************************************** -FIOBJ Hash Maps -***************************************************************************** */ +/** called by the HTTP handle for each header. */ +FIO_SFUNC int fio_http1___write_header_callback(fio_http_s *h, + fio_str_info_s name, + fio_str_info_s value, + void *out_) { + (void)h; + /* manually copy, as this is an "all or nothing" copy (no truncation) */ + fio_str_info_s *out = (fio_str_info_s *)out_; + return fio_string_write2(out, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR2(name.buf, name.len), + FIO_STRING_WRITE_STR2(":", 1), + FIO_STRING_WRITE_STR2(value.buf, value.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); +} -/** Calculates an object's hash value for a specific hash map object. */ -FIO_IFUNC uint64_t FIO_NAME2(fiobj, hash)(FIOBJ o) { - uint64_t seed = (uint64_t)(uintptr_t)&FIO_NAME2(fiobj, hash); - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: - return fio_risky_hash(&o, sizeof(o), seed + (uintptr_t)o); - case FIOBJ_T_NUMBER: { - uintptr_t tmp = FIO_NAME2(fiobj, i)(o); - return fio_risky_hash(&tmp, sizeof(tmp), seed); - } - case FIOBJ_T_FLOAT: { - double tmp = FIO_NAME2(fiobj, f)(o); - return fio_risky_hash(&tmp, sizeof(tmp), seed); - } - case FIOBJ_T_STRING: /* fall through */ - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), hash)(o, seed); - case FIOBJ_T_ARRAY: { - uint64_t h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); - h += fio_risky_hash(&h, sizeof(h), seed + FIOBJ_T_ARRAY); - { - FIOBJ *a = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), ptr)(o); - const size_t count = - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); - if (a) { - for (size_t i = 0; i < count; ++i) { - h += FIO_NAME2(fiobj, hash)(a[i]); - } - } - } - return h; +FIO_SFUNC void fio___http1_send_request(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c->io || !fio_io_is_open(c->io)) + return; + fio_str_info_s buf = FIO_STR_INFO2(NULL, 0); + /* set Content-Length (client is never streaming) */ + if (fio_http_body_length(h)) { + char ibuf[32]; + fio_str_info_s k = FIO_STR_INFO2((char *)"content-length", 14); + fio_str_info_s v = FIO_STR_INFO3(ibuf, 0, 32); + v.len = fio_digits10u(fio_http_body_length(h)); + fio_ltoa10u(v.buf, fio_http_body_length(h), v.len); + fio_http_request_header_set(h, k, v); } - case FIOBJ_T_HASH: { - uint64_t h = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); - h += fio_risky_hash(&h, sizeof(h), seed + FIOBJ_T_HASH); - FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), o, i) { - h += i.hash; - h += FIO_NAME2(fiobj, hash)(i.value); - } - return h; + { /* set sensible defaults for common headers (Accept, User-Agent) */ + fio_http_request_header_set_if_missing(h, + FIO_STR_INFO1((char *)"accept"), + FIO_STR_INFO1((char *)"*/*")); + fio_http_request_header_set_if_missing( + h, + FIO_STR_INFO1((char *)"user-agent"), + FIO_STR_INFO1((char *)"facil.io/" FIO_VERSION_STRING)); } - case FIOBJ_T_OTHER: { - /* TODO: can we avoid "stringifying" the object? */ - fio_str_info_s tmp = (*fiobj_object_metadata(o))->to_s(o); - return fio_risky_hash(tmp.buf, tmp.len, seed); + { /* write status string */ + fio_str_info_s method = fio_http_method(h); + fio_str_info_s path = fio_http_path(h); + fio_str_info_s version = fio_http_version(h); + if (!path.len) + path = FIO_STR_INFO1((char *)"/"); + if ((version.len - 1) > 15) + version = FIO_STR_INFO1((char *)"HTTP/1.1"); + fio_string_write2(&buf, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR_INFO(method), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_STR_INFO(path), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_STR_INFO(version), + FIO_STRING_WRITE_STR2("\r\n", 2)); } + /* write headers */ + fio_http_request_header_each(h, fio_http1___write_header_callback, &buf); + /* write cookies */ + fio_http_cookie_each(h, fio_http1___write_client_cookie_callback, &buf); + fio_string_write(&buf, FIO_STRING_REALLOC, "\r\n", 2); + /* send data (moves memory ownership) */ + fio_io_write2(c->io, + .buf = buf.buf, + .len = buf.len, + .dealloc = FIO_STRING_FREE, + .copy = 0); + /* make sure we listen to incoming data */ + c->suspend = 0; + fio_io_unsuspend(c->io); + /* Write Body */ + if (!fio_http_body_length(h)) + return; + fio_http_body_seek(h, 0); + if (fio_http_body_fd(h) == -1) { + buf = fio_http_body_read(h, (size_t)-1); + fio_io_write2(c->io, + .buf = (char *)fio_http_dup(h), + .len = buf.len, + .offset = (size_t)((char *)h - buf.buf), + .dealloc = (void (*)(void *))fio_http_free); + } else { + fio_io_write2(c->io, + .fd = fio_http_body_fd(h), + .len = fio_http_body_length(h), + .copy = 1); } - return 0; -} - -/** - * Sets a String value in a hash map, allocating the String and automatically - * calculating the hash value. - */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - set2)(FIOBJ hash, - const char *key, - size_t len, - FIOBJ value) { - FIOBJ tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(tmp, (char *)key, len); - FIOBJ v = - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set)(hash, tmp, value, NULL); - fiobj_free(tmp); - return v; } -/** - * Finds a String value in a hash map, using a temporary String and - * automatically calculating the hash value. - */ -FIO_IFUNC FIOBJ FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - get2)(FIOBJ hash, const char *buf, size_t len) { - if (FIOBJ_TYPE_CLASS(hash) != FIOBJ_T_HASH) - return FIOBJ_INVALID; - FIOBJ_STR_TEMP_VAR_STATIC(tmp, buf, len); - FIOBJ v = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(hash, tmp); - return v; +FIO_SFUNC void fio___http1_on_attach_client(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + // c->io = fio_io_dup(io); + c->io = io; + fio___http1_send_request(c->h); + if (c->len) + fio___http1_process_data(io, c); + return; } -/** - * Removes a String value in a hash map, using a temporary String and - * automatically calculating the hash value. - */ -FIO_IFUNC int FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - remove2)(FIOBJ hash, - const char *buf, - size_t len, - FIOBJ *old) { - FIOBJ_STR_TEMP_VAR_STATIC(tmp, buf, len); - int r = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(hash, tmp, old); - FIOBJ_STR_TEMP_DESTROY(tmp); - return r; +/* ***************************************************************************** +HTTP/1 Controller +***************************************************************************** */ +FIO_SFUNC int fio___http_controller_get_fd(fio_http_s *h) { + return fio_io_fd(fio_http_io(h)); } -/** Updates a hash using information from another Hash. */ -FIO_IFUNC void FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), update)(FIOBJ dest, - FIOBJ src) { - if (FIOBJ_TYPE_CLASS(dest) != FIOBJ_T_HASH || - FIOBJ_TYPE_CLASS(src) != FIOBJ_T_HASH) +/** Informs the controller that request / response headers must be sent. */ +FIO_SFUNC void fio___http_controller_http1_send_headers(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c->io || !fio_io_is_open(c->io)) return; - FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), src, i) { - if (i.key == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(i.key) == FIOBJ_T_NULL) { - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), remove)(dest, i.key, NULL); - continue; + fio_str_info_s buf = FIO_STR_INFO2(NULL, 0); + { /* write status string */ + fio_str_info_s ver = fio_http_version(h); + fio_str_info_s status = fio_http_status2str(fio_http_status(h)); + if (ver.len > 15) { + FIO_LOG_ERROR("HTTP/1.1 client version string too long!"); + ver = FIO_STR_INFO1((char *)"HTTP/1.1"); } - register FIOBJ tmp; - switch (FIOBJ_TYPE_CLASS(i.value)) { - case FIOBJ_T_ARRAY: - /* TODO? decide if we should merge elements or overwrite...? */ - tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(dest, i.key); - if (FIOBJ_TYPE_CLASS(tmp) == FIOBJ_T_ARRAY) { - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), concat) - (tmp, i.value); - continue; - } - break; - case FIOBJ_T_HASH: - tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(dest, i.key); - if (FIOBJ_TYPE_CLASS(tmp) == FIOBJ_T_HASH) - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), update) - (dest, i.value); - else break; - continue; - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_PRIMITIVE: /* fall through */ - case FIOBJ_T_STRING: /* fall through */ - case FIOBJ_T_FLOAT: /* fall through */ - case FIOBJ_T_OTHER: break; + fio_string_write2(&buf, + FIO_STRING_REALLOC, + FIO_STRING_WRITE_STR2(ver.buf, ver.len), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_NUM(fio_http_status(h)), + FIO_STRING_WRITE_STR2(" ", 1), + FIO_STRING_WRITE_STR2(status.buf, status.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + } + + /* write headers */ + fio_http_response_header_each(h, fio_http1___write_header_callback, &buf); + /* write cookies */ + fio_http_set_cookie_each(h, fio_http1___write_header_callback, &buf); + /* add streaming headers? */ + if (fio_http_is_streaming(h)) + fio_string_write(&buf, + FIO_STRING_REALLOC, + "transfer-encoding: chunked\r\n", + 28); + fio_string_write(&buf, FIO_STRING_REALLOC, "\r\n", 2); + /* send data (move memory ownership)? */ + c->state.http.buf = buf; + return; + fio_io_write2(c->io, + .buf = buf.buf, + .len = buf.len, + .dealloc = FIO_STRING_FREE, + .copy = 0); +} +/** called by the HTTP handle for each body chunk (or to finish a response. */ +FIO_SFUNC void fio___http_controller_http1_write_body( + fio_http_s *h, + fio_http_write_args_s args) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c->io || !fio_io_is_open(c->io)) + goto no_write_err; + if (fio_http_is_streaming(h)) + goto stream_chunk; + if (c->state.http.buf.len && args.buf && args.len) { + fio_string_write(&c->state.http.buf, + FIO_STRING_REALLOC, + (char *)args.buf + args.offset, + args.len); + if (args.dealloc) + args.dealloc((void *)args.buf); + fio_io_write2(c->io, + .buf = (void *)c->state.http.buf.buf, + .len = c->state.http.buf.len, + .dealloc = FIO_STRING_FREE); + c->state.http.buf = FIO_STR_INFO0; + return; + } + + fio_io_write2(c->io, + .buf = (void *)args.buf, + .fd = args.fd, + .len = args.len, + .offset = args.offset, + .dealloc = args.dealloc, + .copy = (uint8_t)args.copy); + return; + +stream_chunk: + if (args.len) { /* print chunk header */ + char buf[24]; + fio_str_info_s i = FIO_STR_INFO3(buf, 0, 24); + fio_string_write_hex(&i, NULL, args.len); + fio_string_write(&i, NULL, "\r\n", 2); + fio_io_write2(c->io, .buf = (void *)i.buf, .len = i.len, .copy = 1); + } else { + FIO_LOG_ERROR("HTTP1 streaming requires a correctly pre-determined " + "length per chunk."); + } + fio_io_write2(c->io, + .buf = (void *)args.buf, + .fd = args.fd, + .len = args.len, + .offset = args.offset, + .dealloc = args.dealloc, + .copy = (uint8_t)args.copy); + /* print chunk trailer */ + { + fio_buf_info_s trailer = FIO_BUF_INFO2((char *)"\r\n", 2); + fio_io_write2(c->io, .buf = trailer.buf, .len = trailer.len, .copy = 1); + } + return; +no_write_err: + if (args.buf) { + if (args.dealloc) + args.dealloc((void *)args.buf); + } else if (args.fd != -1) { + close(args.fd); + } +} + +FIO_SFUNC void fio___http_controller_http1_on_finish_task(void *c_, + void *upgraded) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + c->suspend = 0; + if (c->state.http.buf.len) { + fio_io_write2(c->io, + .buf = (void *)c->state.http.buf.buf, + .len = c->state.http.buf.len, + .dealloc = FIO_STRING_FREE); + c->state.http.buf = FIO_STR_INFO0; + } + + if (upgraded) + goto upgraded; + + if (fio_io_is_open(c->io)) { + /* TODO: test for connection:close header and h->status values */ + fio___http1_process_data(c->io, c); + } + if (!c->suspend) + fio_io_unsuspend(c->io); + fio_io_free(c->io); + return; + +upgraded: + if (c->h || !fio_io_is_open(c->io)) + goto something_is_wrong; + c->h = (fio_http_s *)upgraded; + { + const size_t pr_i = fio_http_is_websocket(c->h) ? FIO___HTTP_PROTOCOL_WS + : FIO___HTTP_PROTOCOL_SSE; + fio_http_controller_set( + c->h, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr_i] + .controller)); + fio_io_protocol_set( + c->io, + &(FIO_PTR_FROM_FIELD(fio___http_protocol_s, settings, c->settings) + ->state[pr_i] + .protocol)); + if (pr_i == FIO___HTTP_PROTOCOL_SSE) { + fio_str_info_s last_id = + fio_http_request_header(c->h, + FIO_STR_INFO2((char *)"last-event-id", 13), + 0); + if (last_id.buf) + c->settings->on_eventsource_reconnect(c->h, FIO_STR2BUF_INFO(last_id)); } - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) - (dest, i.key, fiobj_dup(i.value), NULL); } + fio_io_unsuspend(c->io); + fio_io_free(c->io); + return; + +something_is_wrong: + if (fio_io_is_open(c->io)) + FIO_LOG_DEBUG2("(%d) Connection upgrade went wrong for fd %d - closing", + fio_io_pid(), + fio_io_fd(c->io)); + fio_io_protocol_set(c->io, NULL); /* make zombie, timeout will clear it. */ + fio_io_free(c->io); + fio___http_connection_free(c); /* free HTTP connection element */ +} + +/** called once a request / response had finished */ +FIO_SFUNC void fio___http_controller_http1_on_finish(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (fio_http_is_streaming(h)) + fio_io_write2(c->io, .buf = (char *)"0\r\n\r\n", .len = 5, .copy = 1); + if (c->log) + fio_http_write_log(h); + if (fio_http_is_upgraded(h)) + goto upgraded; + /* once the function returns, `h` may be freed (auto-finish on free). + * so we must call this callback here (sync), no matter the thread */ + c->state.http.on_finish(h); + fio_io_defer(fio___http_controller_http1_on_finish_task, (void *)(c), NULL); + return; + +upgraded: + fio_io_defer(fio___http_controller_http1_on_finish_task, + (void *)(c), + (void *)h); } /* ***************************************************************************** -FIOBJ JSON support (inline functions) +HTTP/2 Protocol (disconnect, as HTTP/2 is unsupported) ***************************************************************************** */ -typedef struct { - FIOBJ json; - size_t level; - uint8_t beautify; -} fiobj___json_format_internal__s; +// /** Called when an IO is attached to a protocol. */ +// void (*on_attach)(fio_io_s *io); +// /** Called when a data is available. */ +// void (*on_data)(fio_io_s *io); +// /** called once all pending `fio_io_write` calls are finished. */ +// void (*on_ready)(fio_io_s *io); +// /** Called after the connection was closed, and pending tasks +// completed. +// */ void (*on_close)(void *udata); -/* internal helper function for recursive JSON formatting. */ -SFUNC void fiobj___json_format_internal__(fiobj___json_format_internal__s *, - FIOBJ); +/* ***************************************************************************** +HTTP/2 Controller (TODO!) +***************************************************************************** */ -/** Helper function, calls `fiobj_hash_update_json` with string information */ -FIO_IFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - update_json2)(FIOBJ hash, char *ptr, size_t len) { - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - update_json)(hash, FIO_STR_INFO2(ptr, len)); -} +// /** Called when an HTTP handle is freed. */ +// void (*on_destroyed)(fio_http_s *h, void *cdata); +// /** Informs the controller that request / response headers must be +// sent. +// */ void (*send_headers)(fio_http_s *h); +// /** called by the HTTP handle for each body chunk (or to finish a +// response. +// */ void (*write_body)(fio_http_s *h, fio_http_write_args_s args); +// /** called once a request / response had finished */ +// void (*on_finish)(fio_http_s *h); -/** - * Returns a JSON valid FIOBJ String, representing the object. - * - * If `dest` is an existing String, the formatted JSON data will be appended to - * the existing string. - */ -FIO_IFUNC FIOBJ FIO_NAME2(fiobj, json)(FIOBJ dest, FIOBJ o, uint8_t beautify) { - fiobj___json_format_internal__s args = - (fiobj___json_format_internal__s){.json = dest, .beautify = beautify}; - if (FIOBJ_TYPE_CLASS(dest) != FIOBJ_T_STRING) - args.json = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); - fiobj___json_format_internal__(&args, o); - return args.json; -} +/* ***************************************************************************** +Authentication Helper +***************************************************************************** */ -#undef FIO___RECURSIVE_INCLUDE /* from now on, type helpers are internal */ +/** Allows all clients to connect (bypasses authentication). */ +SFUNC int FIO_HTTP_AUTHENTICATE_ALLOW(fio_http_s *h) { + ((void)h); + return 0; +} /* ***************************************************************************** -FIOBJ Mustache support - inline implementation +WebSocket Parser Callbacks ***************************************************************************** */ -/* callback should write `txt` to output and return updated `udata.` */ -FIO_SFUNC void *fiobj___mustache_write_text(void *udata, fio_buf_info_s txt); -/* same as `write_text`, but should also HTML escape (sanitize) data. */ -FIO_SFUNC void *fiobj___mustache_write_text_escaped(void *udata, - fio_buf_info_s raw); -/* callback should return a new context pointer with the value of `name`. */ -FIO_SFUNC void *fiobj___mustache_get_var(void *ctx, fio_buf_info_s name); -/* if context is an Array, should return its length. */ -FIO_SFUNC size_t fiobj___mustache_array_length(void *ctx); -/* if context is an Array, should return a context pointer @ index. */ -FIO_SFUNC void *fiobj___mustache_get_var_index(void *ctx, size_t index); -/* should return the String value of context `var` as a `fio_buf_info_s`. */ -FIO_SFUNC fio_buf_info_s fiobj___mustache_var2str(void *var); -/* should return non-zero if the context pointer refers to a valid value. */ -FIO_SFUNC int fiobj___mustache_var_is_truthful(void *ctx); +FIO_SFUNC int fio___websocket_process_data(fio_io_s *io, + fio___http_connection_s *c); -/** - * Builds a Mustache template using a FIOBJ context (usually a Hash). - * - * Returns a FIOBJ String with the rendered template. May return `FIOBJ_INVALID` - * if nothing was written. - */ -FIO_IFUNC FIOBJ fiobj_mustache_build(fio_mustache_s *m, FIOBJ ctx) { - return (FIOBJ)fio_mustache_build( - m, - .write_text = fiobj___mustache_write_text, - .write_text_escaped = fiobj___mustache_write_text_escaped, - .get_var = fiobj___mustache_get_var, - .array_length = fiobj___mustache_array_length, - .get_var_index = fiobj___mustache_get_var_index, - .var2str = fiobj___mustache_var2str, - .var_is_truthful = fiobj___mustache_var_is_truthful, - .ctx = ctx, - .udata = NULL); +FIO_SFUNC void fio___websocket_on_message_finalize(void *c_, void *ignr_) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + c->suspend = 0; + if (c->len) + fio___websocket_process_data(c->io, c); + fio_io_unsuspend(c->io); + fio_io_free(c->io); + fio___http_connection_free(c); + (void)ignr_; +} + +FIO_SFUNC void fio___websocket_on_message_task(void *c_, void *is_text) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + c->state.ws.on_message(c->h, + fio_bstr_buf(c->state.ws.msg), + (uint8_t)(uintptr_t)is_text); + fio_bstr_free(c->state.ws.msg); + c->state.ws.msg = NULL; + fio_io_defer(fio___websocket_on_message_finalize, c, NULL); +} + +/** Called when a message frame was received. */ +FIO_SFUNC void fio_websocket_on_message(void *udata, + fio_buf_info_s msg, + unsigned char is_text) { + /* TODO: suspend IO and queue in async queue? */ + fio___http_connection_s *c = (fio___http_connection_s *)udata; + // c->state.ws.on_message(c->h, + // fio_bstr_buf(c->state.ws.msg), + // (uint8_t)(uintptr_t)is_text); + // fio_bstr_free(c->state.ws.msg); + // c->state.ws.msg = NULL; + // c->suspend = 0; + // fio___websocket_process_data(c->io, c); + // if (!c->suspend) + // fio_io_unsuspend(c->io); + // return; /* TODO: FIXME! */ + fio_io_dup(c->io); + fio___http_connection_dup(c); + fio_io_suspend(c->io); + c->suspend = 1; + fio_queue_push(c->queue, + fio___websocket_on_message_task, + udata, + (void *)(uintptr_t)is_text); + (void)msg; } /** - * Builds a Mustache template using a FIOBJ context (usually a Hash). + * Called when the parser needs to copy the message to an external buffer. * - * Writes output to `dest` string (may be `FIOBJ_INVALID` / `NULL`). + * MUST return the external buffer, as it may need to be unmasked. * - * Returns `dest` (or a new String). May return `FIOBJ_INVALID` if nothing was - * written and `dest` was empty. + * Partial message length may be equal to zero (`partial.len == 0`). */ -FIO_IFUNC FIOBJ fiobj_mustache_build2(fio_mustache_s *m, - FIOBJ dest, - FIOBJ ctx) { - dest = (FIOBJ)fio_mustache_build( - m, - .write_text = fiobj___mustache_write_text, - .write_text_escaped = fiobj___mustache_write_text_escaped, - .get_var = fiobj___mustache_get_var, - .array_length = fiobj___mustache_array_length, - .get_var_index = fiobj___mustache_get_var_index, - .var2str = fiobj___mustache_var2str, - .var_is_truthful = fiobj___mustache_var_is_truthful, - .ctx = ctx, - .udata = dest); - return dest; +FIO_SFUNC fio_buf_info_s fio_websocket_write_partial(void *udata, + fio_buf_info_s partial, + size_t more_expected) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (!c->state.ws.msg && more_expected) + c->state.ws.msg = fio_bstr_reserve(NULL, more_expected + partial.len); + c->state.ws.msg = fio_bstr_write(c->state.ws.msg, partial.buf, partial.len); + return fio_bstr_buf(c->state.ws.msg); } -/* callback should write `txt` to output and return updated `udata.` */ -FIO_SFUNC void *fiobj___mustache_write_text(void *udata, fio_buf_info_s txt) { - FIOBJ d = (FIOBJ)udata; - if (!d) - d = fiobj_str_new_buf(txt.len + 32); - fiobj_str_write(d, txt.buf, txt.len); - return (void *)d; -} -/* same as `write_text`, but should also HTML escape (sanitize) data. */ -FIO_SFUNC void *fiobj___mustache_write_text_escaped(void *ud, - fio_buf_info_s raw) { - FIOBJ d = (FIOBJ)ud; - if (!d) - d = fiobj_str_new_buf(raw.len + 32); - fiobj_str_write_html_escape(d, raw.buf, raw.len); - return (void *)d; -} -/* callback should return a new context pointer with the value of `name`. */ -FIO_SFUNC void *fiobj___mustache_get_var(void *ctx, fio_buf_info_s name) { - if (!ctx) - return NULL; - if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_HASH)) - return NULL; - return fiobj_hash_get2((FIOBJ)ctx, name.buf, name.len); -} -/* if context is an Array, should return its length. */ -FIO_SFUNC size_t fiobj___mustache_array_length(void *ctx) { - if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_ARRAY)) - return 0; - return fiobj_array_count((FIOBJ)ctx); +/** Called when the permessage-deflate extension requires decompression. */ +FIO_SFUNC fio_buf_info_s fio_websocket_decompress(void *udata, + fio_buf_info_s msg) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + FIO_LOG_ERROR("WebSocket permessage-deflate not yet implemented!"); + (void)c; + return msg; } -/* if context is an Array, should return a context pointer @ index. */ -FIO_SFUNC void *fiobj___mustache_get_var_index(void *ctx, size_t index) { - if (!FIOBJ_TYPE_IS((FIOBJ)ctx, FIOBJ_T_ARRAY) || index > 0xFFFFFFFFUL) - return NULL; - return fiobj_array_get((FIOBJ)ctx, (uint32_t)index); + +/** Called when a `ping` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_ping(void *udata, fio_buf_info_s msg) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + if (msg.len < 248) { + char buf[256]; + size_t len = + (c->is_client + ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(buf, msg.buf, msg.len, 0x0A, 1, 1, 0); + fio_io_write2(c->io, .buf = buf, .len = len, .copy = 1); + } else { + char *pong = fio_bstr_reserve(NULL, msg.len + 11); + size_t len = (c->is_client ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(pong, + msg.buf, + msg.len, + 0x0A, + 1, + 1, + 0); + pong = fio_bstr_len_set(pong, len); + fio_io_write2(c->io, + .buf = pong, + .len = len, + .dealloc = (void (*)(void *))fio_bstr_free); + } + fio_bstr_free(c->state.ws.msg); + c->state.ws.msg = NULL; } -/* should return the String value of context `var` as a `fio_buf_info_s`. */ -FIO_SFUNC fio_buf_info_s fiobj___mustache_var2str(void *var) { - fio_buf_info_s r = {0}; - if (!var || var == fiobj_null()) - return r; - fio_str_info_s tmp = fiobj2cstr((FIOBJ)var); - r = FIO_STR2BUF_INFO(tmp); - return r; + +/** Called when a `pong` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_pong(void *udata, fio_buf_info_s msg) { +#if (DEBUG - 1 + 1) || (FIO_WEBSOCKET_STATS - 1 + 1) + { + char *pos = msg.buf; + static uint64_t longest = 0; + uint64_t ping_time = fio_io_last_tick() - fio_atol16u(&pos); + if (ping_time < (1 << 16) && longest < ping_time) { + longest = ping_time; + FIO_LOG_INFO("WebSocket longest ping round-trip detected as: %zums", + (size_t)ping_time); + } + } +#endif + FIO_LOG_DDEBUG2("Pong (%zu): %s", msg.len, msg.buf); + (void)msg; /* do nothing */ + fio___http_connection_s *c = (fio___http_connection_s *)udata; + fio_bstr_free(c->state.ws.msg); + c->state.ws.msg = NULL; } -/* should return non-zero if the context pointer refers to a valid value. */ -FIO_SFUNC int fiobj___mustache_var_is_truthful(void *v) { - return v && (FIOBJ)v != fiobj_null() && (FIOBJ)v != fiobj_false() && - (!FIOBJ_TYPE_IS((FIOBJ)v, FIOBJ_T_ARRAY) || - fiobj_array_count((FIOBJ)v)); + +/** Called when a `close` message was received. */ +FIO_SFUNC void fio_websocket_on_protocol_close(void *udata, + fio_buf_info_s msg) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + char buf[32]; + size_t len = fio_websocket_server_wrap(buf, NULL, 0, 0x08, 1, 1, 0); + fio_io_write(c->io, buf, len); + if (msg.len > 1) + c->state.ws.code = fio_buf2u16_be(msg.buf); + fio_io_close(c->io); + if (msg.len > 2) + FIO_LOG_DDEBUG2("WebSocket %p closed with error message: %s", + c->io, + msg.buf + 2); + (void)msg; } /* ***************************************************************************** - - -FIOBJ - Externed Implementation - - +WebSocket Protocol ***************************************************************************** */ -#if defined(FIO_EXTERN_COMPLETE) || !defined(FIO_EXTERN) -/* ***************************************************************************** -FIOBJ Basic Object vtable -***************************************************************************** */ +FIO_SFUNC int fio___websocket_process_data(fio_io_s *io, + fio___http_connection_s *c) { + (void)io, (void)c; + size_t consumed = fio_websocket_parse(&c->state.ws.parser, + FIO_BUF_INFO2(c->buf, c->len), + (void *)c); + if (!consumed) + return -1; + if (consumed == FIO_WEBSOCKET_PARSER_ERROR) + goto ws_error; + c->len -= consumed; + if (c->len) + FIO_MEMMOVE(c->buf, c->buf + consumed, c->len); + if (c->suspend) + return -1; + return 0; -FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___OBJECT_CLASS_VTBL = { - .type_id = 99, /* type IDs below 100 are reserved. */ -}; +ws_error: + FIO_LOG_DDEBUG2("WebSocket protocol error?"); + fio_websocket_on_protocol_close((void *)c, ((fio_buf_info_s){0})); + return -1; +} -/* ***************************************************************************** -FIOBJ Complex Iteration -***************************************************************************** */ -typedef struct { - FIOBJ obj; - size_t pos; -} fiobj___stack_element_s; +/** Called when a data is available. */ +FIO_SFUNC void fio___websocket_on_data(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + size_t r; + for (;;) { + if (c->capa == c->len) + return; + if (!(r = fio_io_read(io, c->buf + c->len, c->capa - c->len))) + return; + c->len += r; + if (fio___websocket_process_data(io, c)) + return; + } +} -#define FIO_ARRAY_NAME fiobj___active_stack -#define FIO_ARRAY_TYPE fiobj___stack_element_s -#define FIO_ARRAY_COPY(dest, src) \ - do { \ - (dest).obj = fiobj_dup((src).obj); \ - (dest).pos = (src).pos; \ - } while (0) -#define FIO_ARRAY_TYPE_CMP(a, b) (a).obj == (b).obj -#define FIO_ARRAY_DESTROY(o) fiobj_free(o) -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +FIO_SFUNC void fio___websocket_on_ready(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_http_s *h = c->h; + if (!h) + return; + c->state.ws.on_ready(h); +} -#define FIO_ARRAY_TYPE_CMP(a, b) (a).obj == (b).obj -#define FIO_ARRAY_NAME fiobj___stack -#define FIO_ARRAY_TYPE fiobj___stack_element_s -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +FIO_SFUNC void fio___websocket_on_timeout(fio_io_s *io) { + char buf[32]; + char tm[20] = "0x00000000000000000"; + fio_ltoa16u(tm + 2, fio_io_last_tick(), 16); + size_t len = fio_websocket_server_wrap(buf, tm, 18, 0x09, 1, 1, 0); + fio_io_write(io, buf, len); +} -typedef struct { - int (*task)(fiobj_each_s *info); - void *arg; - FIOBJ next; - size_t count; - fiobj___stack_s stack; - uint32_t end; - uint8_t stop; -} fiobj_____each2_data_s; +FIO_SFUNC void fio___websocket_on_shutdown(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + c->settings->on_shutdown(c->h); + fio_websocket_on_protocol_close(c, ((fio_buf_info_s){0})); +} -FIO_SFUNC uint32_t fiobj____each2_element_count(FIOBJ o) { - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_PRIMITIVE: /* fall through */ - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_STRING: /* fall through */ - case FIOBJ_T_FLOAT: return 0; - case FIOBJ_T_ARRAY: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); - case FIOBJ_T_HASH: - return FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); - case FIOBJ_T_OTHER: /* fall through */ - return (*fiobj_object_metadata(o))->count(o); - } - return 0; +/** Called when an IO is attached to a protocol. */ +FIO_SFUNC void fio___websocket_on_attach(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_http_s *h = c->h; + c->state.ws = (struct fio___http_connection_ws_s){ + .on_message = c->settings->on_message, + .on_ready = c->settings->on_ready, + .parser = {.must_mask = !c->is_client}, + }; + c->settings->on_open(h); + fio___websocket_process_data(io, c); } -FIO_SFUNC int fiobj____each2_wrapper_task(fiobj_each_s *e) { - fiobj_____each2_data_s *d = (fiobj_____each2_data_s *)e->udata; - e->task = d->task; - e->udata = d->arg; - d->stop = (d->task(e) == -1); - d->task = e->task; - d->arg = e->udata; - e->task = fiobj____each2_wrapper_task; - e->udata = d; - ++d->count; - if (d->stop) - return -1; - uint32_t c = fiobj____each2_element_count(e->value); - if (c) { - d->next = e->value; - d->end = c; - return -1; + +/** Called after the connection was closed, and pending tasks completed. */ +FIO_SFUNC void fio___websocket_on_close(void *buf, void *udata) { + FIO_LOG_DDEBUG2("(%d) WebSocket connection closed for %p", + (int)fio_thread_getpid(), + udata); + fio___http_connection_s *c = (fio___http_connection_s *)udata; + c->io = NULL; + fio_bstr_free(c->state.ws.msg); + if (c->h) { + fio_http_status_set(c->h, (size_t)(c->state.ws.code)); + c->settings->on_close(c->h); + c->settings->on_finish(c->h); + fio_http_free(c->h); } - return 0; + fio___http_connection_free(c); + (void)buf; } /** - * Performs a task for the object itself and each element held by the FIOBJ - * object or any of it's elements (a deep task). - * - * The order of performance is by order of appearance, as if all nesting levels - * were flattened. - * - * If `task` returns -1, the `each` loop will break (stop). + * Sets a specific on_message callback for this connection. * - * Returns the number of elements processed. + * Returns -1 on error (i.e., upgrade still in negotiation). */ -SFUNC uint32_t fiobj_each2(FIOBJ o, int (*task)(fiobj_each_s *), void *udata) { - /* TODO - move to recursion with nesting limiter? */ - fiobj_____each2_data_s d = { - .task = task, - .arg = udata, - .next = FIOBJ_INVALID, - .stack = FIO_ARRAY_INIT, - }; - struct FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), each_s) e_tmp = { - - .parent = FIOBJ_INVALID, - .task = (int (*)(FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - each_s) *))fiobj____each2_wrapper_task, - .udata = &d, - .value = o, - }; - fiobj___stack_element_s i = {.obj = o, .pos = 0}; - uint32_t end = fiobj____each2_element_count(o); - fiobj____each2_wrapper_task((fiobj_each_s *)&e_tmp); - while (!d.stop && i.obj && i.pos < end) { - i.pos = - fiobj_each1(i.obj, fiobj____each2_wrapper_task, &d, (uint32_t)i.pos); - if (d.next != FIOBJ_INVALID) { - if (fiobj___stack_count(&d.stack) + 1 > FIOBJ_MAX_NESTING) { - FIO_LOG_ERROR("FIOBJ nesting level too deep (%u)." - "`fiobj_each2` stopping loop early.", - (unsigned int)fiobj___stack_count(&d.stack)); - d.stop = 1; - continue; - } - fiobj___stack_push(&d.stack, i); - i.pos = 0; - i.obj = d.next; - d.next = FIOBJ_INVALID; - end = d.end; - } else { - /* re-collect end position to accommodate for changes */ - end = fiobj____each2_element_count(i.obj); - } - while (i.pos >= end && fiobj___stack_count(&d.stack)) { - fiobj___stack_pop(&d.stack, &i); - end = fiobj____each2_element_count(i.obj); - } - }; - fiobj___stack_destroy(&d.stack); - return (uint32_t)d.count; +SFUNC int fio_http_on_message_set(fio_http_s *h, + void (*on_message)(fio_http_s *, + fio_buf_info_s, + uint8_t)) { + if (!h) + return -1; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c) + return -1; + if (!on_message) + on_message = c->settings->on_message; + c->state.ws.on_message = on_message; + return 0; } /* ***************************************************************************** -FIOBJ Hash / Array / Other (enumerable) Equality test. +WebSocket Writing / Subscription Helpers ***************************************************************************** */ -/** Internal: compares two nestable objects. */ -SFUNC unsigned char fiobj___test_eq_nested(FIOBJ restrict a, - FIOBJ restrict b, - size_t nesting) { - if (a == b) - return 1; - if (FIOBJ_TYPE_CLASS(a) != FIOBJ_TYPE_CLASS(b)) - return 0; - if (fiobj____each2_element_count(a) != fiobj____each2_element_count(b)) - return 0; - if (nesting >= FIOBJ_MAX_NESTING) - return 0; +FIO_IFUNC void fio___http_websocket_subscribe_imp(fio_msg_s *msg, + uint8_t is_text) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(msg->io); + if (!c) + return; + fio_http_websocket_write(c->h, msg->message.buf, msg->message.len, is_text); +} - ++nesting; +/** Optional WebSocket subscription callback - all messages are UTF-8 valid. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT(fio_msg_s *msg) { + fio___http_websocket_subscribe_imp(msg, 1); +} +/** Optional WebSocket subscription callback - messages may be non-UTF-8. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY(fio_msg_s *msg) { + fio___http_websocket_subscribe_imp(msg, 0); +} - switch (FIOBJ_TYPE_CLASS(a)) { - case FIOBJ_T_PRIMITIVE: /* fall through */ - case FIOBJ_T_NUMBER: /* fall through */ - case FIOBJ_T_FLOAT: return a == b; - case FIOBJ_T_STRING: - return FIO_NAME_BL(FIO_NAME(fiobj, FIOBJ___NAME_STRING), eq)(a, b); +/** Optional WebSocket subscription callback. */ +SFUNC void FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT(fio_msg_s *msg) { + ((msg->message.len < FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT) && + (fio_string_utf8_valid( + FIO_STR_INFO2((char *)msg->message.buf, msg->message.len))) + ? FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_TEXT + : FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT_BINARY)(msg); +} - case FIOBJ_T_ARRAY: - if (!fiobj____each2_element_count(a)) - return 1; - /* test each array member with matching index */ - { - const size_t count = fiobj____each2_element_count(a); - for (size_t i = 0; i < count; ++i) { - if (!fiobj___test_eq_nested( - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(a, - (int32_t)i), - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(b, - (int32_t)i), - nesting)) - return 0; - } - } - return 1; +/* ***************************************************************************** +EventSource (SSE) Helpers - HTTP Upgraded Connections +***************************************************************************** */ - case FIOBJ_T_HASH: - if (!fiobj____each2_element_count(a)) - return 1; - FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), a, pos) { - FIOBJ val = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(b, pos.key); - if (!fiobj___test_eq_nested(val, pos.value, nesting)) - return 0; +void fio_http_sse_write___(void); /* IDE Marker */ +/** Writes an SSE message (UTF-8). Fails if connection wasn't upgraded yet. */ +SFUNC int fio_http_sse_write FIO_NOOP(fio_http_s *h, + fio_http_sse_write_args_s args) { + if (!args.data.len || !h || !fio_http_is_sse(h)) + return -1; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c || !c->io) + return -1; + char *payload = + fio_bstr_reserve(NULL, args.id.len + args.event.len + args.data.len + 22); + if (args.id.len) + payload = fio_bstr_write2(payload, + FIO_STRING_WRITE_STR2("id:", 3), + FIO_STRING_WRITE_STR2(args.id.buf, args.id.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + if (args.event.len) + payload = + fio_bstr_write2(payload, + FIO_STRING_WRITE_STR2("event:", 6), + FIO_STRING_WRITE_STR2(args.event.buf, args.event.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + { /* separate lines (add "data:" at beginning of each new line) */ + char *pos; + while (args.data.len && + (pos = (char *)FIO_MEMCHR(args.data.buf, '\n', args.data.len))) { + const size_t len = (pos + 1) - args.data.buf; + pos -= (pos[-1] == '\r'); + payload = fio_bstr_write2( + payload, + FIO_STRING_WRITE_STR2("data:", 5), + FIO_STRING_WRITE_STR2(args.data.buf, (size_t)(pos - args.data.buf)), + FIO_STRING_WRITE_STR2("\r\n", 2)); + args.data.buf += len; + args.data.len -= len; } - return 1; - case FIOBJ_T_OTHER: - if (!fiobj____each2_element_count(a) && - (*fiobj_object_metadata(a))->is_eq(a, b)) - return 1; - /* TODO: iterate through objects and test equality within nesting */ - return (*fiobj_object_metadata(a))->is_eq(a, b); - return 1; } + /* write reminder */ + if (args.data.len) + payload = + fio_bstr_write2(payload, + FIO_STRING_WRITE_STR2("data:", 5), + FIO_STRING_WRITE_STR2(args.data.buf, args.data.len), + FIO_STRING_WRITE_STR2("\r\n", 2)); + /* event ends on empty line */ + payload = fio_bstr_write(payload, "\r\n", 2); + fio_io_write2(c->io, + .buf = payload, + .len = fio_bstr_len(payload), + .dealloc = (void (*)(void *))fio_bstr_free); return 0; } -/* ***************************************************************************** -FIOBJ general helpers -***************************************************************************** */ - -FIO_SFUNC uint32_t fiobj___count_noop(FIOBJ o) { - return 0; - (void)o; +/** Optional EventSource subscription callback - messages MUST be UTF-8. */ +SFUNC void FIO_HTTP_SSE_SUBSCRIBE_DIRECT(fio_msg_s *msg) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(msg->io); + if (!c) + return; + FIO_STR_INFO_TMP_VAR(id_str, 64); + fio_string_write_hex(&id_str, NULL, msg->id); + fio_http_sse_write(c->h, + .id = FIO_STR2BUF_INFO(id_str), + .event = FIO_STR2BUF_INFO(msg->channel), + .data = FIO_STR2BUF_INFO(msg->message)); } /* ***************************************************************************** -FIOBJ Integers (bigger numbers) +WebSocket Writing / Subscription Helpers ***************************************************************************** */ -SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), - cstr)(FIOBJ i) { - static char buf[32 * 128]; - static uint8_t pos = 0; - size_t at = fio_atomic_add(&pos, 1); - fio_str_info_s s = {.buf = buf + ((at & 127) << 5), .capa = 31}; - fio_string_write_i(&s, - NULL, - FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(i)); - return s; +SFUNC int fio_http_websocket_write(fio_http_s *h, + const void *buf, + size_t len, + uint8_t is_text) { + if (!h || !fio_http_is_websocket(h)) + return -1; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (!c) + return -1; + is_text = (!!is_text); + is_text |= (!is_text) << 1; + uint8_t rsv = 0; + if (len < 512) { /* fast-path: no allocation, no compression */ + char tmp[520]; + size_t wlen = + (c->is_client + ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(tmp, buf, len, is_text, 1, 1, rsv); + fio_io_write2(c->io, .buf = tmp, .len = wlen, .copy = 1); + return 0; + } +#if HAVE_ZLIB /* TODO: compress? */ + // if(c->state.ws.deflate) ; +#endif + char *payload = + fio_bstr_reserve(NULL, + fio_websocket_wrapped_len(len) + (c->is_client << 2)); + payload = fio_bstr_len_set( + payload, + (c->is_client + ? fio_websocket_client_wrap + : fio_websocket_server_wrap)(payload, buf, len, is_text, 1, 1, rsv)); + fio_io_write2(c->io, + .buf = payload, + .len = fio_bstr_len(payload), + .dealloc = (void (*)(void *))fio_bstr_free); + return 0 - !fio_io_is_open(c->io); } -FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___NUMBER_CLASS_VTBL = { - /** - * MUST return a unique number to identify object type. - * - * Numbers (IDs) under 100 are reserved. - */ - .type_id = FIOBJ_T_NUMBER, - /** Test for equality between two objects with the same `type_id` */ - .is_eq = FIO_NAME_BL(fiobj___num, eq), - /** Converts an object to a String */ - .to_s = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), cstr), - /** Converts and object to an integer */ - .to_i = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i), - /** Converts and object to a float */ - .to_f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), f), - /** Returns the number of exposed elements held by the object, if any. */ - .count = fiobj___count_noop, - /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ - .each1 = NULL, - /** Deallocates the element (but NOT any of it's exposed elements). */ - .free2 = fiobj___bignum_free2, -}; - /* ***************************************************************************** -FIOBJ Floats (bigger / smaller doubles) +WebSocket Controller ***************************************************************************** */ -FIO_SFUNC unsigned char FIO_NAME_BL(fiobj___float, eq)(FIOBJ restrict a, - FIOBJ restrict b) { - unsigned char r = 0; - union { - uint64_t u; - double f; - } da, db; - da.f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(a); - db.f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(b); - /* regular equality? */ - r |= da.f == db.f; - /* test for small rounding errors (4 bit difference) on normalize floats */ - r |= !((da.u ^ db.u) & UINT64_C(0xFFFFFFFFFFFFFFF0)) && - (da.u & UINT64_C(0x7FF0000000000000)); - /* test for small ULP: */ - r |= (((da.u > db.u) ? da.u - db.u : db.u - da.u) < 2); - /* test for +-0 */ - r |= !((da.u | db.u) & UINT64_C(0x7FFFFFFFFFFFFFFF)); - return r; -} - -SFUNC fio_str_info_s FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), - cstr)(FIOBJ i) { - static char buf[32 * 128]; - static uint8_t pos = 0; - size_t at = fio_atomic_add(&pos, 1); - char *tmp = buf + ((at & 127) << 5); - size_t len = - fio_ftoa(tmp, FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(i), 10); - tmp[len] = 0; - return FIO_STR_INFO2(tmp, len); +/* Called by the HTTP handle for each body chunk (or to finish a response). */ +FIO_SFUNC void fio___http_controller_ws_write_body(fio_http_s *h, + fio_http_write_args_s args) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (args.buf && args.len < FIO_HTTP_WEBSOCKET_WRITE_VALIDITY_TEST_LIMIT) { + unsigned char is_text = + !!fio_string_utf8_valid(FIO_STR_INFO2((char *)args.buf, args.len)); + fio_http_websocket_write(h, (void *)args.buf, args.len, is_text); + if (args.dealloc) + args.dealloc((void *)args.buf); + return; + } + char header[16]; + ((uint8_t *)header)[0] = 0 | 2 | 128; + if (args.len < 126) { + ((uint8_t *)header)[1] = args.len; + fio_io_write(c->io, header, 2); + } else if (args.len < (1UL << 16)) { + /* head is 4 bytes */ + ((uint8_t *)header)[1] = 126 | ((!!c->is_client) << 7); + fio_u2buf16_be(((uint8_t *)header + 2), args.len); + fio_io_write(c->io, header, 4); + } else { + /* Really Long Message */ + ((uint8_t *)header)[1] = 127 | ((!!c->is_client) << 7); + fio_u2buf64_be(((uint8_t *)header + 2), args.len); + fio_io_write(c->io, header, 10); + } + fio_io_write2(c->io, + .buf = (void *)args.buf, + .fd = args.fd, + .len = args.len, + .offset = args.offset, + .dealloc = args.dealloc, + .copy = (uint8_t)args.copy); } -FIOBJ_EXTERN_OBJ_IMP const FIOBJ_class_vtable_s FIOBJ___FLOAT_CLASS_VTBL = { - /** - * MUST return a unique number to identify object type. - * - * Numbers (IDs) under 100 are reserved. - */ - .type_id = FIOBJ_T_FLOAT, - /** Test for equality between two objects with the same `type_id` */ - .is_eq = FIO_NAME_BL(fiobj___float, eq), - /** Converts an object to a String */ - .to_s = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), cstr), - /** Converts and object to an integer */ - .to_i = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), i), - /** Converts and object to a float */ - .to_f = FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f), - /** Returns the number of exposed elements held by the object, if any. */ - .count = fiobj___count_noop, - /** Iterates the exposed elements held by the object. See `fiobj_each1`. */ - .each1 = NULL, - /** Deallocates the element (but NOT any of it's exposed elements). */ - .free2 = fiobj___bigfloat_free2, -}; - /* ***************************************************************************** -FIOBJ JSON support - output +EventSource / SSE Protocol (TODO!) ***************************************************************************** */ -FIO_IFUNC void fiobj___json_format_internal_beauty_pad(FIOBJ json, - size_t level) { - size_t pos = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), len)(json); - fio_str_info_s tmp = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), - resize)(json, (level << 1) + pos + 2); - tmp.buf[pos++] = '\r'; - tmp.buf[pos++] = '\n'; - for (size_t i = 0; i < level; ++i) { - tmp.buf[pos++] = ' '; - tmp.buf[pos++] = ' '; - } -} - -SFUNC void fiobj___json_format_internal__(fiobj___json_format_internal__s *args, - FIOBJ o) { - switch (FIOBJ_TYPE(o)) { - case FIOBJ_T_TRUE: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "true", 4); - return; - case FIOBJ_T_FALSE: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "false", 5); - return; - case FIOBJ_T_NULL: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "null", 4); - return; - case FIOBJ_T_NUMBER: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_i) - (args->json, FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), i)(o)); - return; - case FIOBJ_T_FLOAT: { - char tmp_buf[256]; - size_t len = fio_ftoa(tmp_buf, - FIO_NAME2(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), f)(o), - 10); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, tmp_buf, len); - return; - } - case FIOBJ_T_STRING: /* fall through */ - default: { - fio_str_info_s info = FIO_NAME2(fiobj, cstr)(o); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "\"", 1); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) - (args->json, info.buf, info.len); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "\"", 1); - return; - } - case FIOBJ_T_ARRAY: - if (!FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o)) - goto empty_array; - if (args->level == FIOBJ_MAX_NESTING) - goto err_array_nesting; - { - ++args->level; - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "[", 1); - const uint32_t len = - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), count)(o); - if (args->beautify) { - fiobj___json_format_internal_beauty_pad(args->json, args->level); - } - fiobj___json_format_internal__( - args, - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, 0)); - if (args->beautify) { - for (size_t i = 1; i < len; ++i) { - FIOBJ child = - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, i); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, ",", 1); - fiobj___json_format_internal_beauty_pad(args->json, args->level); - fiobj___json_format_internal__(args, child); - } - } else { - for (size_t i = 1; i < len; ++i) { - FIOBJ child = - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), get)(o, i); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, ",", 1); - fiobj___json_format_internal__(args, child); - } - } - --args->level; - if (args->beautify) { - fiobj___json_format_internal_beauty_pad(args->json, args->level); +FIO_SFUNC void fio___sse_consume_data(fio___http_connection_s *c) { + /* TODO: Fix Me! parse and process SSE data */ + FIO_LOG_DEBUG2("SSE data processing:\n%.*s", (int)c->len, c->buf); + struct fio___http_connection_sse_s *sse = &c->state.sse; + const char *next_line = c->buf; + const char *stop = c->buf + c->len; + for (; next_line < stop;) { + char *line = (char *)next_line; + const char *eol = + (const char *)FIO_MEMCHR(next_line, '\n', stop - next_line); + if (!eol) + break; + next_line = eol + 1; + eol -= (eol > c->buf && eol[-1] == '\n'); + eol -= (eol > c->buf && eol[-1] == '\r'); + if (eol == line) { /* empty line, end of input? */ + if (sse->data || sse->event.buf || sse->id.buf) { + sse->on_message(c->h, sse->id, sse->event, fio_bstr_buf(sse->data)); + fio_bstr_free(sse->data); + sse->data = NULL; + sse->event = sse->id = FIO_BUF_INFO0; } - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write)(args->json, "]", 1); + continue; } - return; - case FIOBJ_T_HASH: - if (!FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o)) - goto empty_hash; - if (args->level == FIOBJ_MAX_NESTING) - goto err_hash_nesting; - { - size_t i = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), count)(o); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "{", 1); - ++args->level; - FIO_MAP_EACH(FIO_NAME(fiobj, FIOBJ___NAME_HASH), o, couplet) { - if (args->beautify) { - fiobj___json_format_internal_beauty_pad(args->json, args->level); - } - fio_str_info_s info = FIO_NAME2(fiobj, cstr)(couplet.key); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "\"", 1); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_escape) - (args->json, info.buf, info.len); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "\":", 2); - fiobj___json_format_internal__(args, couplet.value); - if (--i) - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, ",", 1); - } - --args->level; - if (args->beautify) { - fiobj___json_format_internal_beauty_pad(args->json, args->level); + if (line[0] == ':') /* comment */ + continue; + const size_t line_len = (size_t)(eol - line); + if (line_len > 2 && line[2] == ':') { /* id */ + const char *start = line + 3; + start += (start[0] == ' ' || start[0] == '\t'); + if ((line[0] |= 32) == 'i' && (line[1] |= 32) == 'd') + sse->id = FIO_BUF_INFO2((char *)start, (size_t)(eol - start)); + + } else if (line_len > 4 && line[4] == ':') { /* data */ + const char *start = line + 5; + start += (start[0] == ' ' || start[0] == '\t'); + if ((fio_buf2u32u(line) | 0x20202020U) == fio_buf2u32u("data")) { + if (fio_bstr_len(sse->data) + (size_t)(eol - start) > + c->settings->ws_max_msg_size) + goto breach; + sse->data = fio_bstr_write2( + sse->data, + FIO_STRING_WRITE_STR2("\r\n", ((size_t) !!sse->data << 1)), + FIO_STRING_WRITE_STR2(start, (size_t)(eol - start))); } - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "}", 1); - } - return; + + } else if (line_len > 5 && line[5] == ':') { /* event */ + const char *start = line + 3; + start += (start[0] == ' ' || start[0] == '\t'); + if ((line[0] |= 32) == 'e' && + (fio_buf2u32u(line + 1) | 0x20202020U) == fio_buf2u32u("vent")) + sse->event = FIO_BUF_INFO2((char *)start, (size_t)(eol - start)); + + } else if (!FIO_MEMCHR(line, ':', line_len)) + goto error; } -empty_hash: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "{}", 2); - return; -empty_array: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "[]", 2); + FIO_ASSERT(next_line <= stop, "overflow on next line read"); + if (next_line > stop) + next_line = stop; + c->len -= next_line - c->buf; + if (c->len) + FIO_MEMMOVE(c->buf, next_line, c->len); return; -err_array_nesting: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "[ ]", 3); - goto log_nesting_error; -err_hash_nesting: - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write) - (args->json, "{ }", 3); -log_nesting_error: - FIO_LOG_ERROR("JSON formatting truncated - nesting level too deep."); -} -/* ***************************************************************************** -FIOBJ JSON parsing -***************************************************************************** */ -#if 1 +error: + FIO_LOG_ERROR("SSE incoming data malformed!"); + FIO_LOG_DEBUG2("data dump:\n%.*s", (int)c->len, c->buf); + fio_io_close(c->io); + return; -FIO_SFUNC void *fiobj___json_on_null(void) { - return FIO_NAME(fiobj, FIOBJ___NAME_NULL)(); -} -FIO_SFUNC void *fiobj___json_on_true(void) { return fiobj_true(); } -FIO_SFUNC void *fiobj___json_on_false(void) { return fiobj_false(); } -FIO_SFUNC void *fiobj___json_on_number(int64_t i) { - return FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_NUMBER, new))(i); -} -FIO_SFUNC void *fiobj___json_on_float(double f) { - return FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_FLOAT, new))(f); -} -FIO_SFUNC void *fiobj___json_on_string(const void *start, size_t len) { - FIOBJ str = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, new))(); - FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, write_unescape)) - (str, (const char *)start, len); - return str; -} -FIO_SFUNC void *fiobj___json_on_string_simple(const void *start, size_t len) { - FIOBJ str = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, new))(); - FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_STRING, write)) - (str, (const char *)start, len); - return str; -} -FIO_SFUNC void *fiobj___json_on_map(void *ctx, void *at) { - FIOBJ m = FIOBJ_INVALID; - if (ctx && at && FIOBJ_TYPE_CLASS(ctx) == FIOBJ_T_HASH) - m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, get))((FIOBJ)ctx, - (FIOBJ)at); - if (!m || m == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(m) != FIOBJ_T_ARRAY) - m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, new))(); - return m; -} -FIO_SFUNC void *fiobj___json_on_array(void *ctx, void *at) { - FIOBJ m = FIOBJ_INVALID; - if (ctx && at && FIOBJ_TYPE_CLASS(ctx) == FIOBJ_T_HASH) - m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, get))((FIOBJ)ctx, - (FIOBJ)at); - if (!m || m == FIOBJ_INVALID || FIOBJ_TYPE_CLASS(m) != FIOBJ_T_ARRAY) - m = FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_ARRAY, new))(); - return m; -} -FIO_SFUNC int fiobj___json_map_push(void *ctx, void *key, void *value) { - FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_HASH, set)) - ((FIOBJ)ctx, (FIOBJ)key, (FIOBJ)value, NULL); - fiobj_free((FIOBJ)key); - return 0; -} -FIO_SFUNC int fiobj___json_array_push(void *ctx, void *value) { - FIO_NAME(fiobj, FIO_NAME(FIOBJ___NAME_ARRAY, push))((FIOBJ)ctx, (FIOBJ)value); - return 0; -} -FIO_SFUNC void fiobj___json_free_unused_object(void *ctx) { - fiobj_free((FIOBJ)ctx); -} -FIO_SFUNC void *fiobj___json_on_error(void *ctx) { - fiobj_free((FIOBJ)ctx); - return FIOBJ_INVALID; +breach: + FIO_LOG_SECURITY("SSE incoming data payload too large!"); + fio_io_close(c->io); } -static fio_json_parser_callbacks_s FIOBJ_JSON_PARSER_CALLBACKS = { - .on_null = fiobj___json_on_null, - .on_true = fiobj___json_on_true, - .on_false = fiobj___json_on_false, - .on_number = fiobj___json_on_number, - .on_float = fiobj___json_on_float, - .on_string = fiobj___json_on_string, - .on_string_simple = fiobj___json_on_string_simple, - .on_map = fiobj___json_on_map, - .on_array = fiobj___json_on_array, - .map_push = fiobj___json_map_push, - .array_push = fiobj___json_array_push, - .free_unused_object = fiobj___json_free_unused_object, - .on_error = fiobj___json_on_error, -}; -/** Returns a JSON valid FIOBJ String, representing the object. */ -SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed_p) { - fio_json_result_s result = - fio_json_parse(&FIOBJ_JSON_PARSER_CALLBACKS, str.buf, str.len); - if (consumed_p) - *consumed_p = result.stop_pos; - if (result.err) { -#ifdef DEBUG - FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, (FIOBJ)result.ctx, 0); - FIO_LOG_DEBUG("JSON data being deleted:\n%s", - FIO_NAME2(fiobj, cstr)(s).buf); - fiobj_free(s); -#endif - fiobj_free((FIOBJ)result.ctx); - result.ctx = FIOBJ_INVALID; +/** Called when a data is available. */ +FIO_SFUNC void fio___sse_on_data(fio_io_s *io) { + FIO_LOG_DDEBUG2("(%d) Reading SSE data from socket", fio_io_pid()); + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + size_t r; + for (;;) { + if (c->len + 2 > c->capa) + goto error; + if (!(r = fio_io_read(io, c->buf + c->len, c->capa - c->len))) + return; + c->len += r; + fio___sse_consume_data(c); } - return (FIOBJ)result.ctx; +error: + FIO_LOG_ERROR("Incoming SSE data too long (HTTP line limit set at %zu)!", + c->capa); + fio_io_close(io); } -/** - * Updates a Hash using JSON data. - * - * Parsing errors and non-dictionary object JSON data are silently ignored, - * attempting to update the Hash as much as possible before any errors - * encountered. - * - * Conflicting Hash data is overwritten (preferring the new over the old). - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - update_json)(FIOBJ hash, fio_str_info_s str) { - /* TODO! FIXME! this will leak memory on NULL hash and break on Arrays */ - fio_json_result_s result = fio_json_parse_update(&FIOBJ_JSON_PARSER_CALLBACKS, - hash, - str.buf, - str.len); - // if (consumed_p) - // *consumed_p = result.stop_pos; - if (result.err) { -#ifdef DEBUG - FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, (FIOBJ)result.ctx, 0); - FIO_LOG_DEBUG("JSON data being deleted:\n%s", - FIO_NAME2(fiobj, cstr)(s).buf); - fiobj_free(s); -#endif - fiobj_free((FIOBJ)result.ctx); - result.ctx = FIOBJ_INVALID; - } - return result.stop_pos; - FIO_LOG_ERROR("fiobj_hash_update_json note yet implemented"); - return 0; - (void)str; +/** Called when an IO is attached to a protocol. */ +static void fio___sse_on_attach(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + fio_http_s *h = c->h; + c->state.sse = (struct fio___http_connection_sse_s){ + .on_message = c->settings->on_eventsource, + .on_ready = c->settings->on_ready, + }; + c->settings->on_open(h); + FIO_LOG_DDEBUG2("(%d) SSE attached; buffer length (unread): %zu", + fio_io_pid(), + c->len); + if (c->len && c->is_client) + fio___sse_consume_data(c); } -#else -#define FIO_JSON -#define FIO___RECURSIVE_INCLUDE 1 -#include FIO_INCLUDE_FILE -#undef FIO___RECURSIVE_INCLUDE +FIO_SFUNC void fio___sse_on_timeout(fio_io_s *io) { + char buf[32] = ":ping 0x0000000000000000\r\n\r\n"; + fio_ltoa16u(buf + 8, fio_io_last_tick(), 16); + buf[24] = '\r'; /* overwrite written NUL character */ + fio_io_write(io, buf, 28); +} -/* FIOBJ JSON parser */ -typedef struct { - fio_json_parser_s p; - size_t so; /* stack offset */ - FIOBJ key; - FIOBJ top; - FIOBJ target; - FIOBJ stack[JSON_MAX_DEPTH + 1]; -} fiobj_json_parser_s; +FIO_SFUNC void fio___sse_on_shutdown(fio_io_s *io) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_io_udata(io); + c->settings->on_shutdown(c->h); + // fio_websocket_on_protocol_close(c, ((fio_buf_info_s){0})); +} -static inline void fiobj_json_add2parser(fiobj_json_parser_s *p, FIOBJ o) { - if (p->top) { - if (FIOBJ_TYPE_CLASS(p->top) == FIOBJ_T_HASH) { - if (p->key) { - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), set) - (p->top, p->key, o, NULL); - fiobj_free(p->key); - p->key = FIOBJ_INVALID; - } else { - p->key = o; - } - } else { - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), push)(p->top, o); - } - } else { - p->top = o; +/** Called after the connection was closed, and pending tasks completed. */ +FIO_SFUNC void fio___sse_on_close(void *buf, void *udata) { + fio___http_connection_s *c = (fio___http_connection_s *)udata; + FIO_LOG_DDEBUG2("(%d) SSE connection closed for %p", fio_io_pid(), c->io); + c->io = NULL; + fio_bstr_free(c->state.sse.data); + if (c->h) { + c->settings->on_close(c->h); + c->settings->on_finish(c->h); + fio_http_free(c->h); } + fio___http_connection_free(c); + (void)buf; } -/** a NULL object was detected */ -static inline void fio_json_on_null(fio_json_parser_s *p) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, - FIO_NAME(fiobj, FIOBJ___NAME_NULL)()); +/* ***************************************************************************** +EventSource / SSE Controller (TODO!) +***************************************************************************** */ + +/* called by the HTTP handle for each body chunk (or to finish a response. */ +FIO_SFUNC void fio___http_controller_sse_write_body( + fio_http_s *h, + fio_http_write_args_s args) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (args.buf && args.len) { + fio_http_sse_write(c->h, .data = FIO_BUF_INFO2((char *)args.buf, args.len)); + } + if (args.dealloc && args.buf) + args.dealloc((void *)args.buf); + if (!args.buf && (unsigned)(args.fd + 1) > 1) + close(args.fd); } -/** a TRUE object was detected */ -static inline void fio_json_on_true(fio_json_parser_s *p) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_true()); +/* ***************************************************************************** +Connection Lost +***************************************************************************** */ + +FIO_SFUNC void fio___http_controller_on_destroyed_task(void *c_, void *ignr_) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + fio___http_connection_free(c); + (void)ignr_; } -/** a FALSE object was detected */ -static inline void fio_json_on_false(fio_json_parser_s *p) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, fiobj_false()); + +FIO_SFUNC void fio___http_controller_http1_on_finish_client_task(void *c_, + void *h_) { + fio___http_connection_s *c = (fio___http_connection_s *)c_; + fio_http_s *h = (fio_http_s *)h_; + c->settings->on_finish(h); + fio_http_free(h); + fio___http_connection_free(c); } -/** a Numeral was detected (long long). */ -static inline void fio_json_on_number(fio_json_parser_s *p, long long i) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_NUMBER), new)(i)); + +FIO_SFUNC void fio___http_controller_http1_on_finish_client(fio_http_s *h) { + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + /* on_finish should be called after the `on_close` or after on_http */ + if (!fio_http_is_upgraded(h)) { + /* on_finish always manually called here */ + fio_io_defer(fio___http_controller_http1_on_finish_client_task, + (void *)fio___http_connection_dup(c), + (void *)fio_http_dup(h)); + } } -/** a Float was detected (double). */ -static inline void fio_json_on_float(fio_json_parser_s *p, double f) { - fiobj_json_add2parser((fiobj_json_parser_s *)p, - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_FLOAT), new)(f)); + +/** Called when an HTTP handle is freed. */ +FIO_SFUNC void fio__http_controller_on_destroyed(fio_http_s *h) { + if (!(fio_http_is_upgraded(h) | fio_http_is_finished(h))) { + /* auto-finish if freed without finishing */ + if (!fio_http_status(h)) + fio_http_status_set(h, 500); /* ignored if headers already sent */ + fio_http_write_args_s args = {.finish = 1}; /* never sets upgrade flag */ + fio_http_write FIO_NOOP(h, args); + } + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + if (c->state.http.buf.buf) + FIO_STRING_FREE2(c->state.http.buf); + c->state.http.buf = FIO_STR_INFO0; + fio_queue_push(fio_io_queue(), + fio___http_controller_on_destroyed_task, + fio_http_cdata(h)); } -/** a String was detected (int / float). update `pos` to point at ending */ -static inline void fio_json_on_string(fio_json_parser_s *p, - const void *start, - size_t len) { - FIOBJ str = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), new)(); - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_STRING), write_unescape) - (str, start, len); - fiobj_json_add2parser((fiobj_json_parser_s *)p, str); + +/** Called when an HTTP handle is freed (no auto-finish, post upgrade). */ +FIO_SFUNC void fio__http_controller_on_destroyed2(fio_http_s *h) { + fio_queue_push(fio_io_queue(), + fio___http_controller_on_destroyed_task, + fio_http_cdata(h)); } -/** a dictionary object was detected */ -static inline int fio_json_on_start_object(fio_json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - if (pr->target) { - /* push NULL, don't free the objects */ - pr->stack[pr->so++] = FIOBJ_INVALID; - pr->top = pr->target; - pr->target = FIOBJ_INVALID; - } else { - FIOBJ hash; -#if FIOBJ_JSON_APPEND - hash = FIOBJ_INVALID; - if (pr->key && FIOBJ_TYPE_CLASS(pr->top) == FIOBJ_T_HASH) { - hash = - FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(pr->top, pr->key); - } - if (FIOBJ_TYPE_CLASS(hash) != FIOBJ_T_HASH) { - hash = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); - fiobj_json_add2parser(pr, hash); + +/** Called when an HTTP handle is freed. */ +FIO_SFUNC void fio__http_controller_on_destroyed_client(fio_http_s *h) { + fio_queue_push(fio_io_queue(), + fio___http_controller_on_destroyed_task, + fio_http_cdata(h)); + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + c->state.http.on_finish(h); + c->h = NULL; + if (c->io) + fio_io_close(c->io); + fio_queue_push(fio_io_queue(), fio___http_controller_on_destroyed_task, c); +} + +/* ***************************************************************************** +The Protocols at play +***************************************************************************** */ + +/** Returns a facil.io protocol object with the proper protocol callbacks. */ +FIO_IFUNC fio_io_protocol_s FIO_NOOP +fio___http_protocol_get(fio___http_protocol_selector_e s, int is_client) { + fio_io_protocol_s r = {0}; + (void)is_client, (void)s; + switch (s) { + case FIO___HTTP_PROTOCOL_ACCEPT: + r = (fio_io_protocol_s){.on_attach = fio___http_on_attach_accept, + .on_data = fio___http1_accept_on_data, + .on_close = fio___http_on_close}; + return r; + case FIO___HTTP_PROTOCOL_HTTP1: + if (is_client) { + r = (fio_io_protocol_s){.on_attach = fio___http1_on_attach_client, + .on_data = fio___http1_on_data, + .on_close = fio___http_on_close}; } else { - fiobj_free(pr->key); - pr->key = FIOBJ_INVALID; + r = (fio_io_protocol_s){.on_attach = fio___http1_on_attach, + .on_data = fio___http1_on_data, + .on_close = fio___http_on_close}; } -#else - hash = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), new)(); - fiobj_json_add2parser(pr, hash); -#endif - pr->stack[pr->so++] = pr->top; - pr->top = hash; + return r; + case FIO___HTTP_PROTOCOL_HTTP2: + r = (fio_io_protocol_s){.on_close = fio___http_on_close}; + return r; + case FIO___HTTP_PROTOCOL_WS: + r = (fio_io_protocol_s){ + .on_attach = fio___websocket_on_attach, + .on_data = fio___websocket_on_data, + .on_ready = fio___websocket_on_ready, + .on_close = fio___websocket_on_close, + .on_shutdown = fio___websocket_on_shutdown, + .on_timeout = fio___websocket_on_timeout, + .on_pubsub = FIO_HTTP_WEBSOCKET_SUBSCRIBE_DIRECT, + }; + return r; + case FIO___HTTP_PROTOCOL_SSE: + r = (fio_io_protocol_s){ + .on_attach = fio___sse_on_attach, + .on_data = (is_client ? fio___sse_on_data : NULL), + .on_ready = fio___websocket_on_ready, + .on_close = fio___sse_on_close, + .on_shutdown = fio___sse_on_shutdown, + .on_timeout = fio___sse_on_timeout, + .on_pubsub = FIO_HTTP_SSE_SUBSCRIBE_DIRECT, + }; + return r; + case FIO___HTTP_PROTOCOL_NONE: /* fall through*/ + r = (fio_io_protocol_s){.on_close = fio___http_on_close}; + return r; + default: + FIO_LOG_ERROR("internal function `fio___http_protocol_get` called with " + "illegal arguments!"); + return r; } - return 0; } -/** a dictionary object closure detected */ -static inline void fio_json_on_end_object(fio_json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - if (pr->key) { - FIO_LOG_WARNING("(JSON parsing) malformed JSON, " - "ignoring dangling Hash key."); - fiobj_free(pr->key); - pr->key = FIOBJ_INVALID; + +/** Returns an http controller object with the proper protocol callbacks. */ +FIO_IFUNC fio_http_controller_s +fio___http_controller_get(fio___http_protocol_selector_e s, int is_client) { + fio_http_controller_s r = {0}; + (void)is_client, (void)s; + switch (s) { + case FIO___HTTP_PROTOCOL_ACCEPT: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed, + .send_headers = fio___http_controller_http1_send_headers, + .write_body = fio___http_controller_http1_write_body, + .on_finish = fio___http_controller_http1_on_finish, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_HTTP1: + if (is_client) { + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed_client, + .on_finish = fio___http_controller_http1_on_finish_client, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + } else { + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed, + .send_headers = fio___http_controller_http1_send_headers, + .write_body = fio___http_controller_http1_write_body, + .on_finish = fio___http_controller_http1_on_finish, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + } + return r; + case FIO___HTTP_PROTOCOL_HTTP2: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_WS: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed2, + .write_body = fio___http_controller_ws_write_body, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_SSE: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed2, + .write_body = fio___http_controller_sse_write_body, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + case FIO___HTTP_PROTOCOL_NONE: + r = (fio_http_controller_s){ + .on_destroyed = fio__http_controller_on_destroyed2, + .close_io = fio___http_default_close, + .get_fd = fio___http_controller_get_fd, + }; + return r; + default: + FIO_LOG_ERROR("internal function `fio___http_controller_get` called with " + "illegal arguments!"); + return r; } - pr->top = FIOBJ_INVALID; - if (pr->so) - pr->top = pr->stack[--pr->so]; } -/** an array object was detected */ -static int fio_json_on_start_array(fio_json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - FIOBJ ary = FIOBJ_INVALID; - if (pr->target != FIOBJ_INVALID) { - if (FIOBJ_TYPE_CLASS(pr->target) != FIOBJ_T_ARRAY) - return -1; - ary = pr->target; - pr->target = FIOBJ_INVALID; - } -#if FIOBJ_JSON_APPEND - if (pr->key && FIOBJ_TYPE_CLASS(pr->top) == FIOBJ_T_HASH) { - ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), get)(pr->top, pr->key); + +FIO_IFUNC fio___http_protocol_s *fio___http_protocol_init( + fio___http_protocol_s *p, + const char *url, + fio_http_settings_s s, + bool is_client) { + int should_free_tls = !s.tls; + FIO_ASSERT_ALLOC(p); + for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE + 1; ++i) { + p->state[i].protocol = + fio___http_protocol_get((fio___http_protocol_selector_e)i, is_client); + // p->state[i].protocol.iomem_size = + // sizeof(fio___http_connection_s) + s.max_line_len; + p->state[i].controller = + fio___http_controller_get((fio___http_protocol_selector_e)i, is_client); } - if (FIOBJ_TYPE_CLASS(ary) != FIOBJ_T_ARRAY) { - ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); - fiobj_json_add2parser(pr, ary); - } else { - fiobj_free(pr->key); - pr->key = FIOBJ_INVALID; + for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE; ++i) + p->state[i].protocol.timeout = (unsigned)s.ws_timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_SSE].protocol.timeout = + (unsigned)s.sse_timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_ACCEPT].protocol.timeout = + (unsigned)s.timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_HTTP1].protocol.timeout = + (unsigned)s.timeout * 1000U; + p->state[FIO___HTTP_PROTOCOL_NONE].protocol.timeout = + (unsigned)s.timeout * 1000U; + if (url) { + fio_url_s u = fio_url_parse(url, strlen(url)); + s.tls = fio_io_tls_from_url(s.tls, u); + if (s.tls) { + s.tls = fio_io_tls_dup(s.tls); + /* fio_io_tls_alpn_add(s.tls, "h2", fio___http_on_select_h2); // not yet + */ + // fio_io_tls_alpn_add(s.tls, "http/1.1", fio___http_on_select_h1); + fio_io_functions_s tmp_fn = fio_io_tls_default_functions(NULL); + if (!s.tls_io_func) + s.tls_io_func = &tmp_fn; + for (size_t i = 0; i < FIO___HTTP_PROTOCOL_NONE + 1; ++i) + p->state[i].protocol.io_functions = *s.tls_io_func; + if (should_free_tls) + fio_io_tls_free(s.tls); + } } -#else - FIOBJ ary = FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_ARRAY), new)(); - fiobj_json_add2parser(pr, ary); -#endif + p->settings = s; + p->on_http_callback = is_client ? fio___http_on_http_client + : (p->settings.public_folder.len) + ? fio___http_on_http_with_public_folder + : fio___http_on_http_direct; + p->settings.public_folder.buf = p->public_folder_buf; + p->queue = fio_io_queue(); - pr->stack[pr->so++] = pr->top; - pr->top = ary; - return 0; -} -/** an array closure was detected */ -static inline void fio_json_on_end_array(fio_json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - pr->top = FIOBJ_INVALID; - if (pr->so) - pr->top = pr->stack[--pr->so]; -} -/** the JSON parsing is complete */ -static void fio_json_on_json(fio_json_parser_s *p) { - (void)p; /* nothing special... right? */ -} -/** the JSON parsing is complete */ -static inline void fio_json_on_error(fio_json_parser_s *p) { - fiobj_json_parser_s *pr = (fiobj_json_parser_s *)p; - fiobj_free(pr->stack[0]); - fiobj_free(pr->key); - *pr = (fiobj_json_parser_s){.top = FIOBJ_INVALID}; - FIO_LOG_DEBUG("JSON on_error callback called."); + if (s.public_folder.len) + FIO_MEMCPY(p->public_folder_buf, s.public_folder.buf, s.public_folder.len); + return p; } +/* ***************************************************************************** +HTTP Helpers +***************************************************************************** */ -/** - * Updates a Hash using JSON data. - * - * Parsing errors and non-dictionary object JSON data are silently ignored, - * attempting to update the Hash as much as possible before any errors - * encountered. - * - * Conflicting Hash data is overwritten (preferring the new over the old). - * - * Returns the number of bytes consumed. On Error, 0 is returned and no data is - * consumed. - */ -SFUNC size_t FIO_NAME(FIO_NAME(fiobj, FIOBJ___NAME_HASH), - update_json)(FIOBJ hash, fio_str_info_s str) { - if (hash == FIOBJ_INVALID) - return 0; - fiobj_json_parser_s p = {.top = FIOBJ_INVALID, .target = hash}; - size_t consumed = fio_json_parse(&p.p, str.buf, str.len); - fiobj_free(p.key); - if (p.top != hash) - fiobj_free(p.top); - return consumed; +/** Returns the IO object associated with the HTTP object (request only). */ +SFUNC fio_io_s *fio_http_io(fio_http_s *h) { + if (!h) + return NULL; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + return c->io; } -/** Returns a JSON valid FIOBJ String, representing the object. */ -SFUNC FIOBJ fiobj_json_parse(fio_str_info_s str, size_t *consumed_p) { - fiobj_json_parser_s p = {.top = FIOBJ_INVALID}; - register const size_t consumed = fio_json_parse(&p.p, str.buf, str.len); - if (consumed_p) { - *consumed_p = consumed; - } - if (!consumed || p.p.depth) { - if (p.top) { - FIO_LOG_DEBUG("WARNING - JSON failed secondary validation, no on_error"); - } -#ifdef DEBUG - FIOBJ s = FIO_NAME2(fiobj, json)(FIOBJ_INVALID, p.top, 0); - FIO_LOG_DEBUG("JSON data being deleted:\n%s", - FIO_NAME2(fiobj, cstr)(s).buf); - fiobj_free(s); -#endif - fiobj_free(p.stack[0]); - p.top = FIOBJ_INVALID; - } - fiobj_free(p.key); - return p.top; +/** Returns the HTTP settings associated with the HTTP object, if any. */ +SFUNC fio_http_settings_s *fio_http_settings(fio_http_s *h) { + if (!h) + return NULL; + fio___http_connection_s *c = (fio___http_connection_s *)fio_http_cdata(h); + return c->settings; } -#endif -/** Uses JSON (JavaScript) notation to find data in an object structure. Returns - * a temporary object. */ -SFUNC FIOBJ fiobj_json_find(FIOBJ o, fio_str_info_s n) { - for (;;) { - top: - if (!n.len || (n.len == 1 && n.buf[0] == '.')) - return o; - switch (FIOBJ_TYPE_CLASS(o)) { - case FIOBJ_T_ARRAY: { - if (n.len <= 2 || n.buf[0] != '[' || n.buf[1] < '0' || n.buf[1] > '9') - return FIOBJ_INVALID; - size_t i = 0; - ++n.buf; - --n.len; - while (n.len && fio_c2i(n.buf[0]) < 10) { - i = (i * 10) + fio_c2i(n.buf[0]); - ++n.buf; - --n.len; - } - if (!n.len || n.buf[0] != ']' || i > 0xFFFFFFFFU) - return FIOBJ_INVALID; - o = fiobj_array_get(o, (uint32_t)i); - ++n.buf; - --n.len; - if (n.len) { - if (n.buf[0] == '.') { - ++n.buf; - --n.len; - } else if (n.buf[0] != '[') { - return FIOBJ_INVALID; - } - continue; - } - return o; - } - case FIOBJ_T_HASH: { - FIOBJ tmp = fiobj_hash_get2(o, n.buf, n.len); - if (tmp != FIOBJ_INVALID) - return tmp; - char *end = n.buf + n.len - 1; - while (end > n.buf) { - while (end > n.buf && end[0] != '.' && end[0] != '[') - --end; - if (end == n.buf) - return FIOBJ_INVALID; - const size_t t_len = end - n.buf; - tmp = fiobj_hash_get2(o, n.buf, t_len); - if (tmp != FIOBJ_INVALID) { - o = tmp; - n.len -= t_len + (end[0] == '.'); - n.buf = end + (end[0] == '.'); - goto top; - } - --end; - } - } /* fall through */ - default: return FIOBJ_INVALID; - } - } -} /* ***************************************************************************** -FIOBJ cleanup +Cleanup ***************************************************************************** */ #endif /* FIO_EXTERN_COMPLETE */ -#undef FIOBJ_EXTERN_OBJ -#undef FIOBJ_EXTERN_OBJ_IMP -#undef FIO_FIOBJ -#endif /* FIO_FIOBJ */ +#undef FIO_HTTP +#endif /* FIO_HTTP */ /* ************************************************************************* */ #if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ #define FIO___DEV___ /* Development inclusion - ignore line */ @@ -47309,9 +47580,10 @@ static int ary____test_was_destroyed = 0; /* ***************************************************************************** Environment printout ***************************************************************************** */ - +#ifndef FIO_PRINT_SIZE_OF #define FIO_PRINT_SIZE_OF(T) \ fprintf(stderr, "\t%-19s%zu Bytes\n", #T, sizeof(T)) +#endif FIO_SFUNC void FIO_NAME_TEST(stl, type_sizes)(void) { switch (sizeof(void *)) { @@ -47355,7 +47627,6 @@ FIO_SFUNC void FIO_NAME_TEST(stl, type_sizes)(void) { } #endif /* FIO_OS_POSIX */ } -#undef FIO_PRINT_SIZE_OF /* ***************************************************************************** ***************************************************************************** */ @@ -49898,6 +50169,316 @@ Cleanup + Server Test Helper + + + + +Copyright and License: see header file (000 copyright.h) or top of file +***************************************************************************** */ +#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ + !defined(H___FIO_SERVER_TEST___H) +#define H___FIO_SERVER_TEST___H +#ifndef H___FIO_SERVER___H +#define FIO_SERVER +#define FIO___TEST_REINCLUDE +#include FIO_INCLUDE_FILE +#undef FIO___TEST_REINCLUDE +#endif +/* ***************************************************************************** +Test TLS support +***************************************************************************** */ + +FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_cert)(fio_io_tls_each_s *e, + const char *nm, + const char *public_cert_file, + const char *private_key_file, + const char *pk_password) { + size_t *result = (size_t *)e->udata2; + *result += 0x01U; + const size_t step = result[0] & 0xFF; + struct { + const char *s[4]; + } d = + { + {nm, public_cert_file, private_key_file, pk_password}, + }, + ex = {{NULL, "cert.pem", "key.pem", "1234"}}; + FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_cert"); + for (size_t i = 1; i < 4; ++i) { + FIO_ASSERT(d.s[i] && ex.s[i] && FIO_STRLEN(ex.s[i]) == FIO_STRLEN(d.s[i]) && + !memcmp(ex.s[i], d.s[i], FIO_STRLEN(d.s[i])), + "tls_each_cert string error for argument %zu", + i); + } + return 0; +} +FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_alpn)(fio_io_tls_each_s *e, + const char *nm, + void (*fn)(fio_io_s *)) { + size_t *result = (size_t *)e->udata2; + *result += 0x0100U; + const size_t step = (result[0] >> 8) & 0xFF; + FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_alpn"); + FIO_ASSERT((uintptr_t)fn == step, "fn value error for tls_each_alpn"); + return 0; +} +FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_trust)(fio_io_tls_each_s *e, + const char *nm) { + + size_t *result = (size_t *)e->udata2; + *result += 0x010000U; + const size_t step = (result[0] >> 16) & 0xFF; + FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_trust"); + return 0; +} + +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + tls_each_alpn_cb)(fio_io_s *io) { + ((size_t *)io)[0]++; +} + +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_helpers)(void) { + fprintf(stderr, " * Testing fio_io_tls_s helpers.\n"); + struct { + const char *nm; + const char *public_cert_file; + const char *private_key_file; + const char *pk_password; + } tls_test_cert_data[] = { + { + .nm = "1", + .public_cert_file = "c.pem", + .private_key_file = "k.pem", + .pk_password = NULL, + }, + { + .nm = "2", + .public_cert_file = "cert.pem", + .private_key_file = "key.pem", + .pk_password = "1234", + }, + { + .nm = "1", + .public_cert_file = "cert.pem", + .private_key_file = "key.pem", + .pk_password = "1234", + }, + { + .nm = "3", + .public_cert_file = "cert.pem", + .private_key_file = "key.pem", + .pk_password = "1234", + }, + {NULL}, + }; + struct { + const char *nm; + void (*fn)(fio_io_s *); + } tls_test_alpn_data[] = { + { + .nm = "1", + .fn = (void (*)(fio_io_s *))(uintptr_t)3, + }, + { + .nm = "2", + .fn = (void (*)(fio_io_s *))(uintptr_t)2, + }, + { + .nm = "1", + .fn = (void (*)(fio_io_s *))(uintptr_t)1, + }, + {NULL}, + }; + struct { + const char *nm; + } tls_test_trust_data[] = { + { + .nm = "1", + }, + { + .nm = "2", + }, + {NULL}, + }; + size_t counter = 0; + void *data_containers[] = { + (void *)&tls_test_cert_data, + (void *)&tls_test_alpn_data, + (void *)&tls_test_trust_data, + NULL, + }; + fio_io_tls_s *t = fio_io_tls_new(); + FIO_ASSERT(t, "fio_io_tls_new should return a valid fio_io_tls_s object"); + for (size_t i = 0; tls_test_cert_data[i].nm; ++i) { + fio_io_tls_s *r = + fio_io_tls_cert_add(t, + tls_test_cert_data[i].nm, + tls_test_cert_data[i].public_cert_file, + tls_test_cert_data[i].private_key_file, + tls_test_cert_data[i].pk_password); + FIO_ASSERT(r == t, "`fio_io_tls_X_add` functions should return `self`."); + } + for (size_t i = 0; tls_test_alpn_data[i].nm; ++i) { + fio_io_tls_s *r = fio_io_tls_alpn_add(t, + tls_test_alpn_data[i].nm, + tls_test_alpn_data[i].fn); + FIO_ASSERT(r == t, "`fio_io_tls_X_add` functions should return `self`."); + } + for (size_t i = 0; tls_test_trust_data[i].nm; ++i) { + fio_io_tls_s *r = fio_io_tls_trust_add(t, tls_test_trust_data[i].nm); + FIO_ASSERT(r == t, "`fio_io_tls_X_add` functions should return `self`."); + } + + fio_io_tls_each( + t, + .udata = data_containers, + .udata2 = &counter, + .each_cert = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_cert), + .each_alpn = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_alpn), + .each_trust = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_trust)); + FIO_ASSERT(counter == 0x020203, "fio_io_tls_each iteration count error."); + fio_io_tls_alpn_add(t, + "tst", + FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_each_alpn_cb)); + counter = 0; + fio_io_tls_alpn_select(t, "tst", 3, (fio_io_s *)&counter); + FIO_ASSERT(counter == 1, "fio_io_tls_alpn_select failed."); + fio_io_tls_free(t); + + const struct { + fio_buf_info_s url; + size_t is_tls; + } url_tests[] = { + {FIO_BUF_INFO1((char *)"ws://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"wss://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"sse://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"sses://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"http://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"https://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"tcp://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"tcps://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"udp://ex.com"), 0}, + {FIO_BUF_INFO1((char *)"udps://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"tls://ex.com"), 1}, + {FIO_BUF_INFO1((char *)"ws://ex.com/?TLSN"), 0}, + {FIO_BUF_INFO1((char *)"ws://ex.com/?TLS"), 1}, + {FIO_BUF_INFO0, 0}, + }; + for (size_t i = 0; url_tests[i].url.buf; ++i) { + t = NULL; + fio_url_s u = fio_url_parse(url_tests[i].url.buf, url_tests[i].url.len); + t = fio_io_tls_from_url(t, u); + FIO_ASSERT((!url_tests[i].is_tls && !t) || (url_tests[i].is_tls && t), + "fio_io_tls_from_url result error @ %s", + url_tests[i].url.buf); + fio_io_tls_free(t); + } +} + +/* ***************************************************************************** +Test IO ENV support +***************************************************************************** */ + +/* State callback test task */ +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), + env_on_close)(void *udata) { + size_t *p = (size_t *)udata; + ++p[0]; +} + +/* State callback tests */ +FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env)(void) { + fprintf(stderr, " * Testing fio_env.\n"); + size_t a = 0, b = 0, c = 0; + fio___io_env_safe_s env = FIO___IO_ENV_SAFE_INIT; + fio___io_env_safe_set( + &env, + (char *)"a_key", + 5, + 1, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &a}, + 1); + FIO_ASSERT(fio___io_env_safe_get(&env, (char *)"a_key", 5, 1) == &a, + "fio___io_env_safe_set/get round-trip error!"); + fio___io_env_safe_set( + &env, + (char *)"a_key", + 5, + 2, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &a}, + 2); + fio___io_env_safe_set( + &env, + (char *)"a_key", + 5, + 3, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &a}, + 1); + fio___io_env_safe_set( + &env, + (char *)"b_key", + 5, + 1, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &b}, + 1); + fio___io_env_safe_set( + &env, + (char *)"c_key", + 5, + 1, + (fio___io_env_obj_s){ + .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env_on_close), + .udata = &c}, + 1); + fio___io_env_safe_unset(&env, (char *)"a_key", 5, 3); + FIO_ASSERT(!a, + "unset should have removed an object without calling callback."); + fio___io_env_safe_remove(&env, (char *)"a_key", 5, 3); + FIO_ASSERT(!a, "remove after unset should have no side-effects."); + fio___io_env_safe_remove(&env, (char *)"a_key", 5, 2); + FIO_ASSERT(a == 1, "remove should call callbacks."); + fio___io_env_safe_destroy(&env); + FIO_ASSERT(a == 2 && b == 1 && c == 1, "destroy should call callbacks."); +} + +/* ***************************************************************************** +Test Server Modules +***************************************************************************** */ + +FIO_SFUNC void FIO_NAME_TEST(stl, io)(void) { + fprintf(stderr, "* Testing fio_io units (TODO).\n"); + FIO_PRINT_SIZE_OF(fio_io_protocol_s); + FIO_PRINT_SIZE_OF(fio_io_s); + FIO_NAME_TEST(FIO_NAME_TEST(stl, io), env)(); + FIO_NAME_TEST(FIO_NAME_TEST(stl, io), tls_helpers)(); +} +/* ***************************************************************************** +Cleanup +***************************************************************************** */ +#endif /* FIO_TEST_ALL */ +/* ************************************************************************* */ +#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ +#define FIO___DEV___ /* Development inclusion - ignore line */ +#define FIO_TEST_ALL /* Development inclusion - ignore line */ +#include "./include.h" /* Development inclusion - ignore line */ +#endif /* Development inclusion - ignore line */ +/* ***************************************************************************** + + + + Math Test Helper @@ -50880,7 +51461,7 @@ FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_roundtrip)(void) { .channel = test_channel, \ .filter = -127); \ expected += delta; \ - fio_queue_perform_all(fio_srv_queue()); + fio_queue_perform_all(fio_io_queue()); for (int i = 0; i < sub_count; ++i) { fio_subscribe FIO_NOOP(sub[i]); @@ -50896,7 +51477,7 @@ FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_roundtrip)(void) { FIO_LOG_WARNING("fio_unsubscribe returned an error value"); --delta; --expected; - fio_queue_perform_all(fio___srv_tasks); + fio_queue_perform_all(fio_io_queue()); FIO_ASSERT(state == expected, "unsubscribe should call callback (%i)", i); FIO___PUBLISH2TEST(); FIO_ASSERT(state == expected, "pub/sub test state incorrect (3-%d)", i); @@ -50913,7 +51494,7 @@ FIO_SFUNC void FIO_NAME_TEST(stl, pubsub_roundtrip)(void) { FIO_SFUNC void FIO_NAME_TEST(stl, pubsub)(void) { FIO_NAME_TEST(stl, pubsub_encryption)(); FIO_NAME_TEST(stl, pubsub_roundtrip)(); - fio___srv_cleanup_at_exit(NULL); + fio___io_cleanup_at_exit(NULL); } /* ***************************************************************************** @@ -51851,6 +52432,16 @@ FIO_SFUNC void FIO_NAME_TEST(stl, random)(void) { fio_rand_feed2seed(rs, sizeof(*rs) * test_len); FIO_MEM_FREE(rs, sizeof(*rs) * test_len); fprintf(stderr, "\n"); + { + FIO_STR_INFO_TMP_VAR(data, 1124); + data.len = 1024; + for (size_t i = 0; i < data.len; ++i) + data.buf[i] = (char)(i & 255); + uint64_t h = fio_stable_hash(data.buf, 1024, 0); + FIO_LOG_DDEBUG2("Stable Hash Value: %p", (void *)h); + FIO_ASSERT(h == (uint64_t)0x5DC4DAD435547F67ULL, + "Stable Hash Value Error!"); + } #if DEBUG fprintf(stderr, "\t- to compare CPU cycles, test randomness with optimization.\n\n"); @@ -51871,311 +52462,6 @@ Cleanup - Server Test Helper - - - - -Copyright and License: see header file (000 copyright.h) or top of file -***************************************************************************** */ -#if defined(FIO_TEST_ALL) && !defined(FIO___TEST_REINCLUDE) && \ - !defined(H___FIO_SERVER_TEST___H) -#define H___FIO_SERVER_TEST___H -#ifndef H___FIO_SERVER___H -#define FIO_SERVER -#define FIO___TEST_REINCLUDE -#include FIO_INCLUDE_FILE -#undef FIO___TEST_REINCLUDE -#endif -/* ***************************************************************************** -Test TLS support -***************************************************************************** */ - -FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, server), - tls_each_cert)(fio_tls_each_s *e, - const char *nm, - const char *public_cert_file, - const char *private_key_file, - const char *pk_password) { - size_t *result = (size_t *)e->udata2; - *result += 0x01U; - const size_t step = result[0] & 0xFF; - struct { - const char *s[4]; - } d = - { - {nm, public_cert_file, private_key_file, pk_password}, - }, - ex = {{NULL, "cert.pem", "key.pem", "1234"}}; - FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_cert"); - for (size_t i = 1; i < 4; ++i) { - FIO_ASSERT(d.s[i] && ex.s[i] && FIO_STRLEN(ex.s[i]) == FIO_STRLEN(d.s[i]) && - !memcmp(ex.s[i], d.s[i], FIO_STRLEN(d.s[i])), - "tls_each_cert string error for argument %zu", - i); - } - return 0; -} -FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, server), - tls_each_alpn)(fio_tls_each_s *e, - const char *nm, - void (*fn)(fio_s *)) { - size_t *result = (size_t *)e->udata2; - *result += 0x0100U; - const size_t step = (result[0] >> 8) & 0xFF; - FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_alpn"); - FIO_ASSERT((uintptr_t)fn == step, "fn value error for tls_each_alpn"); - return 0; -} -FIO_SFUNC int FIO_NAME_TEST(FIO_NAME_TEST(stl, server), - tls_each_trust)(fio_tls_each_s *e, const char *nm) { - - size_t *result = (size_t *)e->udata2; - *result += 0x010000U; - const size_t step = (result[0] >> 16) & 0xFF; - FIO_ASSERT(nm && nm[0] == (char)('0' + step), "nm error for tls_each_trust"); - return 0; -} - -FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, server), - tls_each_alpn_cb)(fio_s *io) { - ((size_t *)io)[0]++; -} - -FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, server), tls_helpers)(void) { - fprintf(stderr, " * Testing fio_tls_s helpers.\n"); - struct { - const char *nm; - const char *public_cert_file; - const char *private_key_file; - const char *pk_password; - } tls_test_cert_data[] = { - { - .nm = "1", - .public_cert_file = "c.pem", - .private_key_file = "k.pem", - .pk_password = NULL, - }, - { - .nm = "2", - .public_cert_file = "cert.pem", - .private_key_file = "key.pem", - .pk_password = "1234", - }, - { - .nm = "1", - .public_cert_file = "cert.pem", - .private_key_file = "key.pem", - .pk_password = "1234", - }, - { - .nm = "3", - .public_cert_file = "cert.pem", - .private_key_file = "key.pem", - .pk_password = "1234", - }, - {NULL}, - }; - struct { - const char *nm; - void (*fn)(fio_s *); - } tls_test_alpn_data[] = { - { - .nm = "1", - .fn = (void (*)(fio_s *))(uintptr_t)3, - }, - { - .nm = "2", - .fn = (void (*)(fio_s *))(uintptr_t)2, - }, - { - .nm = "1", - .fn = (void (*)(fio_s *))(uintptr_t)1, - }, - {NULL}, - }; - struct { - const char *nm; - } tls_test_trust_data[] = { - { - .nm = "1", - }, - { - .nm = "2", - }, - {NULL}, - }; - size_t counter = 0; - void *data_containers[] = { - (void *)&tls_test_cert_data, - (void *)&tls_test_alpn_data, - (void *)&tls_test_trust_data, - NULL, - }; - fio_tls_s *t = fio_tls_new(); - FIO_ASSERT(t, "fio_tls_new should return a valid fio_tls_s object"); - for (size_t i = 0; tls_test_cert_data[i].nm; ++i) { - fio_tls_s *r = fio_tls_cert_add(t, - tls_test_cert_data[i].nm, - tls_test_cert_data[i].public_cert_file, - tls_test_cert_data[i].private_key_file, - tls_test_cert_data[i].pk_password); - FIO_ASSERT(r == t, "`fio_tls_X_add` functions should return `self`."); - } - for (size_t i = 0; tls_test_alpn_data[i].nm; ++i) { - fio_tls_s *r = - fio_tls_alpn_add(t, tls_test_alpn_data[i].nm, tls_test_alpn_data[i].fn); - FIO_ASSERT(r == t, "`fio_tls_X_add` functions should return `self`."); - } - for (size_t i = 0; tls_test_trust_data[i].nm; ++i) { - fio_tls_s *r = fio_tls_trust_add(t, tls_test_trust_data[i].nm); - FIO_ASSERT(r == t, "`fio_tls_X_add` functions should return `self`."); - } - - fio_tls_each( - t, - .udata = data_containers, - .udata2 = &counter, - .each_cert = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), tls_each_cert), - .each_alpn = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), tls_each_alpn), - .each_trust = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), tls_each_trust)); - FIO_ASSERT(counter == 0x020203, "fio_tls_each iteration count error."); - fio_tls_alpn_add(t, - "tst", - FIO_NAME_TEST(FIO_NAME_TEST(stl, server), tls_each_alpn_cb)); - counter = 0; - fio_tls_alpn_select(t, "tst", 3, (fio_s *)&counter); - FIO_ASSERT(counter == 1, "fio_tls_alpn_select failed."); - fio_tls_free(t); - - const struct { - fio_buf_info_s url; - size_t is_tls; - } url_tests[] = { - {FIO_BUF_INFO1((char *)"ws://ex.com"), 0}, - {FIO_BUF_INFO1((char *)"wss://ex.com"), 1}, - {FIO_BUF_INFO1((char *)"sse://ex.com"), 0}, - {FIO_BUF_INFO1((char *)"sses://ex.com"), 1}, - {FIO_BUF_INFO1((char *)"http://ex.com"), 0}, - {FIO_BUF_INFO1((char *)"https://ex.com"), 1}, - {FIO_BUF_INFO1((char *)"tcp://ex.com"), 0}, - {FIO_BUF_INFO1((char *)"tcps://ex.com"), 1}, - {FIO_BUF_INFO1((char *)"udp://ex.com"), 0}, - {FIO_BUF_INFO1((char *)"udps://ex.com"), 1}, - {FIO_BUF_INFO1((char *)"tls://ex.com"), 1}, - {FIO_BUF_INFO1((char *)"ws://ex.com/?TLSN"), 0}, - {FIO_BUF_INFO1((char *)"ws://ex.com/?TLS"), 1}, - {FIO_BUF_INFO0, 0}, - }; - for (size_t i = 0; url_tests[i].url.buf; ++i) { - t = NULL; - fio_url_s u = fio_url_parse(url_tests[i].url.buf, url_tests[i].url.len); - t = fio_tls_from_url(t, u); - FIO_ASSERT((!url_tests[i].is_tls && !t) || (url_tests[i].is_tls && t), - "fio_tls_from_url result error @ %s", - url_tests[i].url.buf); - fio_tls_free(t); - } -} - -/* ***************************************************************************** -Test IO ENV support -***************************************************************************** */ - -/* State callback test task */ -FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, server), - env_on_close)(void *udata) { - size_t *p = (size_t *)udata; - ++p[0]; -} - -/* State callback tests */ -FIO_SFUNC void FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env)(void) { - fprintf(stderr, " * Testing fio_env.\n"); - size_t a = 0, b = 0, c = 0; - fio___srv_env_safe_s env = FIO___SRV_ENV_SAFE_INIT; - fio___srv_env_safe_set( - &env, - (char *)"a_key", - 5, - 1, - (fio___srv_env_obj_s){ - .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env_on_close), - .udata = &a}, - 1); - FIO_ASSERT(fio___srv_env_safe_get(&env, (char *)"a_key", 5, 1) == &a, - "fio___srv_env_safe_set/get round-trip error!"); - fio___srv_env_safe_set( - &env, - (char *)"a_key", - 5, - 2, - (fio___srv_env_obj_s){ - .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env_on_close), - .udata = &a}, - 2); - fio___srv_env_safe_set( - &env, - (char *)"a_key", - 5, - 3, - (fio___srv_env_obj_s){ - .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env_on_close), - .udata = &a}, - 1); - fio___srv_env_safe_set( - &env, - (char *)"b_key", - 5, - 1, - (fio___srv_env_obj_s){ - .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env_on_close), - .udata = &b}, - 1); - fio___srv_env_safe_set( - &env, - (char *)"c_key", - 5, - 1, - (fio___srv_env_obj_s){ - .on_close = FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env_on_close), - .udata = &c}, - 1); - fio___srv_env_safe_unset(&env, (char *)"a_key", 5, 3); - FIO_ASSERT(!a, - "unset should have removed an object without calling callback."); - fio___srv_env_safe_remove(&env, (char *)"a_key", 5, 3); - FIO_ASSERT(!a, "remove after unset should have no side-effects."); - fio___srv_env_safe_remove(&env, (char *)"a_key", 5, 2); - FIO_ASSERT(a == 1, "remove should call callbacks."); - fio___srv_env_safe_destroy(&env); - FIO_ASSERT(a == 2 && b == 1 && c == 1, "destroy should call callbacks."); -} - -/* ***************************************************************************** -Test Server Modules -***************************************************************************** */ - -FIO_SFUNC void FIO_NAME_TEST(stl, server)(void) { - fprintf(stderr, "* Testing fio_srv units (TODO).\n"); - FIO_NAME_TEST(FIO_NAME_TEST(stl, server), env)(); - FIO_NAME_TEST(FIO_NAME_TEST(stl, server), tls_helpers)(); -} -/* ***************************************************************************** -Cleanup -***************************************************************************** */ -#endif /* FIO_TEST_ALL */ -/* ************************************************************************* */ -#if !defined(FIO_INCLUDE_FILE) /* Dev test - ignore line */ -#define FIO___DEV___ /* Development inclusion - ignore line */ -#define FIO_TEST_ALL /* Development inclusion - ignore line */ -#include "./include.h" /* Development inclusion - ignore line */ -#endif /* Development inclusion - ignore line */ -/* ***************************************************************************** - - - - FIO_SOCK Test Helper @@ -55107,7 +55393,7 @@ FIO_SFUNC void fio_test_dynamic_types(void) { fprintf(stderr, "===============\n"); FIO_NAME_TEST(stl, fiobj)(); fprintf(stderr, "===============\n"); - FIO_NAME_TEST(stl, server)(); + FIO_NAME_TEST(stl, io)(); FIO_NAME_TEST(stl, pubsub)(); fprintf(stderr, "===============\n"); FIO_NAME_TEST(stl, http_s)(); @@ -55273,32 +55559,38 @@ Finish testing segment #include "210 map2.h" #endif -#include "299 reference counter.h" /* required: pointer tagging cleanup is here */ +#include "249 reference counter.h" /* required: pointer tagging cleanup is here */ + +#if defined(FIO_FIOBJ) && !defined(FIO___RECURSIVE_INCLUDE) +#include "250 fiobj.h" +#endif #ifdef FIO_CRYPTO_CORE -#include "300 crypto core.h" +#include "150 crypto core.h" #endif #ifdef FIO_SHA1 -#include "302 sha1.h" +#include "152 sha1.h" #endif #ifdef FIO_SHA2 -#include "302 sha2.h" +#include "152 sha2.h" #endif #ifdef FIO_CHACHA -#include "302 chacha20poly1305.h" +#include "152 chacha20poly1305.h" #endif #ifdef FIO_ED25519 -#include "304 ed25519.h" +#include "154 ed25519.h" #endif -#if defined(FIO_SERVER) && !defined(FIO___RECURSIVE_INCLUDE) -#include "400 server.h" +#if defined(FIO_IO) && !defined(FIO___RECURSIVE_INCLUDE) +#include "400 io api.h" +#include "401 io types.h" +#include "402 io reactor.h" #if defined(HAVE_OPENSSL) -#include "402 openssl.h" +#include "411 openssl.h" #endif -#endif /* FIO_SERVER */ +#endif /* FIO_IO */ #if defined(FIO_PUBSUB) && !defined(FIO___RECURSIVE_INCLUDE) #include "420 pubsub.h" @@ -55319,10 +55611,6 @@ Finish testing segment #include "439 http.h" #endif -#if defined(FIO_FIOBJ) && !defined(FIO___RECURSIVE_INCLUDE) -#include "500 fiobj.h" -#endif - #ifndef FIO___DEV___ #include "700 cleanup.h" #endif @@ -55338,6 +55626,7 @@ Finish testing segment #include "902 glob matching.h" #include "902 http handle.h" #include "902 imap.h" +#include "902 io.h" #include "902 math.h" #include "902 memalt.h" #include "902 mustache.h" @@ -55345,7 +55634,6 @@ Finish testing segment #include "902 pubsub.h" #include "902 queue.h" #include "902 random.h" -#include "902 server.h" #include "902 sock.h" #include "902 sort.h" #include "902 state callbacks.h" diff --git a/ext/iodine/iodine.c b/ext/iodine/iodine.c index 1359fc0..61ce031 100644 --- a/ext/iodine/iodine.c +++ b/ext/iodine/iodine.c @@ -148,5 +148,5 @@ void Init_iodine_ext(void) { Init_Iodine_TLS(); Init_Iodine_Connection(); - fio_srv_async_init(&IODINE_THREAD_POOL, (uint32_t)fio_cli_get_i("-t")); + fio_io_async_attach(&IODINE_THREAD_POOL, (uint32_t)fio_cli_get_i("-t")); } diff --git a/ext/iodine/iodine.h b/ext/iodine/iodine.h index b29f249..d53f815 100644 --- a/ext/iodine/iodine.h +++ b/ext/iodine/iodine.h @@ -90,9 +90,9 @@ static void iodine_defer_performe_once(void *block, void *ignr); #define IODINE_DEFER_BLOCK(blk) \ do { \ STORE.hold((blk)); \ - fio_srv_async(&IODINE_THREAD_POOL, \ - iodine_defer_performe_once, \ - (void *)(blk)); \ + fio_io_async(&IODINE_THREAD_POOL, \ + iodine_defer_performe_once, \ + (void *)(blk)); \ } while (0) /* ***************************************************************************** @@ -110,7 +110,7 @@ Common Iodine Helpers ***************************************************************************** */ /* IO reactor thread-pool */ -static fio_srv_async_s IODINE_THREAD_POOL; +static fio_io_async_s IODINE_THREAD_POOL; /* layer 1 helpers */ #include "iodine_arg_helper.h" diff --git a/ext/iodine/iodine_cli.h b/ext/iodine/iodine_cli.h index e4b6e73..e1697cc 100644 --- a/ext/iodine/iodine_cli.h +++ b/ext/iodine/iodine_cli.h @@ -279,7 +279,7 @@ static VALUE iodine_cli_get(VALUE self, VALUE key) { } static VALUE iodine_cli_set(VALUE self, VALUE key, VALUE value) { - if (fio_srv_is_running() || !fio_srv_is_master()) + if (fio_io_is_running() || !fio_io_is_master()) rb_raise(rb_eException, "Setting CLI arguments can only be performed before Iodine.start " "and in the master process."); diff --git a/ext/iodine/iodine_connection.h b/ext/iodine/iodine_connection.h index d13b18d..579adad 100644 --- a/ext/iodine/iodine_connection.h +++ b/ext/iodine/iodine_connection.h @@ -43,7 +43,7 @@ typedef enum { } iodine_connection_flags_e; typedef struct iodine_connection_s { - fio_s *io; + fio_io_s *io; fio_http_s *http; VALUE store[IODINE_CONNECTION_STORE_FINISH]; iodine_minimap_s map; @@ -112,14 +112,14 @@ static iodine_connection_s *iodine_connection_ptr(VALUE self) { } /** Creates (and allocates) a new Iodine::Connection object. */ -static VALUE iodine_connection_create_from_io(fio_s *io) { +static VALUE iodine_connection_create_from_io(fio_io_s *io) { VALUE m = rb_obj_alloc(iodine_rb_IODINE_CONNECTION); STORE.hold(m); iodine_connection_s *c = iodine_connection_ptr(m); - c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)fio_udata_get(io); + c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)fio_io_udata(io); c->io = io; c->http = NULL; - fio_udata_set(io, (void *)m); + fio_io_udata_set(io, (void *)m); return m; } @@ -154,7 +154,7 @@ static VALUE iodine_connection_is_clean(VALUE o) { if (c->http) return (fio_http_is_clean(c->http) ? Qfalse : Qtrue); if (c->io) - return (fio_srv_is_open(c->io) ? Qtrue : Qfalse); + return (fio_io_is_open(c->io) ? Qtrue : Qfalse); return Qfalse; } @@ -478,7 +478,7 @@ typedef struct { } service; } iodine_connection_args_s; -static void iodine_tcp_on_stop(fio_protocol_s *p, void *udata); +static void iodine_tcp_on_stop(fio_io_protocol_s *p, void *udata); static void *iodine_tcp_listen(iodine_connection_args_s args); /* ***************************************************************************** @@ -1427,7 +1427,7 @@ Raw IO Callbacks and Helpers (TCP/IP) ***************************************************************************** */ typedef struct { - fio_s *io; + fio_io_s *io; size_t len; char buf[IODINE_RAW_ON_DATA_READ_BUFFER]; } iodine_io_raw_on_data_info_s; @@ -1445,14 +1445,14 @@ static void *iodine_io_raw_on_attach_in_GIL(void *io) { } /** Called when an IO is attached to the protocol. */ -static void iodine_io_raw_on_attach(fio_s *io) { +static void iodine_io_raw_on_attach(fio_io_s *io) { /* Enter GIL */ rb_thread_call_with_gvl(iodine_io_raw_on_attach_in_GIL, io); } static void *iodine_io_raw_on_data_in_GVL(void *info_) { iodine_io_raw_on_data_info_s *i = (iodine_io_raw_on_data_info_s *)info_; - VALUE connection = (VALUE)fio_udata_get(i->io); + VALUE connection = (VALUE)fio_io_udata(i->io); if (!connection || connection == Qnil) return NULL; iodine_connection_s *c = iodine_connection_ptr(connection); @@ -1467,18 +1467,18 @@ static void *iodine_io_raw_on_data_in_GVL(void *info_) { return NULL; } -static void iodine_io_raw_on_data(fio_s *io) { +static void iodine_io_raw_on_data(fio_io_s *io) { iodine_io_raw_on_data_info_s i; i.io = io; - i.len = fio_read(io, i.buf, IODINE_RAW_ON_DATA_READ_BUFFER); + i.len = fio_io_read(io, i.buf, IODINE_RAW_ON_DATA_READ_BUFFER); if (!i.len) return; rb_thread_call_with_gvl(iodine_io_raw_on_data_in_GVL, &i); } #define IODINE_CONNECTION_DEF_CB(named, id) \ - static void iodine_io_raw_##named(fio_s *io) { \ - VALUE connection = (VALUE)fio_udata_get(io); \ + static void iodine_io_raw_##named(fio_io_s *io) { \ + VALUE connection = (VALUE)fio_io_udata(io); \ if (!connection || connection == Qnil) \ return; \ iodine_connection_s *c = iodine_connection_ptr(connection); \ @@ -1495,14 +1495,14 @@ IODINE_CONNECTION_DEF_CB(on_ready, IODINE_ON_DRAINED_ID); /** called if the connection is open when the server is shutting down. */ IODINE_CONNECTION_DEF_CB(on_shutdown, IODINE_ON_SHUTDOWN_ID); /** Called when a connection's open while server is shutting down. */ -static void iodine_io_raw_on_shutdown(fio_s *io); +static void iodine_io_raw_on_shutdown(fio_io_s *io); /** Called when a connection's timeout was reached */ IODINE_CONNECTION_DEF_CB(on_timeout, IODINE_ON_TIMEOUT_ID); #undef IODINE_CONNECTION_DEF_CB /** Called after the connection was closed (called once per IO). */ -static void iodine_io_raw_on_close(void *udata) { +static void iodine_io_raw_on_close(void *buf, void *udata) { VALUE connection = (VALUE)udata; if (!connection || connection == Qnil) return; @@ -1513,9 +1513,10 @@ static void iodine_io_raw_on_close(void *udata) { IODINE_ON_CLOSE_ID, 1, &connection); + (void)buf; } -static fio_protocol_s IODINE_RAW_PROTOCOL = { +static fio_io_protocol_s IODINE_RAW_PROTOCOL = { .on_attach = iodine_io_raw_on_attach, .on_data = iodine_io_raw_on_data, .on_ready = iodine_io_raw_on_ready, @@ -1779,7 +1780,7 @@ static void iodine_connection_on_pubsub(fio_msg_s *m) { rb_thread_call_with_gvl(iodine_connection_on_pubsub_in_gvl, m); } -FIO_IFUNC VALUE iodine_connection_subscribe_internal(fio_s *io, +FIO_IFUNC VALUE iodine_connection_subscribe_internal(fio_io_s *io, int argc, VALUE *argv) { fio_buf_info_s channel = FIO_BUF_INFO2(NULL, 0); @@ -1811,7 +1812,7 @@ FIO_IFUNC VALUE iodine_connection_subscribe_internal(fio_s *io, return proc; } -static VALUE iodine_connection_unsubscribe_internal(fio_s *io, +static VALUE iodine_connection_unsubscribe_internal(fio_io_s *io, int argc, VALUE *argv) { int64_t filter = 0; @@ -1971,14 +1972,14 @@ FIO_IFUNC iodine_connection_args_s iodine_connection_parse_args(int argc, r.rb_tls); r.settings.tls = - (r.rb_tls == Qtrue ? fio_tls_new() - : fio_tls_dup(iodine_tls_get(r.rb_tls))); + (r.rb_tls == Qtrue ? fio_io_tls_new() + : fio_io_tls_dup(iodine_tls_get(r.rb_tls))); if (r.rb_tls == Qtrue && fio_cli_get("-cert") && fio_cli_get("-key")) - fio_tls_cert_add(r.settings.tls, - fio_cli_get("-name"), - fio_cli_get("-cert"), - fio_cli_get("-key"), - fio_cli_get("-tls-pass")); + fio_io_tls_cert_add(r.settings.tls, + fio_cli_get("-name"), + fio_cli_get("-cert"), + fio_cli_get("-key"), + fio_cli_get("-tls-pass")); } fio_cli_set("-b", NULL); @@ -2040,7 +2041,7 @@ static int iodine_connection___client_cookie(VALUE n, VALUE v, VALUE h_) { } /** Called after the connection was closed (called once per IO). */ -static void iodine_io_raw_client_on_close(void *udata) { +static void iodine_io_raw_client_on_close(void *buf, void *udata) { VALUE connection = (VALUE)udata; if (!connection || connection == Qnil) return; @@ -2050,10 +2051,11 @@ static void iodine_io_raw_client_on_close(void *udata) { IODINE_ON_CLOSE_ID, 1, &connection); - fio_protocol_s *p = fio_protocol_get(c->io); + fio_io_protocol_s *p = fio_io_protocol(c->io); FIO_MEM_FREE(p, sizeof(*p)); } STORE.release(connection); + (void)buf; } /** Initializes a Connection object. */ @@ -2091,16 +2093,17 @@ static VALUE iodine_connection_initialize(int argc, VALUE *argv, VALUE self) { } else { /* Raw Connection */ rb_raise(rb_eException, "Iodine::Connection.new using Raw Sockets is on the TODO list..."); - fio_protocol_s *protocol = FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0); + fio_io_protocol_s *protocol = + FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0); FIO_ASSERT_ALLOC(protocol); *protocol = IODINE_RAW_PROTOCOL; protocol->on_close = iodine_io_raw_client_on_close; protocol->timeout = 1000UL * (uint32_t)args.settings.ws_timeout; - c->io = fio_srv_connect(args.url.buf, - .protocol = protocol, - .udata = args.settings.udata, - .tls = args.settings.tls, - .on_failed = iodine_tcp_on_stop); + c->io = fio_io_connect(args.url.buf, + .protocol = protocol, + .udata = args.settings.udata, + .tls = args.settings.tls, + .on_failed = iodine_tcp_on_stop); } c->store[IODINE_CONNECTION_STORE_handler] = (VALUE)args.settings.udata; c->flags |= IODINE_CONNECTION_CLIENT; @@ -2115,7 +2118,7 @@ static VALUE iodine_connection_is_open(VALUE self) { ? Qfalse : Qtrue); if (c->io) - return (fio_srv_is_open(c->io) ? Qtrue : Qfalse); + return (fio_io_is_open(c->io) ? Qtrue : Qfalse); return Qfalse; } @@ -2126,7 +2129,7 @@ static VALUE iodine_connection_is_open(VALUE self) { static VALUE iodine_connection_pending(VALUE self) { iodine_connection_s *c = iodine_connection_ptr(self); if (c && c->io) - return RB_SIZE2NUM(((size_t)fio_stream_length(&c->io->stream))); + return RB_SIZE2NUM(((size_t)fio_io_backlog(c->io))); return Qfalse; } @@ -2139,7 +2142,7 @@ static VALUE iodine_connection_close(VALUE self) { if (fio_http_is_upgraded(c->http)) fio_http_close(c->http); } else if (c->io) - fio_close(c->io); + fio_io_close(c->io); c->flags |= IODINE_CONNECTION_CLOSED; } return self; @@ -2280,13 +2283,13 @@ FIO_IFUNC VALUE iodine_connection_write_internal(VALUE self, .copy = to_copy, .finish = finish); else if (c->io) { - fio_write2(c->io, - .buf = to_write.buf, - .len = to_write.len, - .dealloc = dealloc, - .copy = to_copy); + fio_io_write2(c->io, + .buf = to_write.buf, + .len = to_write.len, + .dealloc = dealloc, + .copy = to_copy); if (finish) - fio_close(c->io); + fio_io_close(c->io); } return Qtrue; @@ -2299,9 +2302,9 @@ FIO_IFUNC VALUE iodine_connection_write_internal(VALUE self, if (c->http) fio_http_write(c->http, .fd = fileno, .finish = finish); else if (c->io) { - fio_write2(c->io, .fd = fileno); + fio_io_write2(c->io, .fd = fileno); if (finish) - fio_close(c->io); + fio_io_close(c->io); } return Qtrue; } @@ -2360,9 +2363,9 @@ static VALUE iodine_connection_finish(int argc, VALUE *argv, VALUE self) { return Qfalse; fio_http_write(c->http, .finish = 1); } else if (c->io) { - if (!fio_srv_is_open(c->io)) + if (!fio_io_is_open(c->io)) return Qfalse; - fio_close(c->io); + fio_io_close(c->io); } return Qtrue; } @@ -2372,7 +2375,7 @@ static VALUE iodine_connection_peer_addr(VALUE self) { fio_buf_info_s buf = FIO_BUF_INFO0; iodine_connection_s *c = iodine_connection_ptr(self); if (c->io) - buf = fio_sock_peer_addr(fio_fd_get(c->io)); + buf = fio_sock_peer_addr(fio_io_fd(c->io)); if (!buf.len) return Qnil; return rb_str_new(buf.buf, buf.len); @@ -2387,7 +2390,7 @@ static VALUE iodine_connection_from(VALUE self) { fio_http_from(&addr, c->http); buf = FIO_STR2BUF_INFO(addr); } else if (c->io) - buf = fio_sock_peer_addr(fio_fd_get(c->io)); + buf = fio_sock_peer_addr(fio_io_fd(c->io)); if (!buf.len) return Qnil; return rb_str_new(buf.buf, buf.len); @@ -2402,7 +2405,7 @@ static VALUE iodine_connection_rack_hijack(VALUE self) { iodine_connection_s *c = iodine_connection_ptr(self); if (!c->http) return Qnil; - int new_fd = fio_sock_dup(fio_fd_get(fio_http_io(c->http))); + int new_fd = fio_sock_dup(fio_io_fd(fio_http_io(c->http))); VALUE nio = rb_io_fdopen(new_fd, O_RDWR, NULL); if (nio && nio != Qnil) { fio_http_close(c->http); @@ -2555,7 +2558,7 @@ static VALUE iodine_connection_subscribe_klass(int argc, return self; } -static VALUE iodine_connection_publish_internal(fio_s *io, +static VALUE iodine_connection_publish_internal(fio_io_s *io, int argc, VALUE *argv, VALUE self) { @@ -2643,25 +2646,25 @@ static VALUE iodine_connection_publish_klass(int argc, Listen to incoming TCP/IP Connections ***************************************************************************** */ -static void iodine_tcp_on_stop(fio_protocol_s *p, void *udata) { +static void iodine_tcp_on_stop(fio_io_protocol_s *p, void *udata) { STORE.release((VALUE)udata); FIO_MEM_FREE(p, sizeof(*p)); } static void *iodine_tcp_listen(iodine_connection_args_s args) { - fio_protocol_s *protocol = FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0); + fio_io_protocol_s *protocol = FIO_MEM_REALLOC(NULL, 0, sizeof(*protocol), 0); if (!protocol) return NULL; STORE.hold((VALUE)args.settings.udata); *protocol = IODINE_RAW_PROTOCOL; protocol->timeout = 1000UL * (uint32_t)args.settings.ws_timeout; - return fio_srv_listen(.url = args.url.buf, - .protocol = protocol, - .udata = args.settings.udata, - .tls = args.settings.tls, - .on_stop = iodine_tcp_on_stop, - .queue_for_accept = 0); + return fio_io_listen(.url = args.url.buf, + .protocol = protocol, + .udata = args.settings.udata, + .tls = args.settings.tls, + .on_stop = iodine_tcp_on_stop, + .queue_for_accept = 0); } /* ***************************************************************************** @@ -2687,10 +2690,10 @@ static VALUE iodine_listen_rb(int argc, VALUE *argv, VALUE self) { s.settings.on_stop = NULL; /* leak the memory, better than accidental.. */ listener = fio_http_listen FIO_NOOP(s.url.buf, s.settings); } - fio_tls_free(s.settings.tls); + fio_io_tls_free(s.settings.tls); if (!listener) rb_raise(rb_eRuntimeError, "Couldn't open listening socket."); - if (fio_srv_listener_is_tls(listener)) + if (fio_io_listener_is_tls(listener)) iodine_env_set_key_pair(IODINE_CONNECTION_ENV_TEMPLATE, FIO_STR_INFO2((char *)"rack.url_scheme", 15), FIO_STR_INFO2((char *)"https", 5)); diff --git a/ext/iodine/iodine_core.h b/ext/iodine/iodine_core.h index 2e19716..1e682a5 100644 --- a/ext/iodine/iodine_core.h +++ b/ext/iodine/iodine_core.h @@ -7,16 +7,16 @@ Starting / Stooping the IO Reactor ***************************************************************************** */ static void iodine_stop___(void *ignr_) { - fio_srv_stop(); + fio_io_stop(); (void)ignr_; } static void iodine_connection_cache_common_strings(void); static void *iodine___run(void *ignr_) { VALUE ver = rb_const_get(iodine_rb_IODINE, rb_intern2("VERSION", 7)); - unsigned threads = (unsigned)fio_srv_workers((int)fio_cli_get_i("-t")); - unsigned workers = (unsigned)fio_srv_workers((int)fio_cli_get_i("-w")); - fio_srv_async_update(&IODINE_THREAD_POOL, (uint32_t)threads); + unsigned threads = (unsigned)fio_io_workers((int)fio_cli_get_i("-t")); + unsigned workers = (unsigned)fio_io_workers((int)fio_cli_get_i("-w")); + fio_io_async_attach(&IODINE_THREAD_POOL, (uint32_t)threads); iodine_env_set_const_val(IODINE_CONNECTION_ENV_TEMPLATE, FIO_STR_INFO1((char *)"rack.multithread"), @@ -35,7 +35,7 @@ static void *iodine___run(void *ignr_) { (workers ? "cluster mode" : "single process"), (int)IODINE_THREAD_POOL.count); - fio_srv_start((int)fio_cli_get_i("-w")); + fio_io_start((int)fio_cli_get_i("-w")); return ignr_; } @@ -52,23 +52,23 @@ static VALUE iodine_start(VALUE self) { // clang-format on * new worker will be spawned. */ static VALUE iodine_stop(VALUE klass) { - fio_srv_stop(); + fio_io_stop(); return klass; } /** Return `true` if reactor is running */ static VALUE iodine_is_running(VALUE klass) { - return fio_srv_is_running() ? Qtrue : Qfalse; + return fio_io_is_running() ? Qtrue : Qfalse; } /** Return `true` if this is the master process. */ static VALUE iodine_is_master(VALUE klass) { - return fio_srv_is_master() ? Qtrue : Qfalse; + return fio_io_is_master() ? Qtrue : Qfalse; } /** Return `true` if this is a worker process. */ static VALUE iodine_is_worker(VALUE klass) { - return fio_srv_is_worker() ? Qtrue : Qfalse; + return fio_io_is_worker() ? Qtrue : Qfalse; } /* ***************************************************************************** @@ -79,7 +79,7 @@ Workers static VALUE iodine_workers(VALUE klass) { if (!fio_cli_get("-w")) return Qnil; - unsigned long tmp = fio_srv_workers((uint16_t)fio_cli_get_i("-w")); + unsigned long tmp = fio_io_workers((uint16_t)fio_cli_get_i("-w")); return LL2NUM(tmp); (void)klass; } @@ -90,7 +90,7 @@ static VALUE iodine_workers(VALUE klass) { * Settable only in the root / master process. */ static VALUE iodine_workers_set(VALUE klass, VALUE workers) { - if (workers != Qnil && fio_srv_is_master()) { + if (workers != Qnil && fio_io_is_master()) { if (TYPE(workers) != T_FIXNUM) { rb_raise(rb_eTypeError, "workers must be a number."); return Qnil; @@ -112,7 +112,7 @@ Threads static VALUE iodine_threads(VALUE klass) { if (!fio_cli_get("-t")) return Qnil; - unsigned long tmp = fio_srv_workers((uint16_t)fio_cli_get_i("-t")); + unsigned long tmp = fio_io_workers((uint16_t)fio_cli_get_i("-t")); return LL2NUM(tmp); (void)klass; } @@ -123,7 +123,7 @@ static VALUE iodine_threads(VALUE klass) { * Settable only in the root / master process. */ static VALUE iodine_threads_set(VALUE klass, VALUE threads) { - if (threads != Qnil && fio_srv_is_master()) { + if (threads != Qnil && fio_io_is_master()) { if (TYPE(threads) != T_FIXNUM) { rb_raise(rb_eTypeError, "threads must be a number."); return Qnil; diff --git a/ext/iodine/iodine_defer.h b/ext/iodine/iodine_defer.h index fe28cd9..afd9f61 100644 --- a/ext/iodine/iodine_defer.h +++ b/ext/iodine/iodine_defer.h @@ -150,7 +150,7 @@ static VALUE iodine_defer_run(VALUE self) { rb_need_block(); VALUE block = rb_block_proc(); STORE.hold(block); - fio_srv_defer(iodine_defer_performe_once, (void *)block, NULL); + fio_io_defer(iodine_defer_performe_once, (void *)block, NULL); return block; (void)self; } @@ -205,11 +205,11 @@ static VALUE iodine_defer_run_after(int argc, VALUE *argv, VALUE self) { IODINE_ARG_PROC(block, 0, "block", 1)); STORE.hold(block); repeat -= 1; - fio_srv_run_every(.every = (uint32_t)milli, - .repetitions = (int32_t)repeat, - .fn = iodine_defer_run_timer, - .udata1 = (void *)block, - .on_finish = iodine_defer_after_timer); + fio_io_run_every(.every = (uint32_t)milli, + .repetitions = (int32_t)repeat, + .fn = iodine_defer_run_timer, + .udata1 = (void *)block, + .on_finish = iodine_defer_after_timer); return block; } diff --git a/ext/iodine/iodine_json.h b/ext/iodine/iodine_json.h index 76f9aa9..3f69f32 100644 --- a/ext/iodine/iodine_json.h +++ b/ext/iodine/iodine_json.h @@ -280,5 +280,6 @@ static void Init_Iodine_JSON(void) { VALUE m = rb_define_module_under(iodine_rb_IODINE, "JSON"); // clang-format on rb_define_singleton_method(m, "parse", iodine_json_parse, 1); rb_define_singleton_method(m, "stringify", iodine_json_stringify, 1); + rb_define_singleton_method(m, "dump", iodine_json_stringify, 1); } #endif /* H___IODINE_JSON___H */ diff --git a/ext/iodine/iodine_mustache.h b/ext/iodine/iodine_mustache.h index a376da3..9212f45 100644 --- a/ext/iodine/iodine_mustache.h +++ b/ext/iodine/iodine_mustache.h @@ -275,14 +275,14 @@ static VALUE mus_build_and_render(int argc, VALUE *argv, VALUE klass) { // clang rb_raise(rb_eStandardError, "template couldn't be found or empty, nothing to build."); - char *result = fio_mustache_build(m, - .get_var = mus_get_var, - .array_length = mus_get_array_len, - .get_var_index = mus_get_var_index, - .var2str = mus_var2str, - .release_var = mus_release_var, - .is_lambda = mus_is_lambda, - .ctx = (void *)ctx); + char *result = (char *)fio_mustache_build(m, + .get_var = mus_get_var, + .array_length = mus_get_array_len, + .get_var_index = mus_get_var_index, + .var2str = mus_var2str, + .release_var = mus_release_var, + .is_lambda = mus_is_lambda, + .ctx = (void *)ctx); fio_mustache_free(m); if (!result) return Qnil; diff --git a/ext/iodine/iodine_pubsub_eng.h b/ext/iodine/iodine_pubsub_eng.h index 5bc0170..c2b46c3 100644 --- a/ext/iodine/iodine_pubsub_eng.h +++ b/ext/iodine/iodine_pubsub_eng.h @@ -202,7 +202,7 @@ static VALUE iodine_pubsub_eng_alloc(VALUE klass) { no_memory: FIO_LOG_FATAL("Memory allocation failed"); - fio_srv_stop(); + fio_io_stop(); return Qnil; } diff --git a/ext/iodine/iodine_pubsub_msg.h b/ext/iodine/iodine_pubsub_msg.h index 8a4a313..f52b1df 100644 --- a/ext/iodine/iodine_pubsub_msg.h +++ b/ext/iodine/iodine_pubsub_msg.h @@ -63,7 +63,7 @@ static VALUE iodine_pubsub_msg_alloc(VALUE klass) { return TypedData_Wrap_Struct(klass, &IODINE_PUBSUB_MSG_DATA_TYPE, m); no_memory: FIO_LOG_FATAL("Memory allocation failed"); - fio_srv_stop(); + fio_io_stop(); return Qnil; } diff --git a/ext/iodine/iodine_store.h b/ext/iodine/iodine_store.h index 596ee7f..37bcec6 100644 --- a/ext/iodine/iodine_store.h +++ b/ext/iodine/iodine_store.h @@ -123,7 +123,7 @@ FIO_SFUNC VALUE iodine_store___print_debug(VALUE self) { FIO_LEAK_COUNTER_COUNT(iodine_pubsub_msg), FIO_LEAK_COUNTER_COUNT(fio_http), FIO_LEAK_COUNTER_COUNT(fio_bstr_s), - FIO_LEAK_COUNTER_COUNT(fio)); + FIO_LEAK_COUNTER_COUNT(fio___io)); // fio_state_callback_print_state(); #endif /* IODINE_STORE_SKIP_PRINT */ return self; diff --git a/ext/iodine/iodine_tls.h b/ext/iodine/iodine_tls.h index 58c58ab..32493d0 100644 --- a/ext/iodine/iodine_tls.h +++ b/ext/iodine/iodine_tls.h @@ -6,20 +6,20 @@ TLS Wrapper ***************************************************************************** */ static void iodine_tls_free(void *ptr_) { - fio_tls_s **p = (fio_tls_s **)ptr_; - fio_tls_free(*p); + fio_io_tls_s **p = (fio_io_tls_s **)ptr_; + fio_io_tls_free(*p); } static VALUE iodine_tls_alloc(VALUE klass) { - fio_tls_s **tls; - VALUE o = Data_Make_Struct(klass, fio_tls_s *, NULL, iodine_tls_free, tls); - *tls = fio_tls_new(); + fio_io_tls_s **tls; + VALUE o = Data_Make_Struct(klass, fio_io_tls_s *, NULL, iodine_tls_free, tls); + *tls = fio_io_tls_new(); return o; } -static fio_tls_s *iodine_tls_get(VALUE self) { - fio_tls_s **p; - Data_Get_Struct(self, fio_tls_s *, p); +static fio_io_tls_s *iodine_tls_get(VALUE self) { + fio_io_tls_s **p; + Data_Get_Struct(self, fio_io_tls_s *, p); return *p; } @@ -31,7 +31,7 @@ The Functions to be Wrapped /** * Adds a certificate a new SSL/TLS context / settings object (SNI support). * - * fio_tls_cert_add(tls, "www.example.com", + * fio_io_tls_cert_add(tls, "www.example.com", * "public_key.pem", * "private_key.pem", NULL ); * @@ -39,7 +39,7 @@ The Functions to be Wrapped * be `NULL`, which a context builder (`fio_io_functions_s`) should treat as a * request for a self-signed certificate. It may be silently ignored. */ -SFUNC fio_tls_s *fio_tls_cert_add(fio_tls_s *, +SFUNC fio_io_tls_s *fio_io_tls_cert_add(fio_io_tls_s *, const char *server_name, const char *public_cert_file, const char *private_key_file, @@ -54,12 +54,12 @@ SFUNC fio_tls_s *fio_tls_cert_add(fio_tls_s *, * * A `NULL` callback (`on_selected`) will be silently replaced with a no-op. */ -SFUNC fio_tls_s *fio_tls_alpn_add(fio_tls_s *tls, +SFUNC fio_io_tls_s *fio_io_tls_alpn_add(fio_io_tls_s *tls, const char *protocol_name, void (*on_selected)(fio_s *)); -/** Calls the `on_selected` callback for the `fio_tls_s` object. */ -SFUNC int fio_tls_alpn_select(fio_tls_s *tls, +/** Calls the `on_selected` callback for the `fio_io_tls_s` object. */ +SFUNC int fio_io_tls_alpn_select(fio_io_tls_s *tls, const char *protocol_name, size_t name_length, fio_s *); @@ -71,21 +71,21 @@ SFUNC int fio_tls_alpn_select(fio_tls_s *tls, * If `public_cert_file` is `NULL`, implementation is expected to add the * system's default trust registry. * - * Note: when the `fio_tls_s` object is used for server connections, this should + * Note: when the `fio_io_tls_s` object is used for server connections, this should * limit connections to clients that connect using a trusted certificate. * - * fio_tls_trust_add(tls, "google-ca.pem" ); + * fio_io_tls_trust_add(tls, "google-ca.pem" ); */ -SFUNC fio_tls_s *fio_tls_trust_add(fio_tls_s *, const char *public_cert_file); +SFUNC fio_io_tls_s *fio_io_tls_trust_add(fio_io_tls_s *, const char *public_cert_file); /** - * Returns the number of `fio_tls_cert_add` instructions. + * Returns the number of `fio_io_tls_cert_add` instructions. * * This could be used when deciding if to add a NULL instruction (self-signed). * - * If `fio_tls_cert_add` was never called, zero (0) is returned. + * If `fio_io_tls_cert_add` was never called, zero (0) is returned. */ -SFUNC uintptr_t fio_tls_cert_count(fio_tls_s *tls); +SFUNC uintptr_t fio_io_tls_cert_count(fio_io_tls_s *tls); /** * Returns the number of registered ALPN protocol names. @@ -95,42 +95,42 @@ SFUNC uintptr_t fio_tls_cert_count(fio_tls_s *tls); * * If no ALPN protocols are registered, zero (0) is returned. */ -SFUNC uintptr_t fio_tls_alpn_count(fio_tls_s *tls); +SFUNC uintptr_t fio_io_tls_alpn_count(fio_io_tls_s *tls); /** - * Returns the number of `fio_tls_trust_add` instructions. + * Returns the number of `fio_io_tls_trust_add` instructions. * * This could be used when deciding if to disable peer verification or not. * - * If `fio_tls_trust_add` was never called, zero (0) is returned. + * If `fio_io_tls_trust_add` was never called, zero (0) is returned. */ -SFUNC uintptr_t fio_tls_trust_count(fio_tls_s *tls); +SFUNC uintptr_t fio_io_tls_trust_count(fio_io_tls_s *tls); -/** Arguments (and info) for `fio_tls_each`. */ -typedef struct fio_tls_each_s { - fio_tls_s *tls; +/** Arguments (and info) for `fio_io_tls_each`. */ +typedef struct fio_io_tls_each_s { + fio_io_tls_s *tls; void *udata; void *udata2; - int (*each_cert)(struct fio_tls_each_s *, + int (*each_cert)(struct fio_io_tls_each_s *, const char *server_name, const char *public_cert_file, const char *private_key_file, const char *pk_password); - int (*each_alpn)(struct fio_tls_each_s *, + int (*each_alpn)(struct fio_io_tls_each_s *, const char *protocol_name, void (*on_selected)(fio_s *)); - int (*each_trust)(struct fio_tls_each_s *, const char *public_cert_file); -} fio_tls_each_s; + int (*each_trust)(struct fio_io_tls_each_s *, const char *public_cert_file); +} fio_io_tls_each_s; /** Calls callbacks for certificate, trust certificate and ALPN added. */ -SFUNC int fio_tls_each(fio_tls_each_s); +SFUNC int fio_io_tls_each(fio_io_tls_each_s); -/** `fio_tls_each` helper macro, see `fio_tls_each_s` for named arguments. */ -#define fio_tls_each(tls_, ...) \ - fio_tls_each(((fio_tls_each_s){.tls = tls_, __VA_ARGS__})) +/** `fio_io_tls_each` helper macro, see `fio_io_tls_each_s` for named arguments. */ +#define fio_io_tls_each(tls_, ...) \ + fio_io_tls_each(((fio_io_tls_each_s){.tls = tls_, __VA_ARGS__})) /** If `NULL` returns current default, otherwise sets it. */ -SFUNC fio_io_functions_s fio_tls_default_io_functions(fio_io_functions_s *); +SFUNC fio_io_functions_s fio_io_tls_default_io_functions(fio_io_functions_s *); #endif /* ***************************************************************************** @@ -168,7 +168,7 @@ Since TLS setup is crucial for security, an initialization error will result in Iodine crashing with an error message. This is expected behavior. */ // clang-format on static VALUE iodine_tls_cert_add(int argc, VALUE *argv, VALUE self) { - fio_tls_s *tls = iodine_tls_get(self); + fio_io_tls_s *tls = iodine_tls_get(self); fio_buf_info_s server_name = FIO_BUF_INFO1((char *)"localhost"); fio_buf_info_s public_cert_file = FIO_BUF_INFO0; fio_buf_info_s private_key_file = FIO_BUF_INFO0; @@ -179,11 +179,11 @@ static VALUE iodine_tls_cert_add(int argc, VALUE *argv, VALUE self) { IODINE_ARG_BUF(public_cert_file, 0, "cert", 0), IODINE_ARG_BUF(private_key_file, 0, "key", 0), IODINE_ARG_BUF(pk_password, 0, "password", 0)); - fio_tls_cert_add(tls, - server_name.buf, - public_cert_file.buf, - private_key_file.buf, - pk_password.buf); + fio_io_tls_cert_add(tls, + server_name.buf, + public_cert_file.buf, + private_key_file.buf, + pk_password.buf); return self; } diff --git a/lib/iodine.rb b/lib/iodine.rb index a6bd110..aabef4d 100644 --- a/lib/iodine.rb +++ b/lib/iodine.rb @@ -38,39 +38,15 @@ def self.trap_sigint module Server # NeoRack support requires Server::Event to map to the class implementing the NeoRack events. Event = Connection - # NeoRack supported extensions. - def self.extensions - return (@extentions ||= { neo_rack: [0,0,2], - ws: [0,0,1], - sse: [0,0,1], - pubsub: [0,0,1], - cookies: [0,0,1], - from: [0,0,1] - }) - end end end -### Automatic ActiveRecord and Sequel support for forking (preventing connection sharing) +### Automatic Sequel support for forking (preventing connection sharing) Iodine.on_state(:before_fork) do - if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:connection) - begin - ActiveRecord::Base.connection.disconnect! - rescue - end - end if defined?(Sequel) begin - Sequel::DATABASES.each { |database| database.disconnect } - rescue - end - end -end -Iodine.on_state(:after_fork) do - if defined?(ActiveRecord) && defined?(ActiveRecord::Base) && ActiveRecord::Base.respond_to?(:establish_connection) - begin - ActiveRecord::Base.establish_connection + Sequel::DATABASES.each(&:disconnect) rescue end end diff --git a/lib/iodine/version.rb b/lib/iodine/version.rb index 7785d60..86db28c 100644 --- a/lib/iodine/version.rb +++ b/lib/iodine/version.rb @@ -3,4 +3,14 @@ module Iodine # The Iodine gem version number VERSION = "0.8.0.dev" + # NeoRack supported extensions. + def self.extensions + return (@extentions ||= { neo_rack: "0.0.2".split(?.), + ws: "0.0.1".split(?.), + sse: "0.0.1".split(?.), + pubsub: "0.0.1".split(?.), + cookies: "0.0.1".split(?.), + from: "0.0.1".split(?.) + }) + end end