diff --git a/Dqn.h b/Dqn.h index abe030b..86a3ffd 100644 --- a/Dqn.h +++ b/Dqn.h @@ -1,3 +1,5 @@ +#if !defined(DQN_H) +#define DQN_H // ----------------------------------------------------------------------------- // NOTE: Dqn // ----------------------------------------------------------------------------- @@ -16,7 +18,6 @@ // #define DQN_WITH_JSON_WRITER // Dqn_JsonWriter // #define DQN_WITH_MAP // Dqn_Map // #define DQN_WITH_MATH // Dqn_V2/3/4/Mat4 and friends ... -// #define DQN_WITH_THREAD_CONTEXT // Dqn_ThreadContext and friends ... // // ----------------------------------------------------------------------------- // NOTE: Configuration @@ -70,14 +71,10 @@ // the descriptor/name to describe the allocation. All extra parameters and // tags are compiled out when tracing is disabled. // -// #define DQN_DEBUG_THREAD_CONTEXT 1 -// When DQN_WITH_THREAD_CONTEXT is defined and an end-application uses -// arenas from the Dqn_ThreadContext, their allocation usage will be -// recorded in the library and can be dumped using -// Dqn_Lib_DumpThreadContextArenaStats(...) - -#if !defined(DQN_H) -#define DQN_H +// #define DQN_DEBUG_THREAD_CONTEXT +// Define this macro to record allocation stats for arenas used in the +// thread context. The thread context arena stats can be printed by using +// Dqn_Lib_DumpThreadContextArenaStats. // ------------------------------------------------------------------------------------------------- // NOTE: Compiler @@ -151,6 +148,11 @@ #define DQN_MEMCMP(ptr1, ptr2, num) memcmp(ptr1, ptr2, num) #endif +#if !defined(DQN_MEMMOVE) + #include + #define DQN_MEMMOVE(dest, src, num) memmove(dest, src, num) +#endif + #if !defined(DQN_SQRTF) #include #define DQN_SQRTF(val) sqrtf(val) @@ -163,9 +165,9 @@ #define DQN_SWAP(a, b) \ do \ { \ - auto tmp = a; \ + auto temp = a; \ a = b; \ - b = tmp; \ + b = temp; \ } while (0) // NOTE: Prefer the templated Dqn_Array/CharCount function for type-safety. I prefer @@ -770,7 +772,6 @@ char const *Dqn_LogTypeString[] = { }; #undef X_MACRO -// Internal global variables for tracking the current logging function typedef void Dqn_LogProc(Dqn_LogType type, void *user_data, char const *file, Dqn_uint file_len, char const *func, Dqn_uint func_len, Dqn_uint line, char const *fmt, va_list va); // ------------------------------------------------------------------------------------------------ @@ -800,29 +801,6 @@ DQN_API void Dqn_Log (Dqn_LogType type, void *user_data, char const *file DQN_API Dqn_uintptr Dqn_AlignAddressEnsuringSpace(Dqn_uintptr address, Dqn_u8 alignment); DQN_API Dqn_uintptr Dqn_AlignAddress (Dqn_uintptr address, Dqn_u8 alignment); -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_PointerMetadata -// ------------------------------------------------------------------------------------------------- -// Store data about a memory allocated pointer. Only used for the generic heap allocator. -struct Dqn_PointerMetadata -{ - Dqn_i64 size; // The size of the user allocation not including the metadata - Dqn_u8 alignment; // The alignment of the user allocation - Dqn_u8 offset; // Subtract offset from aligned ptr to return to the allocation ptr -}; - -// Given an user allocation, compute the total space required which includes space for the metadata -// size: The size requested by the user -// alignment: The alignment requested by the user -DQN_API Dqn_isize Dqn_PointerMetadata_SizeRequired (Dqn_isize size, Dqn_u8 alignment); - -// ptr: The pointer to write the metadata into -// size: The size as calculated from 'PointerMetadata_SizeRequired' -// alignment: The alignment requested by the user -DQN_API char *Dqn_PointerMetadata_Init (void *ptr, Dqn_isize size, Dqn_u8 alignment); -DQN_API Dqn_PointerMetadata Dqn_PointerMetadata_Get (void *ptr); -DQN_API char *Dqn_PointerMetadata_GetRawPointer(void *ptr); - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_AllocationTracer // ------------------------------------------------------------------------------------------------- @@ -897,14 +875,14 @@ struct Dqn_CRTAllocator DQN_API Dqn_CRTAllocator Dqn_CRTAllocator_InitWithProcs(Dqn_CRTAllocator_MallocProc *allocate_proc, Dqn_CRTAllocator_ReallocProc *realloc_proc, Dqn_CRTAllocator_FreeProc *free_proc); DQN_API void Dqn_CRTAllocator_Free (Dqn_CRTAllocator *allocator, void *ptr); -#define Dqn_CRTAllocator_TaggedMalloc( allocator, size, tag) Dqn_CRTAllocator__Malloc(allocator, size, DQN_CALL_SITE(tag)) -#define Dqn_CRTAllocator_Malloc( allocator, size) Dqn_CRTAllocator__Malloc(allocator, size, DQN_CALL_SITE("")) +#define Dqn_CRTAllocator_Malloc(allocator, size) Dqn_CRTAllocator__Malloc(allocator, size, DQN_CALL_SITE("")) +#define Dqn_CRTAllocator_Realloc(allocator, ptr, size) Dqn_CRTAllocator__Realloc(allocator, ptr, size, DQN_CALL_SITE("")) -#define Dqn_CRTAllocator_TaggedRealloc( allocator, ptr, size, tag) Dqn_CRTAllocator__Realloc(allocator, ptr, size, DQN_CALL_SITE(tag)) -#define Dqn_CRTAllocator_Realloc( allocator, ptr, size) Dqn_CRTAllocator__Realloc(allocator, ptr, size, DQN_CALL_SITE("")) +#define Dqn_CRTAllocator_TaggedMalloc(allocator, size, tag) Dqn_CRTAllocator__Malloc(allocator, size, DQN_CALL_SITE(tag)) +#define Dqn_CRTAllocator_TaggedRealloc(allocator, ptr, size, tag) Dqn_CRTAllocator__Realloc(allocator, ptr, size, DQN_CALL_SITE(tag)) -DQN_API void *Dqn_CRTAllocator__Malloc (Dqn_CRTAllocator *allocator, Dqn_usize size DQN_CALL_SITE_ARGS); -DQN_API void *Dqn_CRTAllocator__Realloc (Dqn_CRTAllocator *allocator, void *ptr, Dqn_usize size DQN_CALL_SITE_ARGS); +DQN_API void *Dqn_CRTAllocator__Malloc(Dqn_CRTAllocator *allocator, Dqn_usize size DQN_CALL_SITE_ARGS); +DQN_API void *Dqn_CRTAllocator__Realloc(Dqn_CRTAllocator *allocator, void *ptr, Dqn_usize size DQN_CALL_SITE_ARGS); #endif // DQN_WITH_CRT_ALLOCATOR // ------------------------------------------------------------------------------------------------- @@ -975,10 +953,10 @@ struct Dqn_ArenaScopeData // and on scope exit, the arena will trigger the end of the undo region, // reverting all allocations between the declaration of this variable and the // end of it's scope. -struct Dqn_ScopedArena +struct Dqn_ArenaScope { - Dqn_ScopedArena(Dqn_ArenaAllocator *arena); - ~Dqn_ScopedArena(); + Dqn_ArenaScope(Dqn_ArenaAllocator *arena); + ~Dqn_ArenaScope(); Dqn_ArenaAllocator *arena; // For convenience Dqn_ArenaScopeData region; @@ -1013,10 +991,11 @@ DQN_API void Dqn_ArenaAllocator_EndScope (Dqn_ArenaScopeData regi #define Dqn_ArenaAllocator_TaggedCopy(arena, Type, src, count, tag) (Type *)Dqn_ArenaAllocator__Copy(arena, src, sizeof(*src) * count, alignof(Type) DQN_CALL_SITE(tag)) #define Dqn_ArenaAllocator_Copy(arena, Type, src, count) (Type *)Dqn_ArenaAllocator__Copy(arena, src, sizeof(*src) * count, alignof(Type) DQN_CALL_SITE("")) -DQN_API void *Dqn_ArenaAllocator__Copy (Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS); -DQN_API void *Dqn_ArenaAllocator__CopyNullTerminate(Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS); -DQN_API void *Dqn_ArenaAllocator__Allocate (Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_u8 alignment, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS); -DQN_API void Dqn_ArenaAllocator_LogStats (Dqn_ArenaAllocator const *arena, char const *label); + +DQN_API void *Dqn_ArenaAllocator__Copy (Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS); +DQN_API void *Dqn_ArenaAllocator__CopyNullTerminate(Dqn_ArenaAllocator *arena, void *src, Dqn_isize size, Dqn_u8 alignment DQN_CALL_SITE_ARGS); +DQN_API void *Dqn_ArenaAllocator__Allocate (Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_u8 alignment, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS); +DQN_API void Dqn_ArenaAllocator_LogStats (Dqn_ArenaAllocator const *arena, char const *label); // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_Map @@ -1114,21 +1093,21 @@ template struct Dqn_Array T * end() { return data + size; } }; -template DQN_API Dqn_Array Dqn_Array_InitWithMemory (T *memory, Dqn_isize max, Dqn_isize size = 0); -#define Dqn_Array_InitWithArenaNoGrow( arena, Type, max, size, zero_mem) Dqn_Array__InitWithArenaNoGrow( arena, max, size, zero_mem DQN_CALL_SITE("")) +template DQN_API Dqn_Array Dqn_Array_InitWithMemory(T *memory, Dqn_isize max, Dqn_isize size = 0); +#define Dqn_Array_InitWithArenaNoGrow(arena, Type, max, size, zero_mem) Dqn_Array__InitWithArenaNoGrow(arena, max, size, zero_mem DQN_CALL_SITE("")) -#define Dqn_Array_Reserve( array, size) Dqn_Array__Reserve(array, size DQN_CALL_SITE("")) +#define Dqn_Array_Reserve(array, size) Dqn_Array__Reserve(array, size DQN_CALL_SITE("")) -#define Dqn_Array_AddArray( array, items, num) Dqn_Array__AddArray(array, items, num DQN_CALL_SITE("")) -#define Dqn_Array_Add( array, item) Dqn_Array__Add(array, item DQN_CALL_SITE("")) -#define Dqn_Array_Make( array, num) Dqn_Array__Make(array, num DQN_CALL_SITE("")) -template DQN_API void Dqn_Array_Clear (Dqn_Array *a, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); +#define Dqn_Array_AddArray(array, items, num) Dqn_Array__AddArray(array, items, num DQN_CALL_SITE("")) +#define Dqn_Array_Add(array, item) Dqn_Array__Add(array, item DQN_CALL_SITE("")) +#define Dqn_Array_Make(array, num) Dqn_Array__Make(array, num DQN_CALL_SITE("")) +template DQN_API void Dqn_Array_Clear(Dqn_Array *a, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); -template DQN_API void Dqn_Array_EraseStable (Dqn_Array *a, Dqn_isize index); -template DQN_API void Dqn_Array_EraseUnstable (Dqn_Array *a, Dqn_isize index); +template DQN_API void Dqn_Array_EraseStable (Dqn_Array *a, Dqn_isize index); +template DQN_API void Dqn_Array_EraseUnstable(Dqn_Array *a, Dqn_isize index); -template DQN_API void Dqn_Array_Pop (Dqn_Array *a, Dqn_isize num, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); -template DQN_API T * Dqn_Array_Peek (Dqn_Array *a); +template DQN_API void Dqn_Array_Pop (Dqn_Array *a, Dqn_isize num, Dqn_ZeroMem zero_mem = Dqn_ZeroMem::No); +template DQN_API T * Dqn_Array_Peek(Dqn_Array *a); // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_String @@ -1166,87 +1145,121 @@ DQN_API Dqn_String Dqn_String_InitCString(char const *string); // Make an empty string from a the buffer. 1 byte is reserved for the null-terminator DQN_API Dqn_String Dqn_String_InitMemory(char *buf, Dqn_isize capacity); -// return: False if size is < 0 or the internal string is set to a nullptr +// return: False if size is < 0 or the internal string is set to a nullptr // otherwise true. DQN_API Dqn_b32 Dqn_String_IsValid (Dqn_String in); #define Dqn_String_TaggedFmt(arena, tag, fmt, ...) Dqn_String__Fmt(arena DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) -#define Dqn_String_Fmt(arena, fmt, ...) Dqn_String__Fmt(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) - #define Dqn_String_TaggedFmtV(arena, tag, fmt, ...) Dqn_String__FmtV(arena DQN_CALL_SITE(tag), fmt, ## __VA_ARGS__) -#define Dqn_String_FmtV(arena, fmt, ...) Dqn_String__FmtV(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) -#define Dqn_String_TaggedAllocate(arena, size, zero_mem, tag) Dqn_String__Allocate(arena, size, zero_mem DQN_CALL_SITE(tag)); -#define Dqn_String_Allocate(arena, size, zero_mem) Dqn_String__Allocate(arena, size, zero_mem DQN_CALL_SITE("")); - -#define Dqn_String_TaggedCopyCString(src, size, arena, tag) Dqn_String__CopyCString(src, size, arena DQN_CALL_SITE(tag)) -#define Dqn_String_CopyCString(src, size, arena) Dqn_String__CopyCString(src, size, arena DQN_CALL_SITE("")) +#define Dqn_String_Fmt(arena, fmt, ...) Dqn_String__Fmt(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) +#define Dqn_String_FmtV(arena, fmt, ...) Dqn_String__FmtV(arena DQN_CALL_SITE(""), fmt, ## __VA_ARGS__) +#define Dqn_String_TaggedAllocate(arena, size, zero_mem, tag) Dqn_String__Allocate(arena, size, zero_mem DQN_CALL_SITE(tag)) #define Dqn_String_TaggedCopy(src, arena, tag) Dqn_String__Copy(src, arena DQN_CALL_SITE(tag)) -#define Dqn_String_Copy(src, arena) Dqn_String__Copy(src, arena DQN_CALL_SITE("")) +#define Dqn_String_TaggedCopyCString(src, size, arena, tag) Dqn_String__CopyCString(src, size, arena DQN_CALL_SITE(tag)) -DQN_API Dqn_String Dqn_String__Fmt(Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, ...); -DQN_API Dqn_String Dqn_String__FmtV(Dqn_ArenaAllocator *arena, char const *fmt, va_list va DQN_CALL_SITE_ARGS); -DQN_API Dqn_String Dqn_String__Allocate(Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem); +#define Dqn_String_Allocate(arena, size, zero_mem) Dqn_String__Allocate(arena, size, zero_mem DQN_CALL_SITE("")) +#define Dqn_String_CopyCString(src, size, arena) Dqn_String__CopyCString(src, size, arena DQN_CALL_SITE("")) +#define Dqn_String_Copy(src, arena) Dqn_String__Copy(src, arena DQN_CALL_SITE("")) + +DQN_API Dqn_String Dqn_String__Fmt (Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS, char const *fmt, ...); +DQN_API Dqn_String Dqn_String__FmtV (Dqn_ArenaAllocator *arena, char const *fmt, va_list va DQN_CALL_SITE_ARGS); +DQN_API Dqn_String Dqn_String__Allocate (Dqn_ArenaAllocator *arena, Dqn_isize size, Dqn_ZeroMem zero_mem); DQN_API Dqn_String Dqn_String__CopyCString(char const *string, Dqn_isize size, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); -DQN_API Dqn_String Dqn_String__Copy(Dqn_String const src, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); +DQN_API Dqn_String Dqn_String__Copy (Dqn_String const src, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); DQN_API Dqn_String Dqn_String_TrimWhitespaceAround(Dqn_String src); DQN_API Dqn_b32 operator== (Dqn_String const &lhs, Dqn_String const &rhs); DQN_API Dqn_b32 operator!= (Dqn_String const &lhs, Dqn_String const &rhs); // Append to the string if there's enough capacity. No reallocation is permitted, fails if not enough space -DQN_API Dqn_b32 Dqn_String_AppendFmtV (Dqn_String *str, char const *fmt, va_list va); -DQN_API Dqn_b32 Dqn_String_AppendFmt (Dqn_String *str, char const *fmt, ...); +DQN_API Dqn_b32 Dqn_String_AppendFmtV(Dqn_String *str, char const *fmt, va_list va); +DQN_API Dqn_b32 Dqn_String_AppendFmt (Dqn_String *str, char const *fmt, ...); -enum struct Dqn_StringEqCase +enum struct Dqn_StringEq { Sensitive, Insensitive, }; -DQN_API Dqn_b32 Dqn_String_Eq (Dqn_String const lhs, Dqn_String const rhs, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); +DQN_API Dqn_b32 Dqn_String_Eq (Dqn_String const lhs, Dqn_String const rhs, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); DQN_API Dqn_b32 Dqn_String_EqInsensitive (Dqn_String const lhs, Dqn_String const rhs); -DQN_API Dqn_b32 Dqn_String_StartsWith (Dqn_String string, Dqn_String prefix, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); +DQN_API Dqn_b32 Dqn_String_StartsWith (Dqn_String string, Dqn_String prefix, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); DQN_API Dqn_b32 Dqn_String_StartsWithInsensitive(Dqn_String string, Dqn_String prefix); -DQN_API Dqn_b32 Dqn_String_EndsWith (Dqn_String string, Dqn_String prefix, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); +DQN_API Dqn_b32 Dqn_String_EndsWith (Dqn_String string, Dqn_String prefix, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); DQN_API Dqn_b32 Dqn_String_EndsWithInsensitive (Dqn_String string, Dqn_String prefix); DQN_API Dqn_Array Dqn_String_Split (Dqn_String src, Dqn_ArenaAllocator *arena); -DQN_API Dqn_String Dqn_String_TrimPrefix (Dqn_String src, Dqn_String prefix, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); -DQN_API Dqn_String Dqn_String_TrimSuffix (Dqn_String src, Dqn_String suffix, Dqn_StringEqCase eq_case = Dqn_StringEqCase::Sensitive); -DQN_API Dqn_b32 Dqn_String_IsAllDigits (Dqn_String src); +DQN_API Dqn_String Dqn_String_TrimPrefix (Dqn_String src, Dqn_String prefix, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); +DQN_API Dqn_String Dqn_String_TrimSuffix (Dqn_String src, Dqn_String suffix, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); -DQN_API Dqn_u64 Dqn_String_ToU64 (Dqn_String str); -DQN_API Dqn_i64 Dqn_String_ToI64 (Dqn_String str); +// Trim UTF8 or UTF16 BOM i.e. 0xFFEF or 0xEFBBBF +// TODO(dqn): The trim assumes little endian architecture +DQN_API Dqn_String Dqn_String_TrimByteOrderMark (Dqn_String src); +DQN_API Dqn_b32 Dqn_String_IsAllDigits (Dqn_String src); +DQN_API Dqn_b32 Dqn_String_IsAllHex (Dqn_String src); +DQN_API Dqn_b32 Dqn_String_ContainsChar (Dqn_String src, char ch); + +// Remove the substring denoted by the begin index and the size from the src +// string in-place using MEMMOVE to shift the string back. +DQN_API void Dqn_String_Remove (Dqn_String *in, Dqn_isize begin, Dqn_isize size); + +// start_index: Set an index within the src string to start the search from, if not desired, set to 0 +// return: The index of the matching find, -1 if it is not found +DQN_API Dqn_isize Dqn_String_Find (Dqn_String src, Dqn_String find, Dqn_isize start_index, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); +DQN_API Dqn_String Dqn_String_Replace (Dqn_String src, Dqn_String find, Dqn_String replace, Dqn_isize start_index, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena, Dqn_StringEq eq_case = Dqn_StringEq::Sensitive); +DQN_API Dqn_String Dqn_String_ReplaceInsensitive(Dqn_String src, Dqn_String find, Dqn_String replace, Dqn_isize start_index, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena); + +// Get the file name from a path by searching from the end of the string +// backwards to the first occuring path seperator '/' or '\'. If no path +// seperator is found, the original string is returned. +// path: Must point to a file path on the disk +// return: A string that is a slice of the path (i.e. range into the path string) representing the file name. +DQN_API Dqn_String Dqn_String_FileNameFromPath (Dqn_String src, Dqn_String path); + + +DQN_API Dqn_u64 Dqn_String_ToU64(Dqn_String str); +DQN_API Dqn_i64 Dqn_String_ToI64(Dqn_String str); // ------------------------------------------------------------------------------------------------ // NOTE: Dqn_Lib: Library book-keeping // ------------------------------------------------------------------------------------------------ struct Dqn_Lib { - Dqn_LogProc *LogCallback; - void * log_user_data; + Dqn_LogProc *LogCallback; + void * log_user_data; + bool log_no_output_file; // When true, stop the library from writing to a log file + void * log_file; // TODO(dqn): Hmmm, how should we do this... ? + Dqn_TicketMutex log_file_mutex; // Is locked when instantiating the log_file for the first time #if defined(DQN_OS_WIN32) LARGE_INTEGER win32_qpc_frequency; + + Dqn_TicketMutex win32_bcrypt_rng_mutex; BCRYPT_ALG_HANDLE win32_bcrypt_rng_handle; #endif -#if defined(DQN_WITH_THREAD_CONTEXT) && defined(DQN_DEBUG_THREAD_CONTEXT) +#if defined(DQN_DEBUG_THREAD_CONTEXT) Dqn_TicketMutex thread_context_mutex; Dqn_ArenaStats thread_context_arena_current_stats[256]; Dqn_ArenaStats thread_context_arena_highest_stats[256]; int thread_context_arena_stats_size; #endif }; + extern Dqn_Lib dqn__lib; // Update the default logging function, all logging functions will run through this callback // proc: The new logging function, set to nullptr to revert back to the default logger. // user_data: A user defined parameter to pass to the callback DQN_API void Dqn_Lib_SetLogCallback(Dqn_LogProc *proc, void *user_data); -DQN_API void Dqn_Lib_DumpThreadContextArenasOnExit(Dqn_String file); + +// file: Pass in nullptr to turn off writing logs to disk, otherwise point it to +// the FILE that you wish to write to. +DQN_API void Dqn_Lib_SetLogFile(void *file); + +DQN_API void Dqn_Lib_DumpThreadContextArenaStats(Dqn_String file_path); #if defined(DQN_WITH_FIXED_STRING) // ------------------------------------------------------------------------------------------------- @@ -1305,51 +1318,17 @@ DQN_API Dqn_StringListNode *Dqn_StringList_MakeNode(Dqn_ArenaAllocator *arena, D DQN_API void Dqn_StringList_AddNode(Dqn_StringList *list, Dqn_StringListNode *node); DQN_API void Dqn_StringList_AppendFmtV(Dqn_StringList *list, Dqn_ArenaAllocator *arena, char const *fmt, va_list args); DQN_API void Dqn_StringList_AppendFmt(Dqn_StringList *list, Dqn_ArenaAllocator *arena, char const *fmt, ...); + +// Append a string to the list. The "Copy" variant will copy the string before +// appending and should be used if the string to be passed in has transient +// memory or will be further modified. The non "Copy" variant will avoid +// a memory allocation and just shallow copy the string into the node. +DQN_API void Dqn_StringList_AppendString(Dqn_StringList *list, Dqn_ArenaAllocator *arena, Dqn_String string); +DQN_API void Dqn_StringList_AppendStringCopy(Dqn_StringList *list, Dqn_ArenaAllocator *arena, Dqn_String string); + DQN_API void Dqn_StringList_AppendFmt(Dqn_StringList *list, Dqn_ArenaAllocator *arena, char const *fmt, ...); DQN_API Dqn_String Dqn_StringList_Build(Dqn_StringList const *list, Dqn_ArenaAllocator *arena); -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_StringBuilder -// ------------------------------------------------------------------------------------------------- -// TODO(dqn): Rewrite this, iirc the logic here is way more complicated than it -// needs to be have written a StringList implementation that is many times -// simpler than this. There's value in being able to allocate a block up-front -// and append to it until we run out of space for operations where we make a lot -// of small nodes, like using JsonWriter. -struct Dqn_StringBuilderBlock -{ - char *mem; - Dqn_isize size; - Dqn_isize used; - Dqn_StringBuilderBlock *next; -}; - -// TODO(dqn): Rewrite this as string lists -Dqn_isize constexpr DQN_STRING_BUILDER_MIN_BLOCK_SIZE = DQN_KILOBYTES(4); -template -struct Dqn_StringBuilder -{ - Dqn_ArenaAllocator *arena; - char fixed_mem[N]; - Dqn_StringBuilderBlock fixed_mem_block; - Dqn_StringBuilderBlock *last_mem_block; -}; - -template DQN_API void Dqn_StringBuilder_InitWithArena (Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena); - -// Get the size required to build the string, it returns the length not including the null-terminator. -template DQN_API Dqn_isize Dqn_StringBuilder_GetSize (Dqn_StringBuilder const *builder); - -template DQN_API void Dqn_StringBuilder_BuildToDest (Dqn_StringBuilder const *builder, char *dest, Dqn_usize dest_size); -template DQN_API Dqn_String Dqn_StringBuilder_BuildStringWithArena (Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena); -#define Dqn_StringBuilder_Build( builder, arena, len) Dqn_StringBuilder__Build(builder, arena, len DQN_CALL_SITE("")) - -template DQN_API void Dqn_StringBuilder_AppendFmtV (Dqn_StringBuilder *builder, char const *fmt, va_list va); -template DQN_API void Dqn_StringBuilder_AppendFmt (Dqn_StringBuilder *builder, char const *fmt, ...); -template DQN_API void Dqn_StringBuilder_Append (Dqn_StringBuilder *builder, char const *str, Dqn_isize len = -1); -template DQN_API void Dqn_StringBuilder_AppendString (Dqn_StringBuilder *builder, Dqn_String const string); -template DQN_API void Dqn_StringBuilder_AppendChar (Dqn_StringBuilder *builder, char ch); - #if defined(DQN_WITH_FIXED_ARRAY) // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_FixedArray @@ -1439,17 +1418,12 @@ template DQN_API Dqn_List Dqn_List_InitWithArena(Dqn_ArenaAlloc int *item = it.data; } */ -template DQN_API Dqn_b32 Dqn_List_Iterate (Dqn_List *list, Dqn_ListIterator *iterator); - -#define Dqn_List_TaggedMake( list, count, tag) Dqn_List__Make(list, count DQN_CALL_SITE(tag)) -#define Dqn_List_Make( list, count) Dqn_List__Make(list, count DQN_CALL_SITE("")) -template DQN_API T *Dqn_List__Make( Dqn_List *list, Dqn_isize count DQN_CALL_SITE_ARGS); +template DQN_API Dqn_b32 Dqn_List_Iterate(Dqn_List *list, Dqn_ListIterator *iterator); +#define Dqn_List_TaggedMake(list, count, tag) Dqn_List__Make(list, count DQN_CALL_SITE(tag)) +#define Dqn_List_Make(list, count) Dqn_List__Make(list, count DQN_CALL_SITE("")) +template DQN_API T *Dqn_List__Make(Dqn_List *list, Dqn_isize count DQN_CALL_SITE_ARGS); #if defined(DQN_WITH_MATH) - -#if !defined(DQN_WITH_FIXED_STRING) - #error Math requires DQN_WITH_FIXED_STRING to be defined -#endif // ------------------------------------------------------------------------------------------------- // NOTE: Math // ------------------------------------------------------------------------------------------------- @@ -1626,7 +1600,9 @@ DQN_API Dqn_M4 Dqn_M4_SubF (Dqn_M4 lhs, Dqn_f32 rhs); DQN_API Dqn_M4 Dqn_M4_MulF (Dqn_M4 lhs, Dqn_f32 rhs); DQN_API Dqn_M4 Dqn_M4_DivF (Dqn_M4 lhs, Dqn_f32 rhs); +#if defined(DQN_WITH_FIXED_STRING) DQN_API Dqn_FixedString<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat); +#endif struct Dqn_Rect { @@ -1711,24 +1687,50 @@ DQN_API char Dqn_Char_ToHex (char ch); DQN_API char Dqn_Char_ToHexUnchecked(char ch); DQN_API char Dqn_Char_ToLower (char ch); +// ------------------------------------------------------------------------------------------------- +// NOTE: Dqn_Char +// ------------------------------------------------------------------------------------------------- +DQN_API int Dqn_UTF8_EncodeCodepoint(Dqn_u8 utf8[4], Dqn_u32 codepoint); +DQN_API int Dqn_UTF16_EncodeCodepoint(Dqn_u16 utf16[2], Dqn_u32 codepoint); + #if defined(DQN_WITH_HEX) // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_Hex // ------------------------------------------------------------------------------------------------- -DQN_API char const *Dqn_Hex_CStringTrimSpaceAnd0xPrefix(char const *hex, Dqn_isize size, Dqn_isize *real_size); -DQN_API Dqn_String Dqn_Hex_StringTrimSpaceAnd0xPrefix (Dqn_String const string); +DQN_API char const *Dqn_Hex_CStringTrimSpaceAnd0xPrefix(char const *hex, Dqn_isize size, Dqn_isize *real_size); +DQN_API Dqn_String Dqn_Hex_StringTrimSpaceAnd0xPrefix (Dqn_String const string); + +// Convert a hexadecimal string a 64 bit value. Asserts if the hex string is too +// big to be converted into a 64 bit number. +DQN_API Dqn_u64 Dqn_Hex_CStringToU64(char const *hex, Dqn_isize size); +DQN_API Dqn_u64 Dqn_Hex_StringToU64(Dqn_String hex); + +// Convert a binary buffer into its hex representation into dest. The dest +// buffer must be large enough to contain the hex representation, i.e. +// atleast src_size * 2). +DQN_API void Dqn_Hex_BytesToHex(void const *src, int src_size, char *dest, int dest_size); + +// Convert a hex buffer into its binary representation into dest. The dest +// buffer must be large enough to contain the binary representation, i.e. +// atleast ceil(hex_size / 2). +// hex_size: The size of the hex buffer. This function can handle an odd size +// hex string i.e. parsing "fff" works. +DQN_API void Dqn_Hex_HexToBytes(char const *hex, int hex_size, void *dest, int dest_size); + +// Convert a series of bytes into a string. The CString variant returns a string +// with length (size * 2) with 1 extra byte for the null-terminator. The +// return: nullptr/invalid Dqn_String if the allocation failed, otherwise the hex string in ASCII. +DQN_API char *Dqn_Hex_BytesToHexCStringArena(void const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena); +DQN_API Dqn_String Dqn_Hex_BytesToHexStringArena(void const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena); +DQN_API char *Dqn_Hex_U8ArrayToHexCStringArena(Dqn_Array const array, Dqn_ArenaAllocator *arena); +DQN_API Dqn_String Dqn_Hex_U8ArrayToHexStringArena(Dqn_Array const array, Dqn_ArenaAllocator *arena); // Convert a char string to a binary representation without any checks except assertions in debug. -DQN_API Dqn_u8 *Dqn_Hex_CStringToU8BytesUnchecked(char const *hex, Dqn_isize size, Dqn_isize *real_size, Dqn_ArenaAllocator *arena); -DQN_API Dqn_Array Dqn_Hex_CStringToU8ArrayUnchecked(char const *hex, Dqn_isize size, Dqn_ArenaAllocator *arena); -DQN_API Dqn_Array Dqn_Hex_StringToU8ArrayUnchecked (Dqn_String const hex, Dqn_ArenaAllocator *arena); +// A leading "0x" is permitted and will be skipped as well as odd-sized strings. +DQN_API Dqn_u8 *Dqn_Hex_HexCStringToU8Bytes(char const *hex, Dqn_isize size, Dqn_isize *real_size, Dqn_ArenaAllocator *arena); +DQN_API Dqn_Array Dqn_Hex_HexCStringToU8Array(char const *hex, Dqn_isize size, Dqn_ArenaAllocator *arena); +DQN_API Dqn_Array Dqn_Hex_HexStringToU8Array(Dqn_String const hex, Dqn_ArenaAllocator *arena); -DQN_API Dqn_u64 Dqn_Hex_CStringToU64(char const *hex, Dqn_isize size); -DQN_API Dqn_u64 Dqn_Hex_StringToU64(Dqn_String hex); - -// Convert a series of bytes into a string -DQN_API char *Dqn_Hex_U8BytesToCString(char const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena); -DQN_API Dqn_String Dqn_Hex_U8ArrayToString (Dqn_Array const bytes, Dqn_ArenaAllocator *arena); #endif // DQN_WITH_HEX // ------------------------------------------------------------------------------------------------- @@ -1781,8 +1783,8 @@ DQN_API Dqn_b32 Dqn_File_DirExists(Dqn_String path); DQN_API Dqn_FileInfo Dqn_File_Info (Dqn_String path); DQN_API Dqn_b32 Dqn_File_Copy (Dqn_String src, Dqn_String dest, Dqn_b32 overwrite); -// tmp_arena: Only used on Linux otherwise ignored -DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *tmp_arena); +// temp_arena: Only used on Linux otherwise ignored +DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *temp_arena); DQN_API Dqn_b32 Dqn_File_Move (Dqn_String src, Dqn_String dest, Dqn_b32 overwrite); // TODO(dqn): This doesn't work on directories unless you delete the files @@ -1792,9 +1794,9 @@ DQN_API Dqn_b32 Dqn_File_Delete (Dqn_String path); // file_size: (Optional) The size of the file in bytes, the allocated buffer is (file_size + 1 [null terminator]) in bytes. // allocator: (Optional) When null, the buffer is allocated with DQN_MALLOC, result should be freed with DQN_FREE. // return: nullptr if allocation failed. -#define Dqn_File_ArenaReadFile( file, file_size, arena) Dqn_File__ArenaReadFile(file, file_size, arena DQN_CALL_SITE("")) -#define Dqn_File_TaggedArenaReadFileToString(file, arena, tag) Dqn_File__ArenaReadFileToString(file, arena DQN_CALL_SITE(tag)) -#define Dqn_File_ArenaReadFileToString(file, arena) Dqn_File__ArenaReadFileToString(file, arena DQN_CALL_SITE("")) +#define Dqn_File_ArenaReadFile(file, file_size, arena) Dqn_File__ArenaReadFile(file, file_size, arena DQN_CALL_SITE("")) +#define Dqn_File_TaggedArenaReadFileToString(file, arena, tag) Dqn_File__ArenaReadFileToString(file, arena DQN_CALL_SITE(tag)) +#define Dqn_File_ArenaReadFileToString(file, arena) Dqn_File__ArenaReadFileToString(file, arena DQN_CALL_SITE("")) DQN_API char *Dqn_File__ArenaReadFile (char const *file, Dqn_isize *file_size, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); DQN_API Dqn_String Dqn_File__ArenaReadFileToString(char const *file, Dqn_ArenaAllocator *arena DQN_CALL_SITE_ARGS); DQN_API Dqn_b32 Dqn_File_WriteFile (char const *file, char const *buffer, Dqn_isize buffer_size); @@ -1826,6 +1828,9 @@ struct Dqn_DateHMSTime DQN_API Dqn_DateHMSTime Dqn_Date_HMSLocalTimeNow(); DQN_API Dqn_DateHMSTimeString Dqn_Date_HMSLocalTimeStringNow(char date_separator = '-', char hms_separator = ':'); +// return: The time elapsed since Unix epoch (1970-01-01T00:00:00Z) in seconds +DQN_API Dqn_u64 Dqn_Date_EpochTime(); + // ------------------------------------------------------------------------------------------------- // NOTE: OS // ------------------------------------------------------------------------------------------------- @@ -1839,6 +1844,8 @@ DQN_API Dqn_String Dqn_OS_ExecutableDirectory(Dqn_ArenaAllocator *arena); // ------------------------------------------------------------------------------------------------- // NOTE: Utiltiies // ------------------------------------------------------------------------------------------------- +DQN_API void Dqn_SleepMs(Dqn_uint milliseconds); + DQN_API Dqn_u64 Dqn_PerfCounter_Now (); DQN_API Dqn_f64 Dqn_PerfCounter_S (Dqn_u64 begin, Dqn_u64 end); DQN_API Dqn_f64 Dqn_PerfCounter_Ms (Dqn_u64 begin, Dqn_u64 end); @@ -1876,62 +1883,74 @@ struct Dqn_U64Str DQN_API char *Dqn_U64ToStr (Dqn_u64 val, Dqn_U64Str *result, Dqn_b32 comma_sep); DQN_API char *Dqn_U64ToTempStr(Dqn_u64 val, Dqn_b32 comma_sep = true); -#if defined(DQN_WITH_THREAD_CONTEXT) // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_ThreadContext // ------------------------------------------------------------------------------------------------- // Utility functions for building applications by providing an in-built thread // context that gives the user access to a temporary arena automatically without // explicit setup on the end application's side. -#if !defined(DQN_THREAD_CONTEXT_ARENA_COUNT) - #define DQN_THREAD_CONTEXT_ARENA_COUNT 2 +#if !defined(DQN_THREAD_CONTEXT_ARENAS) + #define DQN_THREAD_CONTEXT_ARENAS 2 #endif -struct Dqn_ThreadArena +struct Dqn_ThreadScratchData { Dqn_ArenaAllocator arena; - Dqn_i8 stats_index; // Index into Dqn_Lib's thread context arena stats array + Dqn_i8 arena_stats_index; // Index into Dqn_Lib's thread context arena stats array }; struct Dqn_ThreadContext { - Dqn_b32 init; - Dqn_ThreadArena arenas[DQN_THREAD_CONTEXT_ARENA_COUNT]; + Dqn_b32 init; + + // Thread data that is handed out when the thread context is requested. + Dqn_ThreadScratchData scratch_data[DQN_THREAD_CONTEXT_ARENAS]; }; -struct Dqn_ThreadScopedArena +struct Dqn_ThreadScratch { - Dqn_ThreadScopedArena(Dqn_ThreadArena *entry) - : thread_data(entry) - , arena(&entry->arena) - , scope_data(Dqn_ArenaAllocator_BeginScope(&entry->arena)) + Dqn_ThreadScratch(Dqn_ThreadScratchData *data) { + destructed = false; + arena = &data->arena; + arena_stats_index = data->arena_stats_index; + arena_scope = Dqn_ArenaAllocator_BeginScope(arena); } - ~Dqn_ThreadScopedArena() + ~Dqn_ThreadScratch() { -#if defined(DQN_DEBUG_THREAD_CONTEXT) - dqn__lib.thread_context_arena_highest_stats[thread_data->stats_index] = arena->highest_stats; - dqn__lib.thread_context_arena_current_stats[thread_data->stats_index] = arena->current_stats; -#endif - Dqn_ArenaAllocator_EndScope(scope_data); + #if defined(DQN_DEBUG_THREAD_CONTEXT) + dqn__lib.thread_context_arena_highest_stats[arena_stats_index] = arena->highest_stats; + dqn__lib.thread_context_arena_current_stats[arena_stats_index] = arena->current_stats; + #endif + + DQN_ASSERT(destructed == false); + Dqn_ArenaAllocator_EndScope(arena_scope); + destructed = true; } - Dqn_ThreadArena *thread_data; - Dqn_ArenaAllocator *arena; // For convenience, aliases thread_data->arena - Dqn_ArenaScopeData scope_data; + Dqn_b32 destructed; + Dqn_ArenaAllocator *arena; + int arena_stats_index; + Dqn_ArenaScopeData arena_scope; }; -DQN_API Dqn_ThreadContext *Dqn_GetThreadContext(); -DQN_API Dqn_ThreadScopedArena Dqn_GetThreadTempScopedArena(const Dqn_ThreadScopedArena *conflict_scope_arena = nullptr); -#endif // DQN_WITH_THREAD_CONTEXT +// A scratch thread context gives you a arena unique to the thread that is +// temporarily scoped to the block of code it is requested in. On scope exit, +// the arena will be freed. This is useful for very short-lived allocations. +// conflict_arena: Pass in any of the arenas currently being used in the +// function to ensure that the arena assigned to the new ThreadScratch +// will not overwrite allocations belonging to another scratch. +DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch(const Dqn_ArenaAllocator *conflict_arena = nullptr); + +// Get the current thread's context- this contains all the metadata for managing +// the thread scratch data. In general you probably want Dqn_Thread_GetScratch() +// which ensures you get a usable scratch arena for temporary allocations +// without having to worry about selecting the right arena from the state. +DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext(); #if defined(DQN_WITH_JSON_WRITER) -#if !defined(DQN_WITH_FIXED_ARRAY) - #error JsonWriter requires DQN_WITH_FIXED_ARRAY to be defined -#endif - #if !defined(DQN_WITH_FIXED_STRING) #error JsonWriter requires DQN_WITH_FIXED_STRING to be defined #endif @@ -1942,11 +1961,12 @@ DQN_API Dqn_ThreadScopedArena Dqn_GetThreadTempScopedArena(const Dqn_ThreadScope // TODO(dqn): We need to write tests for this struct Dqn_JsonWriter { - Dqn_FixedArray parent_field_count_stack; - Dqn_ArenaAllocator *arena; - Dqn_StringList list; - int indent_level; - int spaces_per_indent; + Dqn_u16 parent_field_count_stack[32]; + int parent_field_count_stack_size; + Dqn_ArenaAllocator *arena; + Dqn_StringList list; + int indent_level; + int spaces_per_indent; }; DQN_API Dqn_JsonWriter Dqn_JsonWriter_Init(Dqn_ArenaAllocator *arena, int spaces_per_indent); @@ -2009,9 +2029,9 @@ DQN_API Dqn_StringW Dqn_Win_ExecutableDirectoryW(Dqn_ArenaAllocator *arena); // size: (Optional) The size of the current directory string returned // suffix: (Optional) A suffix to append to the current working directory // suffix_size: (Optional) The size of the suffix to append -DQN_API Dqn_String Dqn_Win_CurrentDir (Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena, Dqn_String suffix); +DQN_API Dqn_String Dqn_Win_CurrentDir (Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena, Dqn_String suffix); DQN_API Dqn_StringW Dqn_Win_CurrentDirW (Dqn_ArenaAllocator *arena, Dqn_StringW suffix); -DQN_API Dqn_Array Dqn_Win_FolderFiles (Dqn_String path, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena); +DQN_API Dqn_Array Dqn_Win_FolderFiles (Dqn_String path, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena); DQN_API Dqn_Array Dqn_Win_FolderFilesW(Dqn_StringW path, Dqn_ArenaAllocator *arena); #endif // DQN_OS_WIN32 @@ -2473,172 +2493,6 @@ DQN_API Dqn_String Dqn_FixedString_ToString(Dqn_FixedString const *str) } #endif // DQN_WITH_FIXED_STRING -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_StringBuilder Template Implementation -// ------------------------------------------------------------------------------------------------- -// -// NOTE: Dqn_StringBuilder Internal Functions -// -template -DQN_API void Dqn_StringBuilder__LazyInitialise(Dqn_StringBuilder *builder) -{ - builder->fixed_mem_block.mem = builder->fixed_mem; - builder->fixed_mem_block.size = Dqn_ArrayCount(builder->fixed_mem); - builder->fixed_mem_block.used = 0; - builder->fixed_mem_block.next = nullptr; - builder->last_mem_block = &builder->fixed_mem_block; -} - -template -DQN_API char *Dqn_StringBuilder__AllocateWriteBuffer(Dqn_StringBuilder *builder, Dqn_isize size_required) -{ - if (!builder->fixed_mem_block.mem) - { - DQN_ASSERT(!builder->last_mem_block); - Dqn_StringBuilder__LazyInitialise(builder); - } - - Dqn_StringBuilderBlock *block = builder->last_mem_block; - Dqn_b32 new_block_needed = (block->size - block->used) < size_required; - if (new_block_needed) - { - Dqn_isize allocation_size = DQN_MAX(size_required, DQN_STRING_BUILDER_MIN_BLOCK_SIZE); - block = Dqn_ArenaAllocator_New(builder->arena, Dqn_StringBuilderBlock, Dqn_ZeroMem::No); - if (!block) return nullptr; - - *block = {}; - block->mem = DQN_CAST(char *)Dqn_ArenaAllocator_Allocate(builder->arena, allocation_size, alignof(char), Dqn_ZeroMem::No); - block->size = allocation_size; - builder->last_mem_block->next = block; - builder->last_mem_block = builder->last_mem_block->next; - } - - char *result = block->mem + block->used; - block->used += size_required; - return result; -} - -// -// NOTE: Dqn_StringBuilder Functions -// -template -DQN_API void Dqn_StringBuilder_InitWithArena(Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena) -{ - *builder = {}; - builder->arena = arena; - Dqn_StringBuilder__LazyInitialise(builder); -} - -template -DQN_API Dqn_isize Dqn_StringBuilder_GetSize(Dqn_StringBuilder const *builder) -{ - Dqn_isize result = 0; - for (Dqn_StringBuilderBlock const *block = &builder->fixed_mem_block; - block; - block = block->next) - { - result += block->used; - } - - return result; -} - -template -DQN_API void Dqn_StringBuilder_BuildToDest(Dqn_StringBuilder const *builder, char *dest, Dqn_usize dest_size) -{ - if (!dest) return; - if (dest_size == 1) { dest[0] = 0; return; } - - char *ptr = dest; - char const *end = dest + dest_size; - Dqn_isize remaining_space = end - ptr; - for (Dqn_StringBuilderBlock const *block = &builder->fixed_mem_block; - block && remaining_space > 0; - block = block->next, remaining_space = end - ptr) - { - Dqn_isize num_bytes = block->used; - Dqn_isize bytes_to_copy = DQN_MIN(num_bytes, remaining_space); - DQN_MEMCOPY(ptr, block->mem, bytes_to_copy); - ptr += bytes_to_copy; - } - - if (remaining_space > 0) ptr[ 0] = 0; // Append null terminator - else ptr[-1] = 0; // Oops ran out of space. Terminate the output prematurely. -} - -template -DQN_API char *Dqn_StringBuilder__Build(Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena, Dqn_isize *len DQN_CALL_SITE_ARGS) -{ - Dqn_isize len_ = 0; - if (!len) len = &len_; - *len = Dqn_StringBuilder_GetSize(builder); - auto *result = DQN_CAST(char *)Dqn_ArenaAllocator__Allocate(arena, *len + 1, alignof(char), Dqn_ZeroMem::No DQN_CALL_SITE_ARGS_INPUT); - Dqn_StringBuilder_BuildToDest(builder, result, *len + 1); - return result; -} - -template -DQN_API Dqn_String Dqn_StringBuilder_BuildStringWithArena(Dqn_StringBuilder *builder, Dqn_ArenaAllocator *arena) -{ - Dqn_isize size = 0; - char *string = Dqn_StringBuilder_Build(builder, arena, &size); - Dqn_String result = Dqn_String_Init(string, size); - return result; -} - -template -DQN_API void Dqn_StringBuilder_AppendFmtV(Dqn_StringBuilder *builder, char const *fmt, va_list va) -{ - if (!fmt) return; - va_list va2; - va_copy(va2, va); - Dqn_isize len = stbsp_vsnprintf(nullptr, 0, fmt, va); - char *buf = Dqn_StringBuilder__AllocateWriteBuffer(builder, len + 1); - stbsp_vsnprintf(buf, static_cast(len + 1), fmt, va2); - va_end(va2); - - DQN_ASSERT(builder->last_mem_block->used >= 0); - builder->last_mem_block->used--; // stbsp_vsnprintf null terminates, back out the null-terminator from the mem block -} - -template -DQN_API void Dqn_StringBuilder_AppendFmt(Dqn_StringBuilder *builder, char const *fmt, ...) -{ - va_list va; - va_start(va, fmt); - Dqn_StringBuilder_AppendFmtV(builder, fmt, va); - va_end(va); -} - -template -DQN_API void Dqn_StringBuilder_Append(Dqn_StringBuilder *builder, char const *str, Dqn_isize len) -{ - if (!str) return; - if (len == -1) len = DQN_CAST(Dqn_isize)Dqn_Str_Size(str); - if (len == 0) return; - char *buf = Dqn_StringBuilder__AllocateWriteBuffer(builder, len); - DQN_MEMCOPY(buf, str, len); -} - -template -DQN_API void Dqn_StringBuilder_AppendString(Dqn_StringBuilder *builder, Dqn_String const string) -{ - if (!string.str || string.size == 0) return; - char *buf = Dqn_StringBuilder__AllocateWriteBuffer(builder, string.size); - DQN_MEMCOPY(buf, string.str, string.size); -} - -template -DQN_API void Dqn_StringBuilder_AppendChar(Dqn_StringBuilder *builder, char ch) -{ - char *buf = Dqn_StringBuilder__AllocateWriteBuffer(builder, 1); - *buf++ = ch; -} - -#if defined(DQN_WITH_FIXED_ARRAY) -// ------------------------------------------------------------------------------------------------- -// NOTE: Dqn_FixedArray Template Implementation -// ------------------------------------------------------------------------------------------------- template void Dqn__EraseStableFromCArray(T *array, Dqn_isize size, Dqn_isize max, Dqn_isize index) { DQN_ASSERT(index >= 0 && index < size); @@ -2648,6 +2502,10 @@ template void Dqn__EraseStableFromCArray(T *array, Dqn_isize size, memmove(array + index, array + next_index, bytes_to_copy); } +#if defined(DQN_WITH_FIXED_ARRAY) +// ------------------------------------------------------------------------------------------------- +// NOTE: Dqn_FixedArray Template Implementation +// ------------------------------------------------------------------------------------------------- DQN_FIXED_ARRAY_TEMPLATE DQN_API DQN_FIXED_ARRAY_TEMPLATE_DECL Dqn_FixedArray_Init(T const *item, int num) { @@ -2799,8 +2657,13 @@ DQN_API Dqn_Array Dqn_Array_InitWithMemory(T *memory, Dqn_isize max, Dqn_isiz template DQN_API Dqn_Array Dqn_Array__InitWithArenaNoGrow(Dqn_ArenaAllocator *arena, Dqn_isize max, Dqn_isize size, Dqn_ZeroMem zero_mem DQN_CALL_SITE_ARGS) { - T *memory = DQN_CAST(T *)Dqn_ArenaAllocator__Allocate(arena, sizeof(T) * max, alignof(T), zero_mem DQN_CALL_SITE_ARGS_INPUT); - Dqn_Array result = Dqn_Array_InitWithMemory(memory, max, size); + Dqn_Array result = {}; + if (max && arena) + { + auto *memory = DQN_CAST(T *)Dqn_ArenaAllocator__Allocate(arena, sizeof(T) * max, alignof(T), zero_mem DQN_CALL_SITE_ARGS_INPUT); + result = Dqn_Array_InitWithMemory(memory, max, size); + } + return result; } @@ -2997,9 +2860,8 @@ Dqn_b32 Dqn_List_Iterate(Dqn_List *list, Dqn_ListIterator *iterator) #include // fprintf, FILE, stdout, stderr #if defined(DQN_OS_WIN32) - + #pragma comment(lib, "bcrypt") #if !defined(DQN_NO_WIN32_MINIMAL_HEADER) - #pragma comment(lib, "bcrypt") // Taken from Windows.h // --------------------------------------------------------------------- @@ -3043,6 +2905,7 @@ Dqn_b32 Dqn_List_Iterate(Dqn_List *list, Dqn_ListIterator *iterator) // NOTE: FindFirstFile #define INVALID_HANDLE_VALUE ((HANDLE)(long *)-1) + #define INVALID_FILE_ATTRIBUTES ((DWORD)-1) #define FIND_FIRST_EX_LARGE_FETCH 0x00000002 #define FILE_ATTRIBUTE_DIRECTORY 0x00000010 #define FILE_ATTRIBUTE_READONLY 0x00000001 @@ -3193,12 +3056,14 @@ Dqn_b32 Dqn_List_Iterate(Dqn_List *list, Dqn_ListIterator *iterator) HANDLE CreateThread (SECURITY_ATTRIBUTES *thread_attributes, size_t stack_size, DWORD (*start_function)(void *), void *user_context, DWORD creation_flags, DWORD *thread_id); HANDLE CreateSemaphoreA (SECURITY_ATTRIBUTES *security_attributes, long initial_count, long max_count, char *lpName); bool ReleaseSemaphore (HANDLE semaphore, long release_count, long *prev_count); + void Sleep (DWORD dwMilliseconds); void *VirtualAlloc (void *address, size_t size, DWORD allocation_type, DWORD protect); bool VirtualFree (void *address, size_t size, DWORD free_type); void GetSystemInfo (SYSTEM_INFO *system_info); void GetSystemTime (SYSTEMTIME *lpSystemTime); + void GetSystemTimeAsFileTime (FILETIME *lpFileTime); void GetLocalTime (SYSTEMTIME *lpSystemTime); DWORD FormatMessageA (DWORD flags, void *source, DWORD message_id, DWORD language_id, char *buffer, DWORD size, va_list *args); @@ -3219,7 +3084,7 @@ Dqn_b32 Dqn_List_Iterate(Dqn_List *list, Dqn_ListIterator *iterator) #include // getrandom #include // stat #include // sendfile - #include // clock_gettime + #include // clock_gettime, nanosleep #include // access #endif @@ -3284,7 +3149,7 @@ unsigned int Dqn_TicketMutex_MakeTicket(Dqn_TicketMutex *mutex) void Dqn_TicketMutex_BeginTicket(const Dqn_TicketMutex *mutex, unsigned int ticket) { - DQN_ASSERT_MSG(ticket <= mutex->serving, + DQN_ASSERT_MSG(mutex->serving <= ticket, "Mutex skipped ticket? Was ticket generated by the correct mutex via MakeTicket? ticket = %u, " "mutex->serving = %u", ticket, @@ -3357,30 +3222,65 @@ DQN_API void Dqn_LogVDefault(Dqn_LogType type, { (void)user_data; + // --------------------------------------------------------------------------------------------- + // NOTE: Ensure log-file is opened for appending, ultra-lightweight spinlock mutex + // --------------------------------------------------------------------------------------------- + Dqn_TicketMutex_Begin(&dqn__lib.log_file_mutex); + if (!dqn__lib.log_no_output_file) + { + if (!dqn__lib.log_file) + { + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(); + Dqn_String exe_dir = Dqn_OS_ExecutableDirectory(scratch.arena); + Dqn_String log_file = Dqn_String_Fmt(scratch.arena, "%.*s/dqn.log", DQN_STRING_FMT(exe_dir)); + dqn__lib.log_file = fopen(log_file.str, "a"); + } + } + Dqn_TicketMutex_End(&dqn__lib.log_file_mutex); + + // --------------------------------------------------------------------------------------------- + // NOTE: Extract file name from the path + // --------------------------------------------------------------------------------------------- Dqn_isize file_name_len = 0; char const *file_name = Dqn_Str_FileNameFromPath(file, file_len, &file_name_len); - Dqn_DateHMSTimeString time = Dqn_Date_HMSLocalTimeStringNow(); - FILE * handle = (type == Dqn_LogType::Error) ? stderr : stdout; - fprintf(handle, - "[%.*s|%.*s|%s|%.*s|%05u|%.*s] ", - time.date_size, time.date, - time.hms_size, time.hms, - Dqn_LogTypeString[DQN_CAST(int) type], - DQN_CAST(int)file_name_len, file_name, - line, - DQN_CAST(int)func_len, func); + // ----------------------------------------------------------------------------------------- + // NOTE: Construct log + // ----------------------------------------------------------------------------------------- + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(); + Dqn_DateHMSTimeString const time = Dqn_Date_HMSLocalTimeStringNow(); + Dqn_StringList string_list = {}; + Dqn_StringList_AppendFmt(&string_list, + scratch.arena, + "[%.*s|%.*s|%s|%.*s|%05u|%.*s] ", + time.date_size, + time.date, + time.hms_size, + time.hms, + Dqn_LogTypeString[DQN_CAST(int) type], + DQN_CAST(int) file_name_len, + file_name, + line, + DQN_CAST(int) func_len, + func); - // NOTE: Use the callback version of stb_sprintf to allow us to chunk logs and print arbitrary - // sized format strings without needing to size it up first. - auto stb_vsprintf_callback = [](char const *buf, void *user, int len) -> char * { - fprintf(DQN_CAST(FILE *)user, "%.*s", len, buf); - return DQN_CAST(char *)buf; + Dqn_StringList_AppendFmtV(&string_list, scratch.arena, fmt, va); + Dqn_String log_line = Dqn_StringList_Build(&string_list, scratch.arena); + + // ----------------------------------------------------------------------------------------- + // NOTE: Print log to destinations + // ----------------------------------------------------------------------------------------- + FILE *handles[] = + { + type == Dqn_LogType::Error ? stderr : stdout, + DQN_CAST(FILE *)dqn__lib.log_file, }; - char stb_buffer[STB_SPRINTF_MIN * 2] = {}; - STB_SPRINTF_DECORATE(vsprintfcb)(stb_vsprintf_callback, handle, stb_buffer, fmt, va); - fputc('\n', handle); + for (FILE *handle : handles) + { + if (handle) + fprintf(handle, "%.*s\n", DQN_STRING_FMT(log_line)); + } } DQN_API void Dqn_LogV(Dqn_LogType type, @@ -3434,75 +3334,6 @@ DQN_API Dqn_uintptr Dqn_AlignAddress(Dqn_uintptr address, Dqn_u8 alignment) return result; } -// ------------------------------------------------------------------------------------------------- -// -// Dqn_PointerMetadata -// -// ------------------------------------------------------------------------------------------------- -DQN_API Dqn_isize Dqn_PointerMetadata_SizeRequired(Dqn_isize size, Dqn_u8 alignment) -{ - DQN_ASSERT(alignment > 0); - if (alignment <= 0) alignment = 1; - Dqn_isize result = size + DQN_CAST(Dqn_i8)(alignment - 1) + DQN_CAST(Dqn_isize)sizeof(Dqn_PointerMetadata); - return result; -} - -DQN_API char *Dqn_PointerMetadata_Init(void *ptr, Dqn_isize size, Dqn_u8 alignment) -{ - DQN_ASSERT_MSG(alignment == 1 || (alignment & 1) == 0, "Alignment must be a power of 2, %u", alignment); - - // NOTE: Given a pointer, it can misaligned by up to (Alignment - 1) bytes. - // After calculating the offset to apply on the aligned ptr, we store the - // allocation metadata right before the ptr for convenience, so that we just - // need to walk back sizeof(metadata) bytes to get to the metadata from the - // pointer. - - // [Metadata Is Stored Here] - // [Raw Pointer] -> [Metadata Storage] [Unaligned Pointer] [Offset to Align Pointer] [Aligned Pointer] - - // NOTE: In the scenario where the pointer is already aligned after the - // [Metadata Storage] bytes, we use the bytes allocated for the metadata - // storage, instead of storing it into the offset storage bytes. - - // [Metadata Is Stored Here] - // [Raw Pointer] -> [Metadata Storage] [Aligned Pointer] - - // Offset is [0->Alignment-1] bytes from the Unaligned ptr. - auto raw_ptr = DQN_CAST(Dqn_uintptr) ptr; - auto unaligned_ptr = raw_ptr + sizeof(Dqn_PointerMetadata); - auto result = DQN_CAST(Dqn_uintptr) unaligned_ptr; - - if ((unaligned_ptr % alignment) > 0) - { - Dqn_uintptr unaligned_to_aligned_offset = alignment - (unaligned_ptr % alignment); - result += unaligned_to_aligned_offset; - } - DQN_ASSERT(result % alignment == 0); - DQN_ASSERT(result >= raw_ptr); - Dqn_intptr difference = DQN_CAST(Dqn_intptr)result - DQN_CAST(Dqn_intptr)raw_ptr; - DQN_ASSERT(difference <= DQN_CAST(Dqn_u8)-1); - - auto *metadata_ptr = DQN_CAST(Dqn_PointerMetadata *)(result - sizeof(Dqn_PointerMetadata)); - metadata_ptr->alignment = alignment; - metadata_ptr->offset = DQN_CAST(Dqn_u8)difference; - metadata_ptr->size = size; - return DQN_CAST(char *)result; -} - -DQN_API Dqn_PointerMetadata Dqn_PointerMetadata_Get(void *ptr) -{ - auto *aligned_ptr = DQN_CAST(char *) ptr; - auto result = *DQN_CAST(Dqn_PointerMetadata *)(aligned_ptr - sizeof(Dqn_PointerMetadata)); - return result; -} - -DQN_API char *Dqn_PointerMetadata_GetRawPointer(void *ptr) -{ - Dqn_PointerMetadata metadata = Dqn_PointerMetadata_Get(ptr); - char *result = DQN_CAST(char *) ptr - metadata.offset; - return result; -} - // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_AllocationTracer // ------------------------------------------------------------------------------------------------- @@ -3750,12 +3581,12 @@ DQN_API Dqn_b32 Dqn_String_AppendFmt(Dqn_String *str, char const *fmt, ...) return result; } -DQN_API Dqn_b32 Dqn_String_Eq(Dqn_String const lhs, Dqn_String const rhs, Dqn_StringEqCase eq_case) +DQN_API Dqn_b32 Dqn_String_Eq(Dqn_String const lhs, Dqn_String const rhs, Dqn_StringEq eq_case) { Dqn_b32 result = false; if (lhs.size == rhs.size) { - if (eq_case == Dqn_StringEqCase::Sensitive) + if (eq_case == Dqn_StringEq::Sensitive) result = (DQN_MEMCMP(lhs.str, rhs.str, DQN_CAST(size_t)lhs.size) == 0); else { @@ -3769,11 +3600,11 @@ DQN_API Dqn_b32 Dqn_String_Eq(Dqn_String const lhs, Dqn_String const rhs, Dqn_St DQN_API Dqn_b32 Dqn_String_EqInsensitive(Dqn_String const lhs, Dqn_String const rhs) { - Dqn_b32 result = Dqn_String_Eq(lhs, rhs, Dqn_StringEqCase::Insensitive); + Dqn_b32 result = Dqn_String_Eq(lhs, rhs, Dqn_StringEq::Insensitive); return result; } -DQN_API Dqn_b32 Dqn_String_StartsWith(Dqn_String string, Dqn_String prefix, Dqn_StringEqCase eq_case) +DQN_API Dqn_b32 Dqn_String_StartsWith(Dqn_String string, Dqn_String prefix, Dqn_StringEq eq_case) { Dqn_b32 result = false; if (prefix.size > string.size) @@ -3786,13 +3617,13 @@ DQN_API Dqn_b32 Dqn_String_StartsWith(Dqn_String string, Dqn_String prefix, Dqn_ DQN_API Dqn_b32 Dqn_String_StartsWithInsensitive(Dqn_String string, Dqn_String prefix) { - Dqn_b32 result = Dqn_String_StartsWith(string, prefix, Dqn_StringEqCase::Insensitive); + Dqn_b32 result = Dqn_String_StartsWith(string, prefix, Dqn_StringEq::Insensitive); return result; } DQN_API Dqn_b32 Dqn_String_EndsWith(Dqn_String string, Dqn_String suffix, - Dqn_StringEqCase eq_case) + Dqn_StringEq eq_case) { Dqn_b32 result = false; if (suffix.size > string.size) @@ -3805,7 +3636,7 @@ DQN_API Dqn_b32 Dqn_String_EndsWith(Dqn_String string, DQN_API Dqn_b32 Dqn_String_EndsWithInsensitive(Dqn_String string, Dqn_String suffix) { - Dqn_b32 result = Dqn_String_EndsWith(string, suffix, Dqn_StringEqCase::Insensitive); + Dqn_b32 result = Dqn_String_EndsWith(string, suffix, Dqn_StringEq::Insensitive); return result; } @@ -3819,7 +3650,6 @@ DQN_API Dqn_Array Dqn_String_Split(Dqn_String src, Dqn_ArenaAllocato }; Dqn_Array result = {}; - int split_index = 0; int split_count = 0; for (int stage = StringSplitStage_Enumerate; @@ -3849,16 +3679,16 @@ DQN_API Dqn_Array Dqn_String_Split(Dqn_String src, Dqn_ArenaAllocato else { if (stage == StringSplitStage_Enumerate) split_count++; - else result.data[split_index++] = split; + else result.data[result.size++] = split; } } } - DQN_ASSERT(split_count == split_index); + DQN_ASSERT(split_count == result.size); return result; } -DQN_API Dqn_String Dqn_String_TrimPrefix(Dqn_String str, Dqn_String prefix, Dqn_StringEqCase eq_case) +DQN_API Dqn_String Dqn_String_TrimPrefix(Dqn_String str, Dqn_String prefix, Dqn_StringEq eq_case) { Dqn_String result = str; if (Dqn_String_StartsWith(str, prefix, eq_case)) @@ -3871,7 +3701,7 @@ DQN_API Dqn_String Dqn_String_TrimPrefix(Dqn_String str, Dqn_String prefix, Dqn_ return result; } -DQN_API Dqn_String Dqn_String_TrimSuffix(Dqn_String str, Dqn_String suffix, Dqn_StringEqCase eq_case) +DQN_API Dqn_String Dqn_String_TrimSuffix(Dqn_String str, Dqn_String suffix, Dqn_StringEq eq_case) { Dqn_String result = str; if (Dqn_String_EndsWith(str, suffix, eq_case)) @@ -3883,9 +3713,19 @@ DQN_API Dqn_String Dqn_String_TrimSuffix(Dqn_String str, Dqn_String suffix, Dqn_ return result; } +DQN_API Dqn_String Dqn_String_TrimByteOrderMark(Dqn_String src) +{ + // TODO(dqn): This is little endian + auto UTF16_BOM = DQN_STRING("\xFF\xEF"); + auto UTF8_BOM = DQN_STRING("\xEF\xBB\xBF"); + Dqn_String result = Dqn_String_TrimPrefix(src, UTF16_BOM); + result = Dqn_String_TrimPrefix(result, UTF8_BOM); + return result; +} + DQN_API Dqn_b32 Dqn_String_IsAllDigits(Dqn_String src) { - if (!src.str) + if (!Dqn_String_IsValid(src)) return false; for (Dqn_isize ch_index = 0; ch_index < src.size; ch_index++) @@ -3893,7 +3733,137 @@ DQN_API Dqn_b32 Dqn_String_IsAllDigits(Dqn_String src) if (!(src.str[ch_index] >= '0' && src.str[ch_index] <= '9')) return false; } - Dqn_b32 result = src.str && src.size; + + return true; +} + +DQN_API Dqn_b32 Dqn_String_IsAllHex(Dqn_String src) +{ + if (!Dqn_String_IsValid(src)) + return false; + + Dqn_String src_no_0x = Dqn_String_TrimPrefix(src, DQN_STRING("0x"), Dqn_StringEq::Insensitive); + for (Dqn_isize ch_index = 0; ch_index < src_no_0x.size; ch_index++) + { + char ch = src_no_0x.str[ch_index]; + if (!((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'f') || (ch >= 'A' && ch <= 'F'))) + return false; + } + + return true; +} + +DQN_API Dqn_b32 Dqn_String_ContainsChar(Dqn_String src, char ch) +{ + // TODO(dqn): This is easily simd-able for heavy workloads + for (Dqn_isize ch_index = 0; ch_index < src.size; ch_index++) + { + if (src.str[ch_index] == ch) + return true; + } + + return false; +} + +DQN_API void Dqn_String_Remove(Dqn_String *in, Dqn_isize begin, Dqn_isize size) +{ + if (!in) + return; + + if (size > in->size) + return; + + char *dest = in->str + begin; + char const *src = in->str + (begin + size); + char const *end = in->str + in->size; + DQN_MEMMOVE(dest, src, end - src); + + in->size -= size; +} + +DQN_API Dqn_isize Dqn_String_Find(Dqn_String src, Dqn_String find, Dqn_isize start_index, Dqn_StringEq eq_case) +{ + Dqn_isize result = -1; + Dqn_isize src_end = src.size - find.size; + for (Dqn_isize index = start_index; index <= src_end; index++) + { + Dqn_String check = Dqn_String_Init(src.str + index, find.size); + if (Dqn_String_Eq(check, find, eq_case)) + { + result = index; + break; + } + } + + return result; +} + +DQN_API Dqn_String Dqn_String_Replace(Dqn_String src, Dqn_String find, Dqn_String replace, Dqn_isize start_index, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena, Dqn_StringEq eq_case) +{ + auto temp_arena_scope = Dqn_ArenaScope(temp_arena); + Dqn_StringList string_list = {}; + Dqn_isize src_end = src.size - find.size; + Dqn_isize head = start_index; + + for (Dqn_isize tail = head; tail <= src_end; tail++) + { + Dqn_String check = Dqn_String_Init(src.str + tail, find.size); + if (!Dqn_String_Eq(check, find, eq_case)) + continue; + + if (start_index > 0 && string_list.string_size == 0) + { + // User provided a hint in the string to start searching from, we + // need to add the string up to the hint. We only do this if there's + // a replacement action, otherwise we have a special case for no + // replacements, where the entire string gets copied. + Dqn_String string = Dqn_String_Init(src.str, head); + Dqn_StringList_AppendString(&string_list, temp_arena, string); + } + + Dqn_String range = Dqn_String_Init(src.str + head, (tail - head)); + Dqn_StringList_AppendString(&string_list, temp_arena, range); + Dqn_StringList_AppendString(&string_list, temp_arena, replace); + head = tail + find.size; + tail += find.size - 1; // NOTE: -1 since the for loop will post increment us past the end of the find string + } + + Dqn_String result = {}; + if (string_list.string_size == 0) + { + // NOTE: No replacement possible, so we just do a full-copy + result = Dqn_String_Copy(src, arena); + } + else + { + Dqn_String remainder = Dqn_String_Init(src.str + head, src.size - head); + Dqn_StringList_AppendString(&string_list, temp_arena, remainder); + result = Dqn_StringList_Build(&string_list, arena); + } + + return result; +} + +DQN_API Dqn_String Dqn_String_ReplaceInsensitive(Dqn_String src, Dqn_String find, Dqn_String replace, Dqn_isize start_index, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena) +{ + Dqn_String result = Dqn_String_Replace(src, find, replace, start_index, arena, temp_arena, Dqn_StringEq::Insensitive); + return result; +} + +DQN_API Dqn_String Dqn_String_FileNameFromPath(Dqn_String path) +{ + Dqn_String result = path; + for (Dqn_isize i = (result.size - 1); i >= 0; --i) + { + if (result.str[i] == '\\' || result.str[i] == '/') + { + char const *end = result.str + result.size; + result.str = result.str + (i + 1); + result.size = DQN_CAST(Dqn_isize)(end - result.str); + break; + } + } + return result; } @@ -3950,13 +3920,26 @@ DQN_API void Dqn_StringList_AppendFmt(Dqn_StringList *list, Dqn_ArenaAllocator * va_end(args); } -DQN_API void Dqn_StringList_AppendString(Dqn_StringList *list, Dqn_ArenaAllocator *arena, Dqn_String string) +DQN_API void Dqn_StringList_AppendStringCopy(Dqn_StringList *list, Dqn_ArenaAllocator *arena, Dqn_String string) { + if (string.size <= 0) + return; + Dqn_StringListNode *node = Dqn_StringList_MakeNode(arena, 0 /*size*/); node->string = Dqn_String_Copy(string, arena); Dqn_StringList_AddNode(list, node); } +DQN_API void Dqn_StringList_AppendString(Dqn_StringList *list, Dqn_ArenaAllocator *arena, Dqn_String string) +{ + if (string.size <= 0) + return; + + Dqn_StringListNode *node = Dqn_StringList_MakeNode(arena, 0 /*size*/); + node->string = string; + Dqn_StringList_AddNode(list, node); +} + DQN_API Dqn_String Dqn_StringList_Build(Dqn_StringList const *list, Dqn_ArenaAllocator *arena) { Dqn_String result = Dqn_String_Allocate(arena, list->string_size, Dqn_ZeroMem::No); @@ -4212,13 +4195,13 @@ DQN_API void Dqn_ArenaAllocator_EndScope(Dqn_ArenaScopeData scope) arena->current_stats = scope.current_stats; } -Dqn_ScopedArena::Dqn_ScopedArena(Dqn_ArenaAllocator *arena) +Dqn_ArenaScope::Dqn_ArenaScope(Dqn_ArenaAllocator *arena) { this->arena = arena; this->region = Dqn_ArenaAllocator_BeginScope(arena); } -Dqn_ScopedArena::~Dqn_ScopedArena() +Dqn_ArenaScope::~Dqn_ArenaScope() { Dqn_ArenaAllocator_EndScope(this->region); } @@ -4585,6 +4568,7 @@ DQN_API Dqn_M4 Dqn_M4_DivF(Dqn_M4 lhs, Dqn_f32 rhs) return result; } +#if defined(DQN_WITH_FIXED_STRING) DQN_API Dqn_FixedString<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat) { Dqn_FixedString<256> result = {}; @@ -4601,6 +4585,7 @@ DQN_API Dqn_FixedString<256> Dqn_M4_ColumnMajorString(Dqn_M4 mat) return result; } +#endif // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_Rect @@ -4956,6 +4941,81 @@ DQN_API char Dqn_Char_ToLower(char ch) return result; } +// ------------------------------------------------------------------------------------------------- +// NOTE: Dqn_UTF +// ------------------------------------------------------------------------------------------------- +DQN_API int Dqn_UTF8_EncodeCodepoint(Dqn_u8 utf8[4], Dqn_u32 codepoint) +{ + // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ + // ----------------------------------------+----------------------------+--------------------+ + // UTF-8 (binary) | Code point (binary) | Range | + // ----------------------------------------+----------------------------+--------------------+ + // 0xxx'xxxx | xxx'xxxx | U+0000 - U+007F | + // 110x'xxxx 10yy'yyyy | xxx'xxyy'yyyy | U+0080 - U+07FF | + // 1110'xxxx 10yy'yyyy 10zz'zzzz | xxxx'yyyy'yyzz'zzzz | U+0800 - U+FFFF | + // 1111'0xxx 10yy'yyyy 10zz'zzzz 10ww'wwww | x'xxyy'yyyy'zzzz'zzww'wwww | U+10000 - U+10FFFF | + // ----------------------------------------+----------------------------+--------------------+ + + if (codepoint <= 0b0111'1111) + { + utf8[0] = DQN_CAST(Dqn_u8)codepoint; + return 1; + } + + if (codepoint <= 0b0111'1111'1111) + { + utf8[0] = (0b1100'0000 | ((codepoint >> 6) & 0b01'1111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // y + return 2; + } + + if (codepoint <= 0b1111'1111'1111'1111) + { + utf8[0] = (0b1110'0000 | ((codepoint >> 12) & 0b00'1111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // z + return 3; + } + + if (codepoint <= 0b1'1111'1111'1111'1111'1111) + { + utf8[0] = (0b1111'0000 | ((codepoint >> 18) & 0b00'0111)); // x + utf8[1] = (0b1000'0000 | ((codepoint >> 12) & 0b11'1111)); // y + utf8[2] = (0b1000'0000 | ((codepoint >> 6) & 0b11'1111)); // z + utf8[3] = (0b1000'0000 | ((codepoint >> 0) & 0b11'1111)); // w + return 4; + } + + return 0; +} + +DQN_API int Dqn_UTF16_EncodeCodepoint(Dqn_u16 utf16[2], Dqn_u32 codepoint) +{ + // NOTE: Table from https://www.reedbeta.com/blog/programmers-intro-to-unicode/ + // ----------------------------------------+------------------------------------+------------------+ + // UTF-16 (binary) | Code point (binary) | Range | + // ----------------------------------------+------------------------------------+------------------+ + // xxxx'xxxx'xxxx'xxxx | xxxx'xxxx'xxxx'xxxx | U+0000–U+FFFF | + // 1101'10xx'xxxx'xxxx 1101'11yy'yyyy'yyyy | xxxx'xxxx'xxyy'yyyy'yyyy + 0x10000 | U+10000–U+10FFFF | + // ----------------------------------------+------------------------------------+------------------+ + + if (codepoint <= 0b1111'1111'1111'1111) + { + utf16[0] = DQN_CAST(Dqn_u16)codepoint; + return 1; + } + + if (codepoint <= 0b1111'1111'1111'1111'1111) + { + Dqn_u32 surrogate_codepoint = codepoint + 0x10000; + utf16[0] = 0b1101'1000'0000'0000 | ((codepoint >> 10) & 0b11'1111'1111); // x + utf16[1] = 0b1101'1100'0000'0000 | ((codepoint >> 0) & 0b11'1111'1111); // y + return 2; + } + + return 0; +} + #if defined(DQN_WITH_HEX) // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_Hex @@ -4981,51 +5041,6 @@ DQN_API Dqn_String Dqn_Hex_StringTrimSpaceAnd0xPrefix(Dqn_String const string) return result; } -DQN_API Dqn_u8 *Dqn_Hex_CStringToU8BytesUnchecked(char const *hex, Dqn_isize size, Dqn_isize *real_size, Dqn_ArenaAllocator *arena) -{ - Dqn_u8 *result = Dqn_ArenaAllocator_NewArray(arena, Dqn_u8, size / 2, Dqn_ZeroMem::No); - Dqn_u8 *ptr = result; - - Dqn_b32 even_size = ((size & 1) == 0); - DQN_ASSERT_MSG(even_size, "Unexpected uneven-size given for converting hex size: %d, hex: %.*s", size, size, hex); - - for (Dqn_isize index = 0; index < size; index += 2) - { - char hex01 = hex[index + 0]; - char hex02 = hex[index + 1]; - DQN_ASSERT_MSG(Dqn_Char_IsHex(hex01), "hex01: %c", hex01); - DQN_ASSERT_MSG(Dqn_Char_IsHex(hex02), "hex02: %c", hex02); - - char value01 = Dqn_Char_HexToU8(hex01); - char value02 = Dqn_Char_HexToU8(hex02); - char value = (value01 << 4) | value02; - *ptr++ = value; - } - - Dqn_u8 const *end = result + size / 2; - DQN_ASSERT_MSG(ptr == end, "ptr: %p, end: %p", ptr, end); - - if (real_size) *real_size = (size / 2); - return result; -} - -DQN_API Dqn_Array Dqn_Hex_CStringToU8ArrayUnchecked(char const *hex, Dqn_isize size, Dqn_ArenaAllocator *arena) -{ - Dqn_isize data_size = 0; - auto *data = DQN_CAST(Dqn_u8 *)Dqn_Hex_CStringToU8BytesUnchecked(hex, size, &data_size, arena); - - Dqn_Array result = Dqn_Array_InitWithMemory(data, data_size, data_size); - return result; -} - -DQN_API Dqn_Array Dqn_Hex_StringToU8ArrayUnchecked(Dqn_String const hex, Dqn_ArenaAllocator *arena) -{ - Dqn_isize data_size = 0; - auto *data = DQN_CAST(Dqn_u8 *)Dqn_Hex_CStringToU8BytesUnchecked(hex.str, hex.size, &data_size, arena); - Dqn_Array result = Dqn_Array_InitWithMemory(data, data_size, data_size); - return result; -} - DQN_API Dqn_u64 Dqn_Hex_CStringToU64(char const *hex, Dqn_isize size) { Dqn_isize trim_size = size; @@ -5062,33 +5077,114 @@ DQN_API Dqn_u64 Dqn_Hex_StringToU64(Dqn_String hex) return result; } -DQN_API char *Dqn_Hex_U8BytesToCString(char const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena) +DQN_API void Dqn_Hex_BytesToHex(void const *src, int src_size, char *dest, int dest_size) { - char *result = size > 0 ? Dqn_ArenaAllocator_NewArray(arena, char, size * 2, Dqn_ZeroMem::No) : nullptr; + (void)src_size; (void)dest_size; + DQN_ASSERT(dest_size >= src_size * 2); + + unsigned char *src_u8 = DQN_CAST(unsigned char *)src; + for (int src_index = 0, dest_index = 0; + src_index < src_size; + src_index += 1, dest_index += 2) + { + char byte = src_u8[src_index]; + char hex01 = (byte >> 4) & 0b1111; + char hex02 = (byte >> 0) & 0b1111; + dest[dest_index + 0] = hex01 < 10 ? (hex01 + '0') : (hex01 - 10) + 'a'; + dest[dest_index + 1] = hex02 < 10 ? (hex02 + '0') : (hex02 - 10) + 'a'; + } +} + +DQN_API void Dqn_Hex_HexToBytes(char const *hex, int hex_size, void *dest, int dest_size) +{ + (void)hex_size; (void)dest_size; + DQN_ASSERT(dest_size >= DQN_CAST(int)((hex_size / 2.f) + .5f)); + + unsigned char *dest_u8 = DQN_CAST(unsigned char *)dest; + for (int hex_index = 0, dest_index = 0; + hex_index < hex_size; + hex_index += 2, dest_index += 1) + { + char hex01 = hex[hex_index]; + char hex02 = (hex_index + 1 < hex_size) ? hex[hex_index + 1] : 0; + + char bit4_01 = (hex01 >= '0' && hex01 <= '9') ? 0 + (hex01 - '0') + : (hex01 >= 'a' && hex01 <= 'f') ? 10 + (hex01 - 'a') + : (hex01 >= 'A' && hex01 <= 'F') ? 10 + (hex01 - 'A') + : 0; + + char bit4_02 = (hex02 >= '0' && hex02 <= '9') ? 0 + (hex02 - '0') + : (hex02 >= 'a' && hex02 <= 'f') ? 10 + (hex02 - 'a') + : (hex02 >= 'A' && hex02 <= 'F') ? 10 + (hex02 - 'A') + : 0; + + char byte = (bit4_01 << 4) | (bit4_02 << 0); + dest_u8[dest_index] = byte; + } +} + +DQN_API char *Dqn_Hex_BytesToHexCStringArena(void const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena) +{ + char *result = size > 0 ? Dqn_ArenaAllocator_NewArray(arena, char, (size * 2) + 1 /*null terminate*/, Dqn_ZeroMem::No) : nullptr; if (result) { - char *ptr = result; - for (Dqn_isize index = 0; index < size; index++) - { - char byte = bytes[index]; - char hex01 = (byte >> 4) & 0xF; - char hex02 = byte & 0xF; - DQN_ASSERT_MSG(hex01 <= 0xF, "hex01: %d", hex01); - DQN_ASSERT_MSG(hex02 <= 0xF, "hex02: %d", hex02); - *ptr++ = Dqn_Char_ToHexUnchecked(hex01); - *ptr++ = Dqn_Char_ToHexUnchecked(hex02); - } + Dqn_Hex_BytesToHex(bytes, size, result, size * 2); + result[size * 2] = 0; } return result; } -DQN_API Dqn_String Dqn_Hex_U8ArrayToString(Dqn_Array const bytes, Dqn_ArenaAllocator *arena) +DQN_API Dqn_String Dqn_Hex_BytesToHexStringArena(void const *bytes, Dqn_isize size, Dqn_ArenaAllocator *arena) { - char *hex = Dqn_Hex_U8BytesToCString(DQN_CAST(char const *)bytes.data, bytes.size, arena); - Dqn_String result = Dqn_String_Init(hex, bytes.size * 2); + char *c_string = Dqn_Hex_BytesToHexCStringArena(bytes, size, arena); + Dqn_String result = {}; + if (c_string) + result = Dqn_String_Init(c_string, size * 2); return result; } + +DQN_API char *Dqn_Hex_U8ArrayToHexCStringArena(Dqn_Array const bytes, Dqn_ArenaAllocator *arena) +{ + char *result = Dqn_Hex_BytesToHexCStringArena(bytes.data, bytes.size, arena); + return result; +} + +DQN_API Dqn_String Dqn_Hex_U8ArrayToHexStringArena(Dqn_Array const bytes, Dqn_ArenaAllocator *arena) +{ + Dqn_String result = Dqn_Hex_BytesToHexStringArena(bytes.data, bytes.size, arena); + return result; +} + +DQN_API Dqn_u8 *Dqn_Hex_HexCStringToU8Bytes(char const *hex, Dqn_isize size, Dqn_isize *real_size, Dqn_ArenaAllocator *arena) +{ + hex = Dqn_Hex_CStringTrimSpaceAnd0xPrefix(hex, size, &size); + Dqn_isize binary_size = DQN_CAST(Dqn_isize)(size / 2.f + .5f); + Dqn_u8 *result = Dqn_ArenaAllocator_NewArray(arena, Dqn_u8, binary_size, Dqn_ZeroMem::No); + if (result) + Dqn_Hex_HexToBytes(hex, size, result, binary_size); + + if (real_size) *real_size = binary_size; + return result; +} + +DQN_API Dqn_Array Dqn_Hex_HexCStringToU8Array(char const *hex, Dqn_isize size, Dqn_ArenaAllocator *arena) +{ + Dqn_isize data_size = 0; + auto *data = DQN_CAST(Dqn_u8 *)Dqn_Hex_HexCStringToU8Bytes(hex, size, &data_size, arena); + Dqn_Array result = Dqn_Array_InitWithMemory(data, data_size, data_size); + return result; +} + +DQN_API Dqn_Array Dqn_Hex_HexStringToU8Array(Dqn_String const hex, Dqn_ArenaAllocator *arena) +{ + Dqn_isize data_size = 0; + auto *data = DQN_CAST(Dqn_u8 *)Dqn_Hex_HexCStringToU8Bytes(hex.str, hex.size, &data_size, arena); + Dqn_Array result = Dqn_Array_InitWithMemory(data, data_size, data_size); + return result; +} + + #endif // DQN_WITH_HEX // ------------------------------------------------------------------------------------------------- @@ -5303,8 +5399,8 @@ DQN_API char const *Dqn_Str_TrimWhitespaceAround(char const *src, Dqn_isize size char const *start = result; char const *end = start + (size - 1); - while (Dqn_Char_IsWhitespace(start[0])) start++; - while (end != start && Dqn_Char_IsWhitespace(end[0])) end--; + while (start <= end && Dqn_Char_IsWhitespace(start[0])) start++; + while (end > start && Dqn_Char_IsWhitespace(end[0])) end--; result = start; if (new_size) *new_size = ((end - start) + 1); @@ -5359,12 +5455,13 @@ DQN_API Dqn_u64 Dqn_Str_ToU64(char const *buf, int size, char separator) if (ch < '0' || ch > '9') break; + if (buf_index) + result = Dqn_Safe_MulU64(result, 10); + Dqn_u64 val = DQN_CAST(Dqn_u64)(ch - '0'); result = Dqn_Safe_AddU64(result, val); - result = Dqn_Safe_MulU64(result, 10); } - result /= 10; return result; } @@ -5388,15 +5485,19 @@ DQN_API Dqn_i64 Dqn_Str_ToI64(char const *buf, int size, char separator) for (int buf_index = 0; buf_index < size; ++buf_index) { char ch = buf_ptr[buf_index]; - if (buf_index && ch == separator) continue; - if (ch < '0' || ch > '9') break; + if (buf_index && ch == separator) + continue; + + if (ch < '0' || ch > '9') + break; + + if (buf_index) + result = Dqn_Safe_MulU64(result, 10); Dqn_i64 val = ch - '0'; - result = Dqn_Safe_AddI64(result, val); - result = Dqn_Safe_MulI64(result, 10); + result = Dqn_Safe_AddI64(result, val); } - result /= 10; if (negative) result *= -1; return result; } @@ -5517,7 +5618,10 @@ DQN_API Dqn_b32 Dqn_File_Exists(Dqn_String path) WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; if (GetFileAttributesExW(path_w, GetFileExInfoStandard, &attrib_data)) - result = (attrib_data.dwFileAttributes & ~FILE_ATTRIBUTE_DIRECTORY); + { + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + !(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } #elif defined(DQN_OS_UNIX) struct stat stat_result; @@ -5541,7 +5645,10 @@ DQN_API Dqn_b32 Dqn_File_DirExists(Dqn_String path) WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; if (GetFileAttributesExW(path_w, GetFileExInfoStandard, &attrib_data)) - result = (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + { + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } #elif defined(DQN_OS_UNIX) struct stat stat_result; @@ -5641,10 +5748,11 @@ DQN_API Dqn_b32 Dqn_File_Copy(Dqn_String src, Dqn_String dest, Dqn_b32 overwrite return result; } -DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *tmp_arena) +DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *temp_arena) { - Dqn_b32 result = true; - Dqn_FixedArray path_indexes = {}; + Dqn_b32 result = true; + int path_indexes_size = 0; + Dqn_u16 path_indexes[64] = {}; #if defined(DQN_OS_WIN32) wchar_t src_w[DQN_OS_WIN32_MAX_PATH]; @@ -5669,11 +5777,11 @@ DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *tmp_arena) if (ch == '/' || ch == '\\' || first_char) { WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - wchar_t tmp = src_w[index]; + wchar_t temp = src_w[index]; if (!first_char) src_w[index] = 0; // Temporarily null terminate it Dqn_b32 successful = GetFileAttributesExW(src_w, GetFileExInfoStandard, &attrib_data); // Check - if (!first_char) src_w[index] = tmp; // Undo null termination + if (!first_char) src_w[index] = temp; // Undo null termination if (successful) { @@ -5696,24 +5804,24 @@ DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *tmp_arena) { // NOTE: There's nothing that exists at this path, we can create // a directory here - Dqn_FixedArray_Add(&path_indexes, DQN_CAST(Dqn_u16)index); + path_indexes[path_indexes_size++] = DQN_CAST(Dqn_u16)index; } } } - for (Dqn_isize index = path_indexes.size - 1; index >= 0 && result; index--) + for (Dqn_isize index = path_indexes_size - 1; index >= 0 && result; index--) { Dqn_u16 path_index = path_indexes[index]; - wchar_t tmp = src_w[path_index]; + wchar_t temp = src_w[path_index]; if (index != 0) src_w[path_index] = 0; result |= (CreateDirectoryW(src_w, nullptr) == 0); - if (index != 0) src_w[path_index] = tmp; + if (index != 0) src_w[path_index] = temp; } #elif defined(DQN_OS_UNIX) - auto scoped_arena = Dqn_ScopedArena(tmp_arena); - Dqn_String copy = Dqn_String_Copy(path, tmp_arena); + auto scoped_arena = Dqn_ArenaScope(temp_arena); + Dqn_String copy = Dqn_String_Copy(path, temp_arena); for (Dqn_i32 index = copy.size - 1; index >= 0; index--) { @@ -5721,10 +5829,10 @@ DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *tmp_arena) wchar_t ch = copy.str[index]; if (ch == '/' || first_char) { - char tmp = copy.str[index]; + char temp = copy.str[index]; if (!first_char) copy.str[index] = 0; // Temporarily null terminate it Dqn_b32 is_file = Dqn_File_Exists(copy); - if (!first_char) copy.str[index] = tmp; // Undo null termination + if (!first_char) copy.str[index] = temp; // Undo null termination if (is_file) { @@ -5746,20 +5854,20 @@ DQN_API Dqn_b32 Dqn_File_MakeDir(Dqn_String path, Dqn_ArenaAllocator *tmp_arena) { // NOTE: There's nothing that exists at this path, we can // create a directory here - Dqn_FixedArray_Add(&path_indexes, DQN_CAST(Dqn_u16)index); + path_indexes[path_indexes_size++] = DQN_CAST(Dqn_u16)index; } } } } - for (Dqn_isize index = path_indexes.size - 1; index >= 0 && result; index--) + for (Dqn_isize index = path_indexes_size - 1; index >= 0 && result; index--) { Dqn_u16 path_index = path_indexes[index]; - char tmp = copy.str[path_index]; + char temp = copy.str[path_index]; if (index != 0) copy.str[path_index] = 0; result |= mkdir(copy.str, 0774) == 0; - if (index != 0) copy.str[path_index] = tmp; + if (index != 0) copy.str[path_index] = temp; } #else @@ -5896,6 +6004,29 @@ DQN_API Dqn_DateHMSTimeString Dqn_Date_HMSLocalTimeStringNow(char date_separator return result; } +DQN_API Dqn_u64 Dqn_Date_EpochTime() +{ +#if defined(DQN_OS_WIN32) + const Dqn_i64 UNIX_TIME_START = 0x019DB1DED53E8000; //January 1, 1970 (start of Unix epoch) in "ticks" + const Dqn_i64 TICKS_PER_SECOND = 10'000'000; // Filetime returned is in intervals of 100 nanoseconds + + FILETIME file_time; + GetSystemTimeAsFileTime(&file_time); + + LARGE_INTEGER date_time; + date_time.LowPart = file_time.dwLowDateTime; + date_time.HighPart = file_time.dwHighDateTime; + Dqn_u64 result = (date_time.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND; + +#elif defined(DQN_OS_UNIX) + Dqn_u64 result = time(nullptr); +#else + #error Unimplemented +#endif + + return result; +} + // ------------------------------------------------------------------------------------------------- // NOTE: OS // ------------------------------------------------------------------------------------------------- @@ -5908,15 +6039,21 @@ DQN_API Dqn_b32 Dqn_OS_SecureRNGBytes(void *buffer, Dqn_isize size) return true; #if defined(DQN_OS_WIN32) + bool init = true; + Dqn_TicketMutex_Begin(&dqn__lib.win32_bcrypt_rng_mutex); if (!dqn__lib.win32_bcrypt_rng_handle) { NTSTATUS init_status = BCryptOpenAlgorithmProvider(&dqn__lib.win32_bcrypt_rng_handle, BCRYPT_RNG_ALGORITHM, nullptr /*implementation*/, 0 /*flags*/); if (!dqn__lib.win32_bcrypt_rng_handle || init_status != 0) { DQN_LOG_E("Failed to initialise random number generator, error: %d", init_status); - return false; + init = false; } } + Dqn_TicketMutex_End(&dqn__lib.win32_bcrypt_rng_mutex); + + if (!init) + return false; NTSTATUS gen_status = BCryptGenRandom(dqn__lib.win32_bcrypt_rng_handle, DQN_CAST(unsigned char *)buffer, size, 0 /*flags*/); if (gen_status != 0) @@ -5948,9 +6085,9 @@ DQN_API Dqn_String Dqn_OS_ExecutableDirectory(Dqn_ArenaAllocator *arena) Dqn_String result = {}; #if defined(DQN_OS_WIN32) - char tmp_mem[sizeof(wchar_t) * DQN_OS_WIN32_MAX_PATH + sizeof(Dqn_ArenaMemBlock)]; - Dqn_ArenaAllocator tmp_arena = Dqn_ArenaAllocator_InitWithMemory(tmp_mem, Dqn_ArrayCountI(tmp_mem)); - Dqn_StringW exe_dir_w = Dqn_Win_ExecutableDirectoryW(&tmp_arena); + char temp_mem[sizeof(wchar_t) * DQN_OS_WIN32_MAX_PATH + sizeof(Dqn_ArenaMemBlock)]; + Dqn_ArenaAllocator temp_arena = Dqn_ArenaAllocator_InitWithMemory(temp_mem, Dqn_ArrayCountI(temp_mem)); + Dqn_StringW exe_dir_w = Dqn_Win_ExecutableDirectoryW(&temp_arena); result = Dqn_Win_ArenaWCharToUTF8(exe_dir_w, arena); #elif defined(DQN_OS_UNIX) @@ -5960,7 +6097,7 @@ DQN_API Dqn_String Dqn_OS_ExecutableDirectory(Dqn_ArenaAllocator *arena) ; try_size *= 2) { - auto scoped_arena = Dqn_ScopedArena(arena); + auto scoped_arena = Dqn_ArenaScope(arena); char *try_buf = Dqn_ArenaAllocator_NewArray(arena, char, try_size, Dqn_ZeroMem::No); int bytes_written = readlink("/proc/self/exe", try_buf, try_size); if (bytes_written == -1) @@ -6036,6 +6173,18 @@ DQN_API Dqn_String Dqn_OS_ExecutableDirectory(Dqn_ArenaAllocator *arena) // ------------------------------------------------------------------------------------------------- // NOTE: Utilities // ------------------------------------------------------------------------------------------------- +DQN_API void Dqn_SleepMs(Dqn_uint milliseconds) +{ +#if defined(DQN_OS_WIN32) + Sleep(milliseconds); +#else + struct timespec ts; + ts.tv_sec = milliseconds / 1000; + ts.tv_nsec = (milliseconds % 1000) * 1'000'000; + nanosleep(&ts, nullptr); +#endif +} + DQN_FILE_SCOPE void Dqn_PerfCounter__Init() { #if defined(DQN_OS_WIN32) @@ -6190,7 +6339,7 @@ DQN_API char *Dqn_U64ToTempStr(Dqn_u64 val, Dqn_b32 comma_sep) DQN_API void Dqn_Lib_DumpThreadContextArenaStats(Dqn_String file_path) { (void)file_path; -#if defined(DQN_WITH_THREAD_CONTEXT) && defined(DQN_DEBUG_THREAD_CONTEXT) +#if defined(DQN_DEBUG_THREAD_CONTEXT) FILE *file = fopen(file_path.str, "a+b"); if (file) { @@ -6206,7 +6355,7 @@ DQN_API void Dqn_Lib_DumpThreadContextArenaStats(Dqn_String file_path) Dqn_TicketMutex_Begin(&dqn__lib.thread_context_mutex); stats_size = dqn__lib.thread_context_arena_stats_size; DQN_MEMCOPY(current_stats, dqn__lib.thread_context_arena_current_stats, sizeof(current_stats[0]) * stats_size); - DQN_MEMCOPY(highest_stats, dqn__lib.thread_context_arena_current_stats, sizeof(highest_stats[0]) * stats_size); + DQN_MEMCOPY(highest_stats, dqn__lib.thread_context_arena_highest_stats, sizeof(highest_stats[0]) * stats_size); Dqn_TicketMutex_End(&dqn__lib.thread_context_mutex); // --------------------------------------------------------------------- @@ -6267,7 +6416,7 @@ DQN_API void Dqn_Lib_DumpThreadContextArenaStats(Dqn_String file_path) { DQN_LOG_E("Failed to dump thread context arenas to %.*s", DQN_STRING_FMT(file_path)); } -#endif // #if defined(DQN_WITH_THREAD_CONTEXT) && defined(DQN_DEBUG_THREAD_CONTEXT) +#endif // #if defined(DQN_DEBUG_THREAD_CONTEXT) } DQN_API void Dqn_Lib_SetLogCallback(Dqn_LogProc *proc, void *user_data) @@ -6276,25 +6425,32 @@ DQN_API void Dqn_Lib_SetLogCallback(Dqn_LogProc *proc, void *user_data) dqn__lib.log_user_data = user_data; } +DQN_API void Dqn_Lib_SetLogFile(FILE *file) +{ + Dqn_TicketMutex_Begin(&dqn__lib.log_file_mutex); + dqn__lib.log_file = file; + dqn__lib.log_no_output_file = file ? false : true; + Dqn_TicketMutex_End(&dqn__lib.log_file_mutex); +} + // ------------------------------------------------------------------------------------------------- // NOTE: Dqn_ThreadContext // ------------------------------------------------------------------------------------------------- -#if defined(DQN_WITH_THREAD_CONTEXT) -Dqn_ThreadContext *Dqn_GetThreadContext() +DQN_API Dqn_ThreadContext *Dqn_Thread_GetContext() { thread_local Dqn_ThreadContext result = {}; if (!result.init) { result.init = true; - for (Dqn_ThreadArena &thread_arena : result.arenas) + for (Dqn_ThreadScratchData &scratch_data : result.scratch_data) { - thread_arena.arena = Dqn_ArenaAllocator_InitWithCRT(DQN_KILOBYTES(4)); + scratch_data.arena = Dqn_ArenaAllocator_InitWithCRT(DQN_MEGABYTES(4)); #if defined(DQN_DEBUG_THREAD_CONTEXT) // NOTE: Allocate this arena a slot in the stats array that we use // to record allocation statistics for each thread's arena. Dqn_TicketMutex_Begin(&dqn__lib.thread_context_mutex); - thread_arena.stats_index = dqn__lib.thread_context_arena_stats_size++; + scratch_data.arena_stats_index = dqn__lib.thread_context_arena_stats_size++; Dqn_TicketMutex_End(&dqn__lib.thread_context_mutex); DQN_HARD_ASSERT(dqn__lib.thread_context_arena_stats_size < Dqn_ArrayCountI(dqn__lib.thread_context_arena_current_stats)); #endif @@ -6304,23 +6460,22 @@ Dqn_ThreadContext *Dqn_GetThreadContext() return &result; } -Dqn_ThreadScopedArena Dqn_GetThreadTempScopedArena(const Dqn_ThreadScopedArena *conflict_scope_arena) +DQN_API Dqn_ThreadScratch Dqn_Thread_GetScratch(const Dqn_ArenaAllocator *conflict_arena) { - Dqn_ThreadContext *thread = Dqn_GetThreadContext(); - Dqn_ThreadArena *available_arena = nullptr; - for (Dqn_ThreadArena &thread_arena : thread->arenas) + Dqn_ThreadContext *thread = Dqn_Thread_GetContext(); + Dqn_ThreadScratchData *result = nullptr; + for (Dqn_ThreadScratchData &scratch_data : thread->scratch_data) { - if (!conflict_scope_arena || (&thread_arena.arena != conflict_scope_arena->arena)) + if (!conflict_arena || (&scratch_data.arena != conflict_arena)) { - available_arena = &thread_arena; + result = &scratch_data; break; } } - DQN_HARD_ASSERT(available_arena); - return Dqn_ThreadScopedArena(available_arena); + DQN_HARD_ASSERT(result); + return Dqn_ThreadScratch(result); } -#endif // DQN_WITH_THREAD_CONTEXT #if defined(DQN_WITH_JSON_WRITER) // ------------------------------------------------------------------------------------------------- @@ -6350,10 +6505,10 @@ DQN_API void Dqn_JsonWriter__DoIndent(Dqn_JsonWriter *writer) DQN_API void Dqn_JsonWriter__PreAddItem(Dqn_JsonWriter *writer) { - if (writer->parent_field_count_stack.size <= 0) + if (writer->parent_field_count_stack_size <= 0) return; - Dqn_u16 *parent_field_count = Dqn_FixedArray_Peek(&writer->parent_field_count_stack); + Dqn_u16 *parent_field_count = &writer->parent_field_count_stack[writer->parent_field_count_stack_size - 1]; if (*parent_field_count == 0) { // NOTE: First time we're adding an item to an object, we need to write @@ -6372,10 +6527,10 @@ DQN_API void Dqn_JsonWriter__PreAddItem(Dqn_JsonWriter *writer) DQN_API void Dqn_JsonWriter__PostAddItem(Dqn_JsonWriter *writer) { - if (writer->parent_field_count_stack.size <= 0) + if (writer->parent_field_count_stack_size <= 0) return; - Dqn_u16 *parent_field_count = Dqn_FixedArray_Peek(&writer->parent_field_count_stack); + Dqn_u16 *parent_field_count = &writer->parent_field_count_stack[writer->parent_field_count_stack_size - 1]; (*parent_field_count)++; } @@ -6390,20 +6545,25 @@ DQN_API void Dqn_JsonWriter__BeginContainer(Dqn_JsonWriter *writer, Dqn_String n Dqn_JsonWriter__PostAddItem(writer); writer->indent_level++; - Dqn_u16 *parent_field_count = Dqn_FixedArray_Make(&writer->parent_field_count_stack, 1); + + DQN_ASSERT(writer->parent_field_count_stack_size < DQN_ARRAY_COUNT(writer->parent_field_count_stack)); + Dqn_u16 *parent_field_count = &writer->parent_field_count_stack[writer->parent_field_count_stack_size++]; *parent_field_count = 0; + } DQN_API void Dqn_JsonWriter__EndContainer(Dqn_JsonWriter *writer, Dqn_b32 array) { - Dqn_u16 *parent_field_count = Dqn_FixedArray_Peek(&writer->parent_field_count_stack); + Dqn_u16 *parent_field_count = &writer->parent_field_count_stack[writer->parent_field_count_stack_size - 1]; if (*parent_field_count > 0) { // NOTE: End of object/array should start on a new line if the // array/object has atleast one field in it. Dqn_StringList_AppendFmt(&writer->list, writer->arena, "\n"); } - Dqn_FixedArray_Pop(&writer->parent_field_count_stack); + + writer->parent_field_count_stack_size--; + DQN_ASSERT(writer->parent_field_count_stack_size >= 0); writer->indent_level--; DQN_ASSERT(writer->indent_level >= 0); @@ -6675,10 +6835,10 @@ DQN_API Dqn_StringW Dqn_Win_ExecutableDirectoryW(Dqn_ArenaAllocator *arena) return result; } -DQN_API Dqn_String Dqn_Win_CurrentDir(Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena, Dqn_String suffix) +DQN_API Dqn_String Dqn_Win_CurrentDir(Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena, Dqn_String suffix) { - Dqn_StringW w_suffix = Dqn_Win_ArenaUTF8ToWChar(suffix, tmp_arena); - Dqn_StringW curr_dir = Dqn_Win_CurrentDirW(tmp_arena, w_suffix); + Dqn_StringW w_suffix = Dqn_Win_ArenaUTF8ToWChar(suffix, temp_arena); + Dqn_StringW curr_dir = Dqn_Win_CurrentDirW(temp_arena, w_suffix); Dqn_String result = Dqn_Win_ArenaWCharToUTF8(curr_dir, arena); return result; } @@ -6691,7 +6851,7 @@ DQN_API Dqn_StringW Dqn_Win_CurrentDirW(Dqn_ArenaAllocator *arena, Dqn_StringW s // NOTE: required_size is the size required *including* the null-terminator DWORD required_size = GetCurrentDirectoryW(0, nullptr); DWORD desired_size = required_size + DQN_CAST(DWORD) suffix.size; - Dqn_ArenaScopeData tmp_state = Dqn_ArenaAllocator_BeginScope(arena); + Dqn_ArenaScopeData temp_state = Dqn_ArenaAllocator_BeginScope(arena); wchar_t *w_path = Dqn_ArenaAllocator_NewArray(arena, wchar_t, desired_size, Dqn_ZeroMem::No); if (!w_path) @@ -6701,7 +6861,7 @@ DQN_API Dqn_StringW Dqn_Win_CurrentDirW(Dqn_ArenaAllocator *arena, Dqn_StringW s if ((bytes_written_wo_null_terminator + 1) != required_size) { // TODO(dqn): Error - Dqn_ArenaAllocator_EndScope(tmp_state); // Undo allocations + Dqn_ArenaAllocator_EndScope(temp_state); // Undo allocations return result; } @@ -6772,11 +6932,11 @@ DQN_API Dqn_Array Dqn_Win_FolderFilesW(Dqn_StringW path, Dqn_ArenaA return result; } -DQN_API Dqn_Array Dqn_Win_FolderFiles(Dqn_String path, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *tmp_arena) +DQN_API Dqn_Array Dqn_Win_FolderFiles(Dqn_String path, Dqn_ArenaAllocator *arena, Dqn_ArenaAllocator *temp_arena) { - auto tmp_scoped_arena = Dqn_ScopedArena(tmp_arena); - Dqn_StringW w_path = Dqn_Win_ArenaUTF8ToWChar(path, tmp_arena); - Dqn_Array files = Dqn_Win_FolderFilesW(w_path, tmp_arena); + auto arena_scope = Dqn_ArenaScope(temp_arena); + Dqn_StringW w_path = Dqn_Win_ArenaUTF8ToWChar(path, temp_arena); + Dqn_Array files = Dqn_Win_FolderFilesW(w_path, temp_arena); Dqn_Array result = Dqn_Array_InitWithArenaNoGrow(arena, Dqn_String, files.size, 0, Dqn_ZeroMem::No); for (Dqn_StringW file : files) diff --git a/Dqn_Curl.h b/Dqn_Curl.h index 0826a44..8797b9b 100644 --- a/Dqn_Curl.h +++ b/Dqn_Curl.h @@ -3,9 +3,8 @@ // ----------------------------------------------------------------------------- // NOTE: Dqn_Curl // ----------------------------------------------------------------------------- -// A wrapper over CURL that is primarily used for hot-code reloading by -// packaging the CURL api into a struct that can be passed across DLL -// boundaries. +// Define DQN_CURL_IMPLEMENTATION in one and only one file to enable the +// implementation in translation unit. // // curl_global_init(CURL_GLOBAL_ALL) must return CURLE_OK before anything // in this file is used. You may cleanup curl on exit via curl_global_cleanup() @@ -14,15 +13,56 @@ // An easy way to generate the curl commands required to query a url is to use // CURL itself and the option to dump the command to a C-compatible file, i.e. // -// curl --libcurl RequestToCCode.c -X POST -H "Content-Type: application/json" --data-binary "{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_service_nodes\", \"params\": []}" oxen.observer:22023/json_rpc +// curl --libcurl RequestToCCode.c -X POST -H "Content-Type: application/json" --data-binary "{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_block_count\", \"params\": []}" oxen.observer:22023/json_rpc // // ----------------------------------------------------------------------------- -// NOTE: Configuration +// NOTE: Example // ----------------------------------------------------------------------------- -// #define DQN_CURL_IMPLEMENTATION -// Define this in one and only one C++ file to enable the implementation -// code of the header file. +#if 0 +struct CurlWriteFunctionUserData +{ + Dqn_ArenaAllocator *arena; + Dqn_StringList data; +}; +size_t CurlWriteFunction(char *ptr, size_t size, size_t nmemb, void *userdata) +{ + auto *user_data = DQN_CAST(CurlWriteFunctionUserData *)userdata; + Dqn_StringList_AppendStringCopy(&user_data->data, user_data->arena, Dqn_String_Init(ptr, nmemb)); + DQN_ASSERT(size == 1); + return nmemb; +} + +void main() +{ + // NOTE: Setup Curl handle + CURL *handle = curl_easy_init(); + + struct curl_slist *header_list = nullptr; + header_list = curl_slist_append(header_list, "Content-Type: application/json"); + curl_easy_setopt(handle, CURLOPT_HTTPHEADER, header_list); + + Dqn_String post_data = DQN_STRING("{\"jsonrpc\": \"2.0\", \"id\": \"0\", \"method\": \"get_block_count\", \"params\": []}"); + Dqn_Curl_SetHTTPPost(handle, "oxen.observer:22023/json_rpc", post_data.str, DQN_CAST(int)post_data.size); + + // NOTE: Set write callback + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(); + CurlWriteFunctionUserData user_data = {}; + user_data.arena = scratch.arena; + Dqn_Curl_SetWriteCallback(handle, CurlWriteFunction, &user_data); + + // NOTE: Execute CURL query + curl_easy_perform(handle); + Dqn_String output = Dqn_StringList_Build(&user_data.string_list, scoped_arena.arena); + DQN_LOG_I("%.*s", DQN_STRING_FMT(output)); + + // NOTE: Cleanup + curl_slist_free_all(header_list); + curl_easy_cleanup(handle); +} +#endif + +// NOTE: Warning this causes Windows.h to be included. #define CURL_STATICLIB #define NOMINMAX #include @@ -47,8 +87,9 @@ struct Dqn_CurlProcs }; Dqn_CurlProcs Dqn_CurlProcs_Init(); -void Dqn_Curl_SetPostData(CURL *curl, char const *post_data, int post_size); - +void Dqn_Curl_SetURL(CURL *handle, char const *url); +void Dqn_Curl_SetHTTPPost(CURL *handle, char const *url, char const *post_data, int post_data_size); +void Dqn_Curl_SetWriteCallback(CURL *handle, size_t (*curl_write_callback)(char *ptr, size_t size, size_t nmemb, void *userdata), void *user_data); #endif // DQN_CURL_H #if defined(DQN_CURL_IMPLEMENTATION) @@ -80,10 +121,29 @@ Dqn_CurlProcs Dqn_CurlProcs_Init() return result; } -void Dqn_Curl_SetPostData(CURL *curl, char const *post_data, int post_size) +void Dqn_Curl_SetURL(CURL *handle, char const *url) { - curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, "POST"); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_data); - curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, post_size); + curl_easy_setopt(handle, CURLOPT_URL, url); +} + +void Dqn_Curl_SetHTTPPost(CURL *handle, char const *url, char const *post_data, int post_data_size) +{ + curl_easy_setopt(handle, CURLOPT_BUFFERSIZE, 102400L); + curl_easy_setopt(handle, CURLOPT_URL, url); + curl_easy_setopt(handle, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(handle, CURLOPT_POSTFIELDS, post_data); + curl_easy_setopt(handle, CURLOPT_POSTFIELDSIZE_LARGE, (curl_off_t)post_data_size); + curl_easy_setopt(handle, CURLOPT_USERAGENT, "curl/7.55.1"); + curl_easy_setopt(handle, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(handle, CURLOPT_CUSTOMREQUEST, "POST"); + curl_easy_setopt(handle, CURLOPT_TCP_KEEPALIVE, 1L); +} + +void Dqn_Curl_SetWriteCallback(CURL *handle, + size_t (*curl_write_callback)(char *ptr, size_t size, size_t nmemb, void *userdata), + void *user_data) +{ + curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, curl_write_callback); + curl_easy_setopt(handle, CURLOPT_WRITEDATA, user_data); } #endif // DQN_CURL_IMPLEMENTATION diff --git a/Dqn_Jsmn.h b/Dqn_Jsmn.h index 0dc40e0..0ef4cdc 100644 --- a/Dqn_Jsmn.h +++ b/Dqn_Jsmn.h @@ -1,4 +1,4 @@ -#ifndef DQN_JSMN_H +#if !defined(DQN_JSMN_H) #define DQN_JSMN_H // ----------------------------------------------------------------------------- // NOTE: Dqn_Jsmn @@ -16,7 +16,7 @@ // #if !defined(DQN_H) - #error You must include "Dqn.h" before including "Dqn_Jsmn.h" + #error You must include "dqn.h" before including "dqn_jsmn.h" #endif // DQN_H // ----------------------------------------------------------------------------- @@ -135,15 +135,149 @@ JSMN_API int jsmn_parse(jsmn_parser *parser, const char *js, const size_t len, #endif #endif // JSMN_H +// NOTE: Iterator copied from: https://github.com/zserge/jsmn/pull/69 +// TODO(dqn): Write our own iterator logic in a manner that is more stateful +// than the current implementation, we should not have pass information back and +// forth between iterators, i.e. see my iterator abstraction that sits on top of +// this. +#ifndef __JSMN_ITERATOR_H__ +#define __JSMN_ITERATOR_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Error return codes for jsmn iterator + */ +enum { + /* Input parameter error */ + JSMNITER_ERR_PARAMETER = -1, + /* JSMN index doesn't point at an Array/Object */ + JSMNITER_ERR_TYPE = -2, + /* Group item misses string identifier */ + JSMNITER_ERR_NOIDENT = -3, + /* Broken JSON */ + JSMNITER_ERR_BROKEN = -4, +}; + + +/** + * Struct with state information for jsmn iterator + * - When the no more items for iterator the parser_pos value will point to + * JSMN index for next object after current Array/Object + */ +typedef struct { + jsmntok_t *jsmn_tokens; + unsigned int jsmn_len; + unsigned int parent_pos; + unsigned int parser_pos; + unsigned int index; +} jsmn_iterator_t; + + +/** + * @brief Takes an JSMN Array/Object and locates index for last item in collection + * @details Iterates over JSMN Array/Object until last item is found + * + * @param jsmn_tokens JSMN tokens + * @param jsmn_len JSMN token count + * @param parser_pos Current JSMN token + * + * @return < 0 - Error has occured, corresponds to one of JSMNITER_ERR_* + * >=0 - JSMN index for last item in Array/Object + */ +int jsmn_iterator_find_last( jsmntok_t *jsmn_tokens, unsigned int jsmn_len, unsigned int parser_pos ); + + +/** + * @brief Initialize iterator + * @details Set initial value for iterator struct + * + * @param iterator Iterator struct + * @param jsmn_tokens JSMN tokens + * @param jsmn_len JSMN token count + * @param parser_pos Current JSMN token + * + * @return < 0 - Error has occured, corresponds to one of JSMNITER_ERR_* + * >=0 - Ok + */ +int jsmn_iterator_init( jsmn_iterator_t *iterator, jsmntok_t *jsmn_tokens, unsigned int jsmn_len, + unsigned int parser_pos ); + + +/** + * @brief Get next item in JSMN Array/Object + * @details Gets JSMN position for next identifier and value in Array/Object + * + * @param iterator Iterator struct + * @param jsmn_identifier Return pointer for identifier, NULL for Array + * @param jsmn_value Return pointer for value + * @param next_value_index Possible to indicate where next value begins, allows determine end of sub + * Array/Object withouth manually searching for it + * + * @return < 0 - Error has occured, corresponds to one of JSMNITER_ERR_* + * 0 - No more values + * > 0 - Value (and identifier) has been returned + */ +int jsmn_iterator_next( jsmn_iterator_t *iterator, jsmntok_t **jsmn_identifier, jsmntok_t **jsmn_value, + unsigned int next_value_index ); + + + +/** + * @brief Return current parser position + * @details For Array the parser point to current value index + * For Object the parser points to the identifier + * + * @param iterator [description] + * @return [description] + */ +#define jsmn_iterator_position(_iterator_) ((_iterator_)->parser_pos) + + +#ifdef __cplusplus +} +#endif + +#endif /*__JSMN_ITERATOR_H__*/ + // ----------------------------------------------------------------------------- // Header File // ----------------------------------------------------------------------------- +#define DQN_JSMN_X_MACRO \ + DQN_JSMN_X_ENTRY(Object) \ + DQN_JSMN_X_ENTRY(Array) \ + DQN_JSMN_X_ENTRY(String) \ + DQN_JSMN_X_ENTRY(Number) \ + DQN_JSMN_X_ENTRY(Bool) + +enum struct Dqn_JsmnTokenIs +{ +#define DQN_JSMN_X_ENTRY(enum_val) enum_val, +DQN_JSMN_X_MACRO +#undef DQN_JSMN_X_ENTRY +}; + +inline Dqn_String const Dqn_JsmnTokenIsEnumString(Dqn_JsmnTokenIs token) +{ + switch (token) + { + #define DQN_JSMN_X_ENTRY(enum_val) case Dqn_JsmnTokenIs::enum_val: return DQN_STRING(#enum_val); + DQN_JSMN_X_MACRO + #undef DQN_JSMN_X_ENTRY + } + + return DQN_STRING("DQN_JSMN_UNHANDLED_ENUM"); +}; + struct Dqn_JsmnError { - jsmntok_t token; - char const *cpp_func; - char const *cpp_file; // The file of the .cpp/h source code that triggered the error - int cpp_line; // The line of the .cpp/h source code that triggered the error + jsmntok_t token; + Dqn_String json; + Dqn_JsmnTokenIs expected; + char const *cpp_file; // The file of the .cpp/h source code that triggered the error + int cpp_line; // The line of the .cpp/h source code that triggered the error }; struct Dqn_JsmnErrorHandle @@ -152,28 +286,94 @@ struct Dqn_JsmnErrorHandle Dqn_List list; }; -void Dqn_JsmnErrorHandle_AddError (Dqn_JsmnErrorHandle *err_handle, char const *func, char const *file, int line); -Dqn_String Dqn_JsmnToken_String (Dqn_String json, jsmntok_t token); -Dqn_b32 Dqn_JsmnToken_Bool (Dqn_String json, jsmntok_t token); -Dqn_u64 Dqn_JsmnToken_U64 (Dqn_String json, jsmntok_t token); -jsmntok_t *Dqn_JsmnToken_AdvanceItPastObject(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json); -jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray (jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json); +struct Dqn_Jsmn +{ + jsmn_parser parser; + Dqn_String json; + int tokens_size; + jsmntok_t *tokens; +}; -#define Dqn_JsmnToken_ExpectBool(json, token, err_handle) Dqn_JsmnToken__ExpectBool(json, token, err_handle, __FILE__, __LINE__) -#define Dqn_JsmnToken_ExpectNumber(json, token, err_handle) Dqn_JsmnToken__ExpectNumber(json, token, err_handle, __FILE__, __LINE__) -#define Dqn_JsmnToken_ExpectString(token, err_handle) Dqn_JsmnToken__ExpectString(token, err_handle, __FILE__, __LINE__) -#define Dqn_JsmnToken_ExpectArray(token, err_handle) Dqn_JsmnToken__ExpectArray(token, err_handle, __FILE__, __LINE__) -#define Dqn_JsmnToken_ExpectObject(token, err_handle) Dqn_JsmnToken__ExpectObject(token, err_handle, __FILE__, __LINE__) +struct Dqn_JsmnIterator +{ + Dqn_b32 init; + jsmn_iterator_t jsmn_it; + Dqn_String json; + jsmntok_t *key; + jsmntok_t *value; -#define DQN_JSMN_ERROR_HANDLE_DUMP(handle) \ - for (Dqn_ListChunk *chunk = handle.list.head; chunk; chunk = chunk->next) \ + // When obj/array iteration is finished, we set the token_index_hint to the + // parent iterator so that it knows where to continue off from and to skip + // over the object/array we just iterated. + int token_index_hint; +}; + +Dqn_Jsmn Dqn_Jsmn_InitWithJSON (Dqn_String json, Dqn_ArenaAllocator *arena); +Dqn_Jsmn Dqn_Jsmn_InitWithJSONFile (Dqn_String file, Dqn_ArenaAllocator *arena); + +// return: If the token is an array, return the size of the array otherwise -1. +int Dqn_Jsmn_TokenArraySize(jsmntok_t token); +Dqn_String Dqn_Jsmn_TokenString(jsmntok_t token, Dqn_String json); +Dqn_b32 Dqn_Jsmn_TokenBool(jsmntok_t token, Dqn_String json); +Dqn_u64 Dqn_Jsmn_TokenU64(jsmntok_t token, Dqn_String json); + +// Iterator abstraction over jsmn_iterator_t, example on how to use this is +// shown below. The goal here is to minimise the amount of state the user has to +// manage. +#if 0 + Dqn_ArenaAllocator arena = {}; + Dqn_String json = DQN_STRING(R"({ + "test": { + "test2": 0 + } + })"); + + Dqn_Jsmn jsmn_state = Dqn_Jsmn_InitWithJSON(json, &arena); + for (Dqn_JsmnIterator it = {}; Dqn_JsmnIterator_Next(&it, &jsmn_state, nullptr /*prev_it*/); ) + { + Dqn_String key_str = Dqn_Jsmn_TokenString(*it.key, jsmn_state.json); + if (Dqn_JsmnIterator_Key(&it) == DQN_STRING("test")) + { + if (!Dqn_JsmnIterator_ExpectValue(&it, Dqn_JsmnTokenIs::Object, nullptr)) + continue; + + for (Dqn_JsmnIterator obj_it = {}; Dqn_JsmnIterator_Next(&obj_it, &jsmn_state, &it); ) + { + if (Dqn_JsmnIterator_Key(&it) == DQN_STRING("test2")) + { + if (!Dqn_JsmnIterator_ExpectValue(&it, Dqn_JsmnTokenIs::Number, nullptr)) + continue; + + Dqn_u64 test_2_value = Dqn_JsmnIterator_U64(&obj_it); + } + } + } + } +#endif +Dqn_b32 Dqn_JsmnIterator_Next(Dqn_JsmnIterator *it, Dqn_Jsmn *jsmn_state, Dqn_JsmnIterator *prev_it); +Dqn_String Dqn_JsmnIterator_Key (Dqn_JsmnIterator *it); +Dqn_JsmnIterator Dqn_JsmnIterator_FindKey(Dqn_Jsmn *jsmn_state, Dqn_String key, Dqn_JsmnIterator *parent_it); + +#define Dqn_JsmnIterator_ExpectValue(it, expected, err_handle) Dqn_JsmnIterator__ExpectValue(it, expected, err_handle, __FILE__, __LINE__) +#define Dqn_JsmnIterator_ExpectKey(it, expected, err_handle) Dqn_JsmnIterator__ExpectKey(it, expected, err_handle, __FILE__, __LINE__) + +// Convert the value part of the key-value JSON pair the iterator is currently +// pointing to, to a string/bool/u64. If the iterator's value does not point to +// the type requested, a zero initialised value is returned. +Dqn_String Dqn_JsmnIterator_String(Dqn_JsmnIterator const *it); +Dqn_b32 Dqn_JsmnIterator_Bool (Dqn_JsmnIterator const *it); +Dqn_u64 Dqn_JsmnIterator_U64 (Dqn_JsmnIterator const *it); + +#define DQN_JSMN_ERROR_HANDLE_DUMP(handle) \ + for (Dqn_ListChunk *chunk = handle.list.head; chunk; chunk = chunk->next) \ { \ for (auto *error = chunk->data; error != (chunk->data + chunk->count); error++) \ { \ - DQN_LOG_E("Json parsing error at %s from %s:%d", \ - error->cpp_func, \ + DQN_LOG_E("Json parsing error in %s:%d, expected token type: %.*s, token was: %.*s", \ Dqn_Str_FileNameFromPath(error->cpp_file), \ - error->cpp_line); \ + error->cpp_line, \ + DQN_STRING_FMT(Dqn_JsmnTokenIsEnumString(error->expected)), \ + DQN_STRING_FMT(Dqn_Jsmn_TokenString(error->token, error->json))); \ } \ } @@ -183,7 +383,58 @@ jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray (jsmntok_t *start_it, Dqn_JsmnError // ----------------------------------------------------------------------------- // Implementation // ----------------------------------------------------------------------------- -void Dqn_JsmnErrorHandle_AddError(Dqn_JsmnErrorHandle *err_handle, char const *func, char const *file, int line) +Dqn_Jsmn Dqn_Jsmn_InitWithJSON(Dqn_String json, Dqn_ArenaAllocator *arena) +{ + Dqn_Jsmn result = {}; + result.json = json; + + jsmn_init(&result.parser); + result.tokens_size = jsmn_parse(&result.parser, result.json.str, result.json.size, nullptr, 0); + result.tokens = Dqn_ArenaAllocator_NewArray(arena, jsmntok_t, result.tokens_size, Dqn_ZeroMem::No); + + jsmn_init(&result.parser); + result.tokens_size = jsmn_parse(&result.parser, result.json.str, result.json.size, result.tokens, result.tokens_size); + + return result; +} + +Dqn_Jsmn Dqn_Jsmn_InitWithJSONFile(Dqn_String file, Dqn_ArenaAllocator *arena) +{ + Dqn_String json = Dqn_File_ArenaReadFileToString(file.str, arena); + Dqn_Jsmn result = Dqn_Jsmn_InitWithJSON(json, arena); + return result; +} + +int Dqn_Jsmn_TokenArraySize(jsmntok_t token) +{ + int result = token.type == JSMN_ARRAY ? token.size : -1; + return result; +} + +Dqn_String Dqn_Jsmn_TokenString(jsmntok_t token, Dqn_String json) +{ + Dqn_String result = Dqn_String_Init(json.str + token.start, token.end - token.start); + return result; +} + +Dqn_b32 Dqn_Jsmn_TokenBool(jsmntok_t token, Dqn_String json) +{ + DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); + char ch = json.str[token.start]; + Dqn_b32 result = ch == 't'; + if (!result) { DQN_ASSERT(ch == 'f'); } + return result; +} + +Dqn_u64 Dqn_Jsmn_TokenU64(jsmntok_t token, Dqn_String json) +{ + DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); + Dqn_String string = Dqn_String_Init(json.str + token.start, token.end - token.start); + Dqn_u64 result = Dqn_String_ToU64(string); + return result; +} + +void Dqn_JsmnErrorHandle__AddError(Dqn_JsmnErrorHandle *err_handle, jsmntok_t token, Dqn_String json, Dqn_JsmnTokenIs expected, char const *file, int line) { if (!err_handle) return; @@ -194,120 +445,123 @@ void Dqn_JsmnErrorHandle_AddError(Dqn_JsmnErrorHandle *err_handle, char const *f Dqn_JsmnError *error = Dqn_List_Make(&err_handle->list, 1); if (error) { - error->cpp_func = func; + error->expected = expected; + error->json = json; error->cpp_file = file; error->cpp_line = line; } } -Dqn_b32 Dqn_JsmnToken__ExpectBool(Dqn_String json, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) +Dqn_b32 Dqn_JsmnIterator_Next(Dqn_JsmnIterator *it, Dqn_Jsmn *jsmn_state, Dqn_JsmnIterator *prev_it) { - DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); - char ch = json.str[token.start]; - Dqn_b32 result = token.type == JSMN_PRIMITIVE && (ch == 't' || ch == 'f'); - if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); - return result; -} - -Dqn_b32 Dqn_JsmnToken__ExpectNumber(Dqn_String json, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) -{ - DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); - char ch = json.str[token.start]; - Dqn_b32 result = token.type == JSMN_PRIMITIVE && (ch == '-' || Dqn_Char_IsDigit(ch)); - if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); - return result; -} - -Dqn_b32 Dqn_JsmnToken__ExpectString(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) -{ - Dqn_b32 result = token.type == JSMN_STRING; - if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); - return result; -} - -Dqn_b32 Dqn_JsmnToken__ExpectArray(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) -{ - Dqn_b32 result = token.type == JSMN_ARRAY; - if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); - return result; -} - -Dqn_b32 Dqn_JsmnToken__ExpectObject(jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, int line) -{ - Dqn_b32 result = token.type == JSMN_OBJECT; - if (!result) Dqn_JsmnErrorHandle_AddError(err_handle, __func__, file, line); - return result; -} - -Dqn_String Dqn_JsmnToken_String(Dqn_String json, jsmntok_t token) -{ - Dqn_String result = Dqn_String_Init(json.str + token.start, token.end - token.start); - return result; -} - -Dqn_b32 Dqn_JsmnToken_Bool(Dqn_String json, jsmntok_t token) -{ - DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); - char ch = json.str[token.start]; - Dqn_b32 result = ch == 't'; - if (!result) { DQN_ASSERT(ch == 'f'); } - return result; -} - -Dqn_u64 Dqn_JsmnToken_U64(Dqn_String json, jsmntok_t token) -{ - DQN_ASSERT_MSG(token.start < json.size, "%I64d < %I64u", token.start, json.size); - Dqn_String string = Dqn_JsmnToken_String(json, token); - Dqn_u64 result = Dqn_String_ToU64(string); - return result; -} - -jsmntok_t *Dqn_JsmnToken_AdvanceItPastObject(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json) -{ - jsmntok_t *result = start_it; - if (!Dqn_JsmnToken_ExpectObject(*result, err_handle)) return result; - jsmntok_t *object = result++; - - for (int index = 0; index < object->size; index++) + if (!it->init) { - jsmntok_t *key = result++; - jsmntok_t *value = result++; - Dqn_String key_str = Dqn_JsmnToken_String(json, *key); - Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str; + it->init = true; + it->json = jsmn_state->json; + jsmn_iterator_init(&it->jsmn_it, jsmn_state->tokens, jsmn_state->tokens_size, prev_it ? jsmn_iterator_position(&prev_it->jsmn_it) : 0); + } - if (value->type == JSMN_OBJECT) + Dqn_b32 result = false; + if (!Dqn_String_IsValid(it->json) || it->json.size <= 0) { + return result; + } + + result = jsmn_iterator_next(&it->jsmn_it, &it->key, &it->value, it->token_index_hint) > 0; + if (!result) + { + // NOTE: Iterator has finished object/array, previous iterator will + // continue off where this iterator left off. + if (prev_it) + prev_it->token_index_hint = jsmn_iterator_position(&it->jsmn_it); + } + + return result; +} + +Dqn_String Dqn_JsmnIterator_Key(Dqn_JsmnIterator *it) +{ + Dqn_String result = {}; + if (it && it->key) + result = Dqn_String_Init(it->json.str + it->key->start, it->key->end - it->key->start); + return result; +} + +Dqn_JsmnIterator Dqn_JsmnIterator_FindKey(Dqn_Jsmn *jsmn_state, Dqn_String key, Dqn_JsmnIterator *parent_it) +{ + Dqn_JsmnIterator result = {}; + for (Dqn_JsmnIterator it = {}; Dqn_JsmnIterator_Next(&it, jsmn_state, parent_it); ) + { + Dqn_String it_key = Dqn_Jsmn_TokenString(*it.key, jsmn_state->json); + if (it_key == key) { - result = Dqn_JsmnToken_AdvanceItPastObject(value, err_handle, json); - } - else if (value->type == JSMN_ARRAY) - { - result = Dqn_JsmnToken_AdvanceItPastArray(value, err_handle, json); + result = it; + break; } } return result; } -jsmntok_t *Dqn_JsmnToken_AdvanceItPastArray(jsmntok_t *start_it, Dqn_JsmnErrorHandle *err_handle, Dqn_String json) +static Dqn_b32 Dqn_JsmnIterator__Expect(Dqn_JsmnIterator *it, Dqn_JsmnTokenIs expected, jsmntok_t token, Dqn_JsmnErrorHandle *err_handle, char const *file, unsigned int line) { - jsmntok_t *result = start_it; - if (!Dqn_JsmnToken_ExpectArray(*result, err_handle)) return result; - jsmntok_t *array = result++; - - for (int index = 0; index < array->size; index++) + Dqn_b32 result = false; + switch (expected) { - jsmntok_t *value = result++; - Dqn_String value_str = Dqn_JsmnToken_String(json, *value); (void)value_str; - if (value->type == JSMN_OBJECT) + case Dqn_JsmnTokenIs::Object: result = token.type == JSMN_OBJECT; break; + case Dqn_JsmnTokenIs::Array: result = token.type == JSMN_ARRAY; break; + case Dqn_JsmnTokenIs::String: result = token.type == JSMN_STRING; break; + + case Dqn_JsmnTokenIs::Number: { - Dqn_JsmnToken_AdvanceItPastObject(start_it, err_handle, json); + DQN_ASSERT_MSG(token.start < it->json.size, "%I64d < %I64u", token.start, it->json.size); + char ch = it->json.str[token.start]; + result = token.type == JSMN_PRIMITIVE && (ch == '-' || Dqn_Char_IsDigit(ch)); } - else if (value->type == JSMN_ARRAY) + break; + + case Dqn_JsmnTokenIs::Bool: { - Dqn_JsmnToken_AdvanceItPastArray(start_it, err_handle, json); + DQN_ASSERT_MSG(token.start < it->json.size, "%I64d < %I64u", token.start, it->json.size); + char ch = it->json.str[token.start]; + result = token.type == JSMN_PRIMITIVE && (ch == 't' || ch == 'f'); } + break; } + if (!result) + Dqn_JsmnErrorHandle__AddError(err_handle, token, it->json, expected, file, line); + + return result; +} + +Dqn_b32 Dqn_JsmnIterator__ExpectValue(Dqn_JsmnIterator *it, Dqn_JsmnTokenIs expected, Dqn_JsmnErrorHandle *err_handle, char const *file, unsigned int line) +{ + Dqn_b32 result = it->value && Dqn_JsmnIterator__Expect(it, expected, *it->value, err_handle, file, line); + return result; +} + +Dqn_b32 Dqn_JsmnIterator__ExpectKey(Dqn_JsmnIterator *it, Dqn_JsmnTokenIs expected, Dqn_JsmnErrorHandle *err_handle, char const *file, unsigned int line) +{ + Dqn_b32 result = it->key && Dqn_JsmnIterator__Expect(it, expected, *it->key, err_handle, file, line); + return result; +} + +Dqn_String Dqn_JsmnIterator_String(Dqn_JsmnIterator const *it) +{ + Dqn_String result = {}; + if (it->value && it->json.str) result = Dqn_String_Init(it->json.str + it->value->start, it->value->end - it->value->start); + return result; +} + +Dqn_b32 Dqn_JsmnIterator_Bool(Dqn_JsmnIterator const *it) +{ + Dqn_b32 result = it->value && Dqn_Jsmn_TokenBool(*it->value, it->json); + return result; +} + +Dqn_u64 Dqn_JsmnIterator_U64(Dqn_JsmnIterator const *it) +{ + Dqn_u64 result = it->value && Dqn_Jsmn_TokenU64(*it->value, it->json); return result; } @@ -681,4 +935,236 @@ JSMN_API void jsmn_init(jsmn_parser *parser) { #ifdef __cplusplus } #endif + +/** + * @brief Takes an JSMN Array/Object and locates index for last item in collection + * @details Iterates over JSMN Array/Object until last item is found + * + * @param jsmn_tokens JSMN tokens + * @param jsmn_len JSMN token count + * @param parser_pos Current JSMN token + * + * @return < 0 - Error has occured, corresponds to one of JSMNITER_ERR_* + * >=0 - JSMN index for last item in Array/Object + */ +int jsmn_iterator_find_last( jsmntok_t *jsmn_tokens, unsigned int jsmn_len, unsigned int parser_pos ) { + int child_count; + unsigned int child_index; + int parent_end; + + /* No tokens */ + if (!jsmn_tokens) + return JSMNITER_ERR_PARAMETER; + /* pos outside tokens */ + if (parser_pos >= jsmn_len) + return JSMNITER_ERR_PARAMETER; + /* Not an Array/Object */ + if (jsmn_tokens[parser_pos].type != JSMN_ARRAY && + jsmn_tokens[parser_pos].type != JSMN_OBJECT) + return JSMNITER_ERR_TYPE; + + /* End position for Array/Object */ + parent_end = jsmn_tokens[parser_pos].end; + + /* First child item */ + child_index = parser_pos + 1; + + /* Count number of children we need to iterate */ + child_count = jsmn_tokens[parser_pos].size * (jsmn_tokens[parser_pos].type == JSMN_OBJECT ? 2 : 1); + + /* Loop until end of current Array/Object */ + while(child_index < jsmn_len && jsmn_tokens[child_index].start <= parent_end && child_count >= 0) { + /* Add item count in sub Arrays/Objects that we need to skip */ + if (jsmn_tokens[child_index].type == JSMN_ARRAY) + child_count += jsmn_tokens[child_index].size; + else if (jsmn_tokens[child_index].type == JSMN_OBJECT) + child_count += jsmn_tokens[child_index].size * 2; + + child_count--; + child_index++; + } + + /* Validate that we have visited correct number of children */ + if (child_count != 0) + return JSMNITER_ERR_BROKEN; + + /* Points to last index inside Array/Object */ + return (int)child_index - 1; +} + + +/** + * @brief Initialize iterator + * @details Set initial value for iterator struct + * + * @param iterator Iterator struct + * @param jsmn_tokens JSMN tokens + * @param jsmn_len JSMN token count + * @param parser_pos Current JSMN token + * + * @return < 0 - Error has occured, corresponds to one of JSMNITER_ERR_* + * >=0 - Ok + */ +int jsmn_iterator_init(jsmn_iterator_t *iterator, jsmntok_t *jsmn_tokens, unsigned int jsmn_len, unsigned int parser_pos) { + /* No iterator */ + if (!iterator) + return JSMNITER_ERR_PARAMETER; + /* No tokens */ + if (!jsmn_tokens) + return JSMNITER_ERR_PARAMETER; + /* pos outside tokens */ + if (parser_pos >= jsmn_len) + return JSMNITER_ERR_PARAMETER; + /* Not an Array/Object */ + if (jsmn_tokens[parser_pos].type != JSMN_ARRAY && + jsmn_tokens[parser_pos].type != JSMN_OBJECT) + return JSMNITER_ERR_TYPE; + + /* Save jsmn pointer */ + iterator->jsmn_tokens = jsmn_tokens; + iterator->jsmn_len = jsmn_len; + + iterator->parent_pos = parser_pos; + iterator->parser_pos = parser_pos; + + iterator->index = 0; + return 0; +} + + +/** + * @brief Get next item in JSMN Array/Object + * @details Gets JSMN position for next identifier and value in Array/Object + * + * @param iterator Iterator struct + * @param jsmn_identifier Return pointer for identifier, NULL for Array + * @param jsmn_value Return pointer for value + * @param next_value_index Possible to indicate where next value begins, allows determine end of sub + * Array/Object withouth manually searching for it + * + * @return < 0 - Error has occured, corresponds to one of JSMNITER_ERR_* + * 0 - No more values + * > 0 - Value (and identifier) has been returned + */ + int jsmn_iterator_next(jsmn_iterator_t *iterator, jsmntok_t **jsmn_identifier, jsmntok_t **jsmn_value, + unsigned int next_value_index) { + unsigned int is_object; + jsmntok_t *parent_item; + jsmntok_t *current_item; + jsmntok_t *jsmn_tokens; + unsigned int jsmn_len; + + /* No iterator */ + if (!iterator) + return JSMNITER_ERR_PARAMETER; + /* No value return pointer */ + if (!jsmn_value) + return JSMNITER_ERR_PARAMETER; + /* Parser position is outside JSMN tokens array */ + if (iterator->parser_pos > iterator->jsmn_len) + return JSMNITER_ERR_BROKEN; + + jsmn_tokens = iterator->jsmn_tokens; + jsmn_len = iterator->jsmn_len; + parent_item = &jsmn_tokens[iterator->parent_pos]; + + /* parser_position is at the end of JSMN token array or points outside parent Array/Object */ + if (jsmn_iterator_position(iterator) == iterator->jsmn_len || jsmn_tokens[jsmn_iterator_position(iterator)].start > parent_item->end) { + if (iterator->index != (unsigned int)parent_item->size) + return JSMNITER_ERR_BROKEN; + return 0; + } + + current_item = &jsmn_tokens[jsmn_iterator_position(iterator)]; + + /* Are we in an Object */ + is_object = (parent_item->type == JSMN_OBJECT ? 1 : 0); + + /* Is it item we only need jump one to the next index */ + if (jsmn_iterator_position(iterator) == iterator->parent_pos) { + next_value_index = jsmn_iterator_position(iterator) + 1; + } + /* For items that isn't Array/Object we only need to take the next value */ + else if (current_item->type != JSMN_ARRAY && + current_item->type != JSMN_OBJECT) { + next_value_index = jsmn_iterator_position(iterator) + 1; + } + /* Check if next_value_index is correct, else we need to calculate it ourself */ + else if (next_value_index == 0 || + next_value_index > jsmn_len || + next_value_index <= jsmn_iterator_position(iterator) || + current_item->end < jsmn_tokens[next_value_index - 1].end || + (next_value_index < jsmn_len && current_item->end >= jsmn_tokens[next_value_index].start)) { + /* Find index for last item in the Array/Object manually */ + int return_pos = jsmn_iterator_find_last(jsmn_tokens, jsmn_len, jsmn_iterator_position(iterator)); + + /* Error, bail out */ + if (return_pos < 0) + return return_pos; + + /* Update position to the next item token */ + next_value_index = (unsigned int)(return_pos) + 1; + + /* Outside valid array */ + if (next_value_index > jsmn_len) + return JSMNITER_ERR_BROKEN; + /* Earlier than current value (not valid jsmn tree) */ + if (next_value_index <= jsmn_iterator_position(iterator)) + return JSMNITER_ERR_BROKEN; + /* Previus item is NOT inside current Array/Object */ + if (jsmn_tokens[next_value_index - 1].end > current_item->end) + return JSMNITER_ERR_BROKEN; + /* Not last item and next item is NOT outside Array/Object */ + if (next_value_index < jsmn_len && current_item->end > jsmn_tokens[next_value_index].start) + return JSMNITER_ERR_BROKEN; + } + + /* Parser position is outside JSMN tokens array */ + if (next_value_index > iterator->jsmn_len) + return JSMNITER_ERR_BROKEN; + + /* parser_position is at the end of JSMN token array or points outside parent Array/Object */ + if (next_value_index == (unsigned int)iterator->jsmn_len || jsmn_tokens[next_value_index].start > parent_item->end) { + if (iterator->index != (unsigned int)parent_item->size) + return JSMNITER_ERR_BROKEN; + /* Update parser position before exit */ + iterator->parser_pos = next_value_index; + return 0; + } + + /* Get current value/identifier */ + current_item = &jsmn_tokens[next_value_index]; + + /* We have identifier, read it */ + if (is_object) { + /* Must be string */ + if (current_item->type != JSMN_STRING) + return JSMNITER_ERR_NOIDENT; + /* Ensure that we have next token */ + if (next_value_index + 1 >= jsmn_len) + return JSMNITER_ERR_BROKEN; + /* Missing identifier pointer */ + if (!jsmn_identifier) + return JSMNITER_ERR_PARAMETER; + + /* Set identifier and update current pointer to value item */ + *jsmn_identifier = current_item; + next_value_index++; + current_item = &jsmn_tokens[next_value_index]; + } + /* Clear identifier if is set */ + else if (jsmn_identifier) { + *jsmn_identifier = NULL; + } + + /* Set value */ + *jsmn_value = current_item; + + /* Update parser position */ + iterator->parser_pos = next_value_index; + + /* Increase the index and return it as the positive value */ + iterator->index++; + return (int)iterator->index; +} #endif // DQN_JSMN_IMPLEMENTATION diff --git a/Dqn_Tests.cpp b/Dqn_Tests.cpp index 2efeb79..cf40c64 100644 --- a/Dqn_Tests.cpp +++ b/Dqn_Tests.cpp @@ -18,9 +18,14 @@ #define DQN_WITH_MAP // Dqn_Map #define DQN_WITH_MATH // Dqn_V2/3/4/Mat4 and friends ... #define DQN_WITH_THREAD_CONTEXT // Dqn_ThreadContext and friends ... - #include "Dqn.h" + #include "dqn.h" + + #define DQN_KECCAK_IMPLEMENTATION + #include "dqn_keccak.h" #endif +#include "dqn_tests_helpers.cpp" + struct Dqn_TestState { int indent_level; @@ -59,7 +64,7 @@ static int g_dqn_test_total_tests; #define DQN_TEST_ANSI_COLOR_RESET "\x1b[0m" #endif -#define DQN_TEST_START_SCOPE(testing_state, test_name) \ +#define DQN_TEST_START_SCOPE(testing_state, test_name, ...) \ DQN_DEFER \ { \ if (testing_state.test.fail_expr.size == 0) testing_state.num_tests_ok_in_group++; \ @@ -67,7 +72,7 @@ static int g_dqn_test_total_tests; Dqn_ArenaAllocator_ResetUsage(&testing_state.arena, Dqn_ZeroMem::No); \ testing_state.test = {}; \ }; \ - testing_state.test.name = DQN_STRING(test_name); \ + testing_state.test.name = Dqn_String_Fmt(&testing_state.arena, test_name, ##__VA_ARGS__); \ testing_state.test.scope_started = true; \ testing_state.num_tests_in_group++ @@ -1394,131 +1399,6 @@ void Dqn_Test_String() } } -void Dqn_Test_StringBuilder() -{ - Dqn_TestingState testing_state = {}; - DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_StringBuilder"); - Dqn_ArenaAllocator arena = {}; - // NOTE: Dqn_StringBuilder_Append - { - { - DQN_TEST_START_SCOPE(testing_state, "Append variable size strings and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_Append(&builder, "Abc", 1); - Dqn_StringBuilder_Append(&builder, "cd"); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = "Acd"; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - - { - DQN_TEST_START_SCOPE(testing_state, "Append empty string and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_Append(&builder, ""); - Dqn_StringBuilder_Append(&builder, ""); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = ""; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - - { - DQN_TEST_START_SCOPE(testing_state, "Append empty string onto string and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_Append(&builder, "Acd"); - Dqn_StringBuilder_Append(&builder, ""); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = "Acd"; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - - { - DQN_TEST_START_SCOPE(testing_state, "Append nullptr and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_Append(&builder, nullptr, 5); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = ""; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - - { - DQN_TEST_START_SCOPE(testing_state, "Append and require new linked buffer and build using heap arena"); - Dqn_StringBuilder<2> builder = {}; - Dqn_StringBuilder_InitWithArena(&builder, &arena); - Dqn_StringBuilder_Append(&builder, "A"); - Dqn_StringBuilder_Append(&builder, "z"); // Should force a new memory block - Dqn_StringBuilder_Append(&builder, "tec"); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = "Aztec"; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - } - - // NOTE: Dqn_StringBuilder_AppendChar - { - DQN_TEST_START_SCOPE(testing_state, "Append char and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_AppendChar(&builder, 'a'); - Dqn_StringBuilder_AppendChar(&builder, 'b'); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = "ab"; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - - // NOTE: Dqn_StringBuilder_AppendFmt - { - { - DQN_TEST_START_SCOPE(testing_state, "Append format string and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_AppendFmt(&builder, "Number: %d, String: %s, ", 4, "Hello Sailor"); - Dqn_StringBuilder_AppendFmt(&builder, "Extra Stuff"); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = "Number: 4, String: Hello Sailor, Extra Stuff"; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - - { - DQN_TEST_START_SCOPE(testing_state, "Append nullptr format string and build using heap arena"); - Dqn_StringBuilder<> builder = {}; - Dqn_StringBuilder_AppendFmt(&builder, nullptr); - Dqn_isize size = 0; - char *result = Dqn_StringBuilder_Build(&builder, &arena, &size); - DQN_DEFER { Dqn_ArenaAllocator_Free(&arena); }; - - char const EXPECT_STR[] = ""; - DQN_TEST_EXPECT_MSG(testing_state, size == Dqn_CharCountI(EXPECT_STR), "size: %zd", size); - DQN_TEST_EXPECT_MSG(testing_state, strncmp(result, EXPECT_STR, size) == 0, "result: %s", result); - } - } -} - void Dqn_Test_TicketMutex() { Dqn_TestingState testing_state = {}; @@ -1609,6 +1489,221 @@ void Dqn_Test_Win() #endif // DQN_OS_WIN32 } +#define DQN_TESTS_HASH_X_MACRO \ + DQN_TESTS_HASH_X_ENTRY(SHA3_224, "SHA3-224") \ + DQN_TESTS_HASH_X_ENTRY(SHA3_256, "SHA3-256") \ + DQN_TESTS_HASH_X_ENTRY(SHA3_384, "SHA3-384") \ + DQN_TESTS_HASH_X_ENTRY(SHA3_512, "SHA3-512") \ + DQN_TESTS_HASH_X_ENTRY(Keccak_224, "Keccak-224") \ + DQN_TESTS_HASH_X_ENTRY(Keccak_256, "Keccak-256") \ + DQN_TESTS_HASH_X_ENTRY(Keccak_384, "Keccak-384") \ + DQN_TESTS_HASH_X_ENTRY(Keccak_512, "Keccak-512") \ + DQN_TESTS_HASH_X_ENTRY(Count, "Keccak-512") + +enum Dqn_Tests__HashType +{ +#define DQN_TESTS_HASH_X_ENTRY(enum_val, string) Hash_##enum_val, + DQN_TESTS_HASH_X_MACRO +#undef DQN_TESTS_HASH_X_ENTRY +}; + +Dqn_String const DQN_TESTS__HASH_STRING[] = +{ +#define DQN_TESTS_HASH_X_ENTRY(enum_val, string) DQN_STRING(string), + DQN_TESTS_HASH_X_MACRO +#undef DQN_TESTS_HASH_X_ENTRY +}; + + +void Dqn_Test__KeccakDispatch(Dqn_TestingState *testing_state, int hash_type, Dqn_String input) +{ +#if defined(DQN_KECCAK_H) + Dqn_ThreadScratch scratch = Dqn_Thread_GetScratch(); + Dqn_String input_hex = Dqn_Hex_BytesToHexStringArena(input.str, input.size, scratch.arena); + + switch(hash_type) + { + case Hash_SHA3_224: + { + Dqn_KeccakBytes28 hash = Dqn_Keccak_SHA3_224_StringToBytes28(input); + Dqn_KeccakBytes28 expect; + FIPS202_SHA3_224(DQN_CAST(u8 *)input.str, input.size, (u8 *)expect.data); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes28Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING56_FMT(Dqn_Keccak_Bytes28ToHex(&hash).str), + DQN_KECCAK_STRING56_FMT(Dqn_Keccak_Bytes28ToHex(&expect).str)); + } + break; + + case Hash_SHA3_256: + { + Dqn_KeccakBytes32 hash = Dqn_Keccak_SHA3_256_StringToBytes32(input); + Dqn_KeccakBytes32 expect; + FIPS202_SHA3_256(DQN_CAST(u8 *)input.str, input.size, (u8 *)expect.data); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes32Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING64_FMT(Dqn_Keccak_Bytes32ToHex(&hash).str), + DQN_KECCAK_STRING64_FMT(Dqn_Keccak_Bytes32ToHex(&expect).str)); + } + break; + + case Hash_SHA3_384: + { + Dqn_KeccakBytes48 hash = Dqn_Keccak_SHA3_384_StringToBytes48(input); + Dqn_KeccakBytes48 expect; + FIPS202_SHA3_384(DQN_CAST(u8 *)input.str, input.size, (u8 *)expect.data); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes48Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING96_FMT(Dqn_Keccak_Bytes48ToHex(&hash).str), + DQN_KECCAK_STRING96_FMT(Dqn_Keccak_Bytes48ToHex(&expect).str)); + } + break; + + case Hash_SHA3_512: + { + Dqn_KeccakBytes64 hash = Dqn_Keccak_SHA3_512_StringToBytes64(input); + Dqn_KeccakBytes64 expect; + FIPS202_SHA3_512(DQN_CAST(u8 *)input.str, input.size, (u8 *)expect.data); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes64Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING128_FMT(Dqn_Keccak_Bytes64ToHex(&hash).str), + DQN_KECCAK_STRING128_FMT(Dqn_Keccak_Bytes64ToHex(&expect).str)); + } + break; + + case Hash_Keccak_224: + { + Dqn_KeccakBytes28 hash = Dqn_Keccak_224_StringToBytes28(input); + Dqn_KeccakBytes28 expect; + Keccak(1152, 448, DQN_CAST(u8 *)input.str, input.size, 0x01, (u8 *)expect.data, sizeof(expect)); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes28Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING56_FMT(Dqn_Keccak_Bytes28ToHex(&hash).str), + DQN_KECCAK_STRING56_FMT(Dqn_Keccak_Bytes28ToHex(&expect).str)); + } + break; + + case Hash_Keccak_256: + { + Dqn_KeccakBytes32 hash = Dqn_Keccak_256_StringToBytes32(input); + Dqn_KeccakBytes32 expect; + Keccak(1088, 512, DQN_CAST(u8 *)input.str, input.size, 0x01, (u8 *)expect.data, sizeof(expect)); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes32Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING64_FMT(Dqn_Keccak_Bytes32ToHex(&hash).str), + DQN_KECCAK_STRING64_FMT(Dqn_Keccak_Bytes32ToHex(&expect).str)); + } + break; + + case Hash_Keccak_384: + { + Dqn_KeccakBytes48 hash = Dqn_Keccak_384_StringToBytes48(input); + Dqn_KeccakBytes48 expect; + Keccak(832, 768, DQN_CAST(u8 *)input.str, input.size, 0x01, (u8 *)expect.data, sizeof(expect)); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes48Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING96_FMT(Dqn_Keccak_Bytes48ToHex(&hash).str), + DQN_KECCAK_STRING96_FMT(Dqn_Keccak_Bytes48ToHex(&expect).str)); + } + break; + + case Hash_Keccak_512: + { + Dqn_KeccakBytes64 hash = Dqn_Keccak_512_StringToBytes64(input); + Dqn_KeccakBytes64 expect; + Keccak(576, 1024, DQN_CAST(u8 *)input.str, input.size, 0x01, (u8 *)expect.data, sizeof(expect)); + DQN_TEST_EXPECT_MSG((*testing_state), + Dqn_Keccak_Bytes64Equals(&hash, &expect), + "\ninput: %.*s" + "\nhash: %.*s" + "\nexpect: %.*s" + , + DQN_STRING_FMT(input_hex), + DQN_KECCAK_STRING128_FMT(Dqn_Keccak_Bytes64ToHex(&hash).str), + DQN_KECCAK_STRING128_FMT(Dqn_Keccak_Bytes64ToHex(&expect).str)); + } + break; + + } +#endif // DQN_KECCAK_H +} + +void Dqn_Test_Keccak() +{ +#if defined(DQN_KECCAK_H) + Dqn_TestingState testing_state = {}; + + Dqn_String const INPUTS[] = { + DQN_STRING("abc"), + DQN_STRING(""), + DQN_STRING("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"), + DQN_STRING("abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmno" + "pqrstnopqrstu"), + }; + + DQN_TEST_DECLARE_GROUP_SCOPED(testing_state, "Dqn_Keccak"); + for (int hash_type = 0; hash_type < Hash_Count; hash_type++) + { + pcg32_random_t rng = {}; + pcg32_srandom_r(&rng, 0xd48e'be21'2af8'733d, 0x3f89'3bd2'd6b0'4eef); + + for (Dqn_String input : INPUTS) + { + DQN_TEST_START_SCOPE(testing_state, "%.*s - Input: %.*s", DQN_STRING_FMT(DQN_TESTS__HASH_STRING[hash_type]), DQN_MIN(input.size, 54), input.str); + Dqn_Test__KeccakDispatch(&testing_state, hash_type, input); + } + + DQN_TEST_START_SCOPE(testing_state, "%.*s - Deterministic random inputs", DQN_STRING_FMT(DQN_TESTS__HASH_STRING[hash_type])); + for (int index = 0; index < 128; index++) + { + char src[4096] = {}; + Dqn_u32 src_size = pcg32_boundedrand_r(&rng, sizeof(src)); + + for (int src_index = 0; src_index < src_size; src_index++) + src[src_index] = pcg32_boundedrand_r(&rng, 255); + + Dqn_String input = Dqn_String_Init(src, src_size); + Dqn_Test__KeccakDispatch(&testing_state, hash_type, input); + } + } +#endif // DQN_KECCAK_H +} + void Dqn_Test_RunSuite() { Dqn_Test_Array(); @@ -1623,9 +1718,9 @@ void Dqn_Test_RunSuite() Dqn_Test_Rect(); Dqn_Test_PerfCounter(); Dqn_Test_OS(); + Dqn_Test_Keccak(); Dqn_Test_Str(); Dqn_Test_String(); - Dqn_Test_StringBuilder(); Dqn_Test_TicketMutex(); Dqn_Test_Win(); diff --git a/Dqn_U128.h b/Dqn_U128.h deleted file mode 100644 index 4f458a5..0000000 --- a/Dqn_U128.h +++ /dev/null @@ -1,1039 +0,0 @@ -#ifndef DQN_U128_H -#define DQN_U128_H -// ----------------------------------------------------------------------------- -// NOTE: Dqn_U128 -// ----------------------------------------------------------------------------- -// Provides some helper funtions ontop of intx that currently use heap -// allocation but don't actually need heap allocation. -// -// ----------------------------------------------------------------------------- -// NOTE: Configuration -// ----------------------------------------------------------------------------- -// #define DQN_U128_IMPLEMENTATION -// Define this in one and only one C++ file to enable the implementation -// code of the header file. - -// intx: extended precision integer library. -// Copyright 2019-2020 Pawel Bylica. -// Licensed under the Apache License, Version 2.0. - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef __has_builtin - #define __has_builtin(NAME) 0 -#endif - -#ifdef _MSC_VER - #include -#endif - -#if !defined(__has_builtin) - #define __has_builtin(NAME) 0 -#endif - -#if !defined(__has_feature) - #define __has_feature(NAME) 0 -#endif - -#if !defined(NDEBUG) - #define INTX_UNREACHABLE() assert(false) -#elif __has_builtin(__builtin_unreachable) - #define INTX_UNREACHABLE() __builtin_unreachable() -#elif defined(_MSC_VER) - #define INTX_UNREACHABLE() __assume(0) -#else - #define INTX_UNREACHABLE() (void)0 -#endif - - -#if __has_builtin(__builtin_expect) - #define INTX_UNLIKELY(EXPR) __builtin_expect(bool{EXPR}, false) -#else - #define INTX_UNLIKELY(EXPR) (bool{EXPR}) -#endif - -#if !defined(NDEBUG) - #define INTX_REQUIRE assert -#else - #define INTX_REQUIRE(X) (X) ? (void)0 : INTX_UNREACHABLE() -#endif - - -// Detect compiler support for 128-bit integer __int128 -#if defined(__SIZEOF_INT128__) - #define INTX_HAS_BUILTIN_INT128 1 -#else - #define INTX_HAS_BUILTIN_INT128 0 -#endif - - -namespace intx -{ -#if INTX_HAS_BUILTIN_INT128 - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wpedantic" // Usage of __int128 triggers a pedantic warning. - -/// Alias for the compiler supported unsigned __int128 type. -using builtin_uint128 = unsigned __int128; - - #pragma GCC diagnostic pop -#endif - -template -struct uint; - -/// The 128-bit unsigned integer. -/// -/// This type is defined as a specialization of uint<> to easier integration with full intx package, -/// however, uint128 may be used independently. -template <> -struct uint<128> -{ - using word_type = uint64_t; - static constexpr auto word_num_bits = sizeof(word_type) * 8; - static constexpr unsigned num_bits = 128; - static constexpr auto num_words = num_bits / word_num_bits; - -private: - uint64_t words_[2]{}; - -public: - constexpr uint() noexcept = default; - - constexpr uint(uint64_t low, uint64_t high) noexcept : words_{low, high} {} - - template ::value>> - constexpr uint(T x) noexcept : words_{static_cast(x), 0} // NOLINT - {} - -#if INTX_HAS_BUILTIN_INT128 - constexpr uint(builtin_uint128 x) noexcept // NOLINT - : words_{uint64_t(x), uint64_t(x >> 64)} - {} - - constexpr explicit operator builtin_uint128() const noexcept - { - return (builtin_uint128{words_[1]} << 64) | words_[0]; - } -#endif - - constexpr uint64_t& operator[](size_t i) noexcept { return words_[i]; } - constexpr const uint64_t& operator[](size_t i) const noexcept { return words_[i]; } - - constexpr explicit operator bool() const noexcept { return (words_[0] | words_[1]) != 0; } - - /// Explicit converting operator for all builtin integral types. - template ::value>::type> - constexpr explicit operator Int() const noexcept - { - return static_cast(words_[0]); - } -}; - -using uint128 = uint<128>; - - -inline constexpr bool is_constant_evaluated() noexcept -{ -#if __has_builtin(__builtin_is_constant_evaluated) || (defined(_MSC_VER) && _MSC_VER >= 1925) - return __builtin_is_constant_evaluated(); -#else - return true; -#endif -} - - -/// Contains result of add/sub/etc with a carry flag. -template -struct result_with_carry -{ - T value; - bool carry; - - /// Conversion to tuple of references, to allow usage with std::tie(). - constexpr operator std::tuple() noexcept { return {value, carry}; } -}; - - -/// Linear arithmetic operators. -/// @{ - -inline constexpr result_with_carry add_with_carry( - uint64_t x, uint64_t y, bool carry = false) noexcept -{ - const auto s = x + y; - const auto carry1 = s < x; - const auto t = s + carry; - const auto carry2 = t < s; - return {t, carry1 || carry2}; -} - -template -inline constexpr result_with_carry> add_with_carry( - const uint& x, const uint& y, bool carry = false) noexcept -{ - uint s; - bool k = carry; - for (size_t i = 0; i < uint::num_words; ++i) - { - s[i] = x[i] + y[i]; - const auto k1 = s[i] < x[i]; - s[i] += k; - k = (s[i] < uint64_t{k}) || k1; - } - return {s, k}; -} - -inline constexpr uint128 operator+(uint128 x, uint128 y) noexcept -{ - return add_with_carry(x, y).value; -} - -inline constexpr uint128 operator+(uint128 x) noexcept -{ - return x; -} - -inline constexpr result_with_carry sub_with_carry( - uint64_t x, uint64_t y, bool carry = false) noexcept -{ - const auto d = x - y; - const auto carry1 = x < y; - const auto e = d - carry; - const auto carry2 = d < uint64_t{carry}; - return {e, carry1 || carry2}; -} - -/// Performs subtraction of two unsigned numbers and returns the difference -/// and the carry bit (aka borrow, overflow). -template -inline constexpr result_with_carry> sub_with_carry( - const uint& x, const uint& y, bool carry = false) noexcept -{ - uint z; - bool k = carry; - for (size_t i = 0; i < uint::num_words; ++i) - { - z[i] = x[i] - y[i]; - const auto k1 = x[i] < y[i]; - const auto k2 = z[i] < uint64_t{k}; - z[i] -= k; - k = k1 || k2; - } - return {z, k}; -} - -inline constexpr uint128 operator-(uint128 x, uint128 y) noexcept -{ - return sub_with_carry(x, y).value; -} - -inline constexpr uint128 operator-(uint128 x) noexcept -{ - // Implementing as subtraction is better than ~x + 1. - // Clang9: Perfect. - // GCC8: Does something weird. - return 0 - x; -} - -inline uint128& operator++(uint128& x) noexcept -{ - return x = x + 1; -} - -inline uint128& operator--(uint128& x) noexcept -{ - return x = x - 1; -} - -inline uint128 operator++(uint128& x, int) noexcept -{ - auto ret = x; - ++x; - return ret; -} - -inline uint128 operator--(uint128& x, int) noexcept -{ - auto ret = x; - --x; - return ret; -} - -/// Optimized addition. -/// -/// This keeps the multiprecision addition until CodeGen so the pattern is not -/// broken during other optimizations. -inline constexpr uint128 fast_add(uint128 x, uint128 y) noexcept -{ -#if INTX_HAS_BUILTIN_INT128 - return builtin_uint128{x} + builtin_uint128{y}; -#else - return x + y; // Fallback to generic addition. -#endif -} - -/// @} - - -/// Comparison operators. -/// -/// In all implementations bitwise operators are used instead of logical ones -/// to avoid branching. -/// -/// @{ - -inline constexpr bool operator==(uint128 x, uint128 y) noexcept -{ - // Clang7: generates perfect xor based code, - // much better than __int128 where it uses vector instructions. - // GCC8: generates a bit worse cmp based code - // although it generates the xor based one for __int128. - return (x[0] == y[0]) & (x[1] == y[1]); -} - -inline constexpr bool operator!=(uint128 x, uint128 y) noexcept -{ - // Analogous to ==, but == not used directly, because that confuses GCC 8-9. - return (x[0] != y[0]) | (x[1] != y[1]); -} - -inline constexpr bool operator<(uint128 x, uint128 y) noexcept -{ - // OPT: This should be implemented by checking the borrow of x - y, - // but compilers (GCC8, Clang7) - // have problem with properly optimizing subtraction. -#if INTX_HAS_BUILTIN_INT128 - return builtin_uint128{x} < builtin_uint128{y}; -#else - return (x[1] < y[1]) | ((x[1] == y[1]) & (x[0] < y[0])); -#endif -} - -inline constexpr bool operator<=(uint128 x, uint128 y) noexcept -{ - return !(y < x); -} - -inline constexpr bool operator>(uint128 x, uint128 y) noexcept -{ - return y < x; -} - -inline constexpr bool operator>=(uint128 x, uint128 y) noexcept -{ - return !(x < y); -} - -/// @} - - -/// Bitwise operators. -/// @{ - -inline constexpr uint128 operator~(uint128 x) noexcept -{ - return {~x[0], ~x[1]}; -} - -inline constexpr uint128 operator|(uint128 x, uint128 y) noexcept -{ - // Clang7: perfect. - // GCC8: stupidly uses a vector instruction in all bitwise operators. - return {x[0] | y[0], x[1] | y[1]}; -} - -inline constexpr uint128 operator&(uint128 x, uint128 y) noexcept -{ - return {x[0] & y[0], x[1] & y[1]}; -} - -inline constexpr uint128 operator^(uint128 x, uint128 y) noexcept -{ - return {x[0] ^ y[0], x[1] ^ y[1]}; -} - -inline constexpr uint128 operator<<(uint128 x, uint64_t shift) noexcept -{ - return (shift < 64) ? - // Find the part moved from lo to hi. - // For shift == 0 right shift by (64 - shift) is invalid so - // split it into 2 shifts by 1 and (63 - shift). - uint128{x[0] << shift, (x[1] << shift) | ((x[0] >> 1) >> (63 - shift))} : - - // Guarantee "defined" behavior for shifts larger than 128. - (shift < 128) ? uint128{0, x[0] << (shift - 64)} : 0; -} - -inline constexpr uint128 operator<<(uint128 x, uint128 shift) noexcept -{ - if (INTX_UNLIKELY(shift[1] != 0)) - return 0; - - return x << shift[0]; -} - -inline constexpr uint128 operator>>(uint128 x, uint64_t shift) noexcept -{ - return (shift < 64) ? - // Find the part moved from lo to hi. - // For shift == 0 left shift by (64 - shift) is invalid so - // split it into 2 shifts by 1 and (63 - shift). - uint128{(x[0] >> shift) | ((x[1] << 1) << (63 - shift)), x[1] >> shift} : - - // Guarantee "defined" behavior for shifts larger than 128. - (shift < 128) ? uint128{x[1] >> (shift - 64)} : 0; -} - -inline constexpr uint128 operator>>(uint128 x, uint128 shift) noexcept -{ - if (INTX_UNLIKELY(shift[1] != 0)) - return 0; - - return x >> static_cast(shift); -} - -/// @} - - -/// Multiplication -/// @{ - -/// Full unsigned multiplication 64 x 64 -> 128. -inline constexpr uint128 umul(uint64_t x, uint64_t y) noexcept -{ -#if INTX_HAS_BUILTIN_INT128 - return builtin_uint128{x} * builtin_uint128{y}; -#elif defined(_MSC_VER) && _MSC_VER >= 1925 - if (!is_constant_evaluated()) - { - unsigned __int64 hi = 0; - const auto lo = _umul128(x, y, &hi); - return {lo, hi}; - } - // For constexpr fallback to portable variant. -#endif - - // Portable full unsigned multiplication 64 x 64 -> 128. - uint64_t xl = x & 0xffffffff; - uint64_t xh = x >> 32; - uint64_t yl = y & 0xffffffff; - uint64_t yh = y >> 32; - - uint64_t t0 = xl * yl; - uint64_t t1 = xh * yl; - uint64_t t2 = xl * yh; - uint64_t t3 = xh * yh; - - uint64_t u1 = t1 + (t0 >> 32); - uint64_t u2 = t2 + (u1 & 0xffffffff); - - uint64_t lo = (u2 << 32) | (t0 & 0xffffffff); - uint64_t hi = t3 + (u2 >> 32) + (u1 >> 32); - return {lo, hi}; -} - -inline constexpr uint128 operator*(uint128 x, uint128 y) noexcept -{ - auto p = umul(x[0], y[0]); - p[1] += (x[0] * y[1]) + (x[1] * y[0]); - return {p[0], p[1]}; -} - -/// @} - - -/// Assignment operators. -/// @{ - -inline constexpr uint128& operator+=(uint128& x, uint128 y) noexcept -{ - return x = x + y; -} - -inline constexpr uint128& operator-=(uint128& x, uint128 y) noexcept -{ - return x = x - y; -} - -inline uint128& operator*=(uint128& x, uint128 y) noexcept -{ - return x = x * y; -} - -inline constexpr uint128& operator|=(uint128& x, uint128 y) noexcept -{ - return x = x | y; -} - -inline constexpr uint128& operator&=(uint128& x, uint128 y) noexcept -{ - return x = x & y; -} - -inline constexpr uint128& operator^=(uint128& x, uint128 y) noexcept -{ - return x = x ^ y; -} - -inline constexpr uint128& operator<<=(uint128& x, uint64_t shift) noexcept -{ - return x = x << shift; -} - -inline constexpr uint128& operator>>=(uint128& x, uint64_t shift) noexcept -{ - return x = x >> shift; -} - -/// @} - - -inline constexpr unsigned clz_generic(uint32_t x) noexcept -{ - unsigned n = 32; - for (int i = 4; i >= 0; --i) - { - const auto s = unsigned{1} << i; - const auto hi = x >> s; - if (hi != 0) - { - n -= s; - x = hi; - } - } - return n - x; -} - -inline constexpr unsigned clz_generic(uint64_t x) noexcept -{ - unsigned n = 64; - for (int i = 5; i >= 0; --i) - { - const auto s = unsigned{1} << i; - const auto hi = x >> s; - if (hi != 0) - { - n -= s; - x = hi; - } - } - return n - static_cast(x); -} - -inline constexpr unsigned clz(uint32_t x) noexcept -{ -#ifdef _MSC_VER - return clz_generic(x); -#else - return x != 0 ? unsigned(__builtin_clz(x)) : 32; -#endif -} - -inline constexpr unsigned clz(uint64_t x) noexcept -{ -#ifdef _MSC_VER - return clz_generic(x); -#else - return x != 0 ? unsigned(__builtin_clzll(x)) : 64; -#endif -} - -inline constexpr unsigned clz(uint128 x) noexcept -{ - // In this order `h == 0` we get less instructions than in case of `h != 0`. - return x[1] == 0 ? clz(x[0]) + 64 : clz(x[1]); -} - - -inline constexpr uint64_t bswap(uint64_t x) noexcept -{ -#if __has_builtin(__builtin_bswap64) - return __builtin_bswap64(x); -#else - #ifdef _MSC_VER - if (!is_constant_evaluated()) - return _byteswap_uint64(x); - #endif - const auto a = ((x << 8) & 0xFF00FF00FF00FF00) | ((x >> 8) & 0x00FF00FF00FF00FF); - const auto b = ((a << 16) & 0xFFFF0000FFFF0000) | ((a >> 16) & 0x0000FFFF0000FFFF); - return (b << 32) | (b >> 32); -#endif -} - -inline constexpr uint128 bswap(uint128 x) noexcept -{ - return {bswap(x[1]), bswap(x[0])}; -} - - -/// Division. -/// @{ - -template -struct div_result -{ - QuotT quot; - RemT rem; - - /// Conversion to tuple of references, to allow usage with std::tie(). - constexpr operator std::tuple() noexcept { return {quot, rem}; } -}; - -namespace internal -{ -inline constexpr uint16_t reciprocal_table_item(uint8_t d9) noexcept -{ - return uint16_t(0x7fd00 / (0x100 | d9)); -} - -#define REPEAT4(x) \ - reciprocal_table_item((x) + 0), reciprocal_table_item((x) + 1), \ - reciprocal_table_item((x) + 2), reciprocal_table_item((x) + 3) - -#define REPEAT32(x) \ - REPEAT4((x) + 4 * 0), REPEAT4((x) + 4 * 1), REPEAT4((x) + 4 * 2), REPEAT4((x) + 4 * 3), \ - REPEAT4((x) + 4 * 4), REPEAT4((x) + 4 * 5), REPEAT4((x) + 4 * 6), REPEAT4((x) + 4 * 7) - -#define REPEAT256() \ - REPEAT32(32 * 0), REPEAT32(32 * 1), REPEAT32(32 * 2), REPEAT32(32 * 3), REPEAT32(32 * 4), \ - REPEAT32(32 * 5), REPEAT32(32 * 6), REPEAT32(32 * 7) - -/// Reciprocal lookup table. -constexpr uint16_t reciprocal_table[] = {REPEAT256()}; - -#undef REPEAT4 -#undef REPEAT32 -#undef REPEAT256 -} // namespace internal - -/// Computes the reciprocal (2^128 - 1) / d - 2^64 for normalized d. -/// -/// Based on Algorithm 2 from "Improved division by invariant integers". -inline uint64_t reciprocal_2by1(uint64_t d) noexcept -{ - INTX_REQUIRE(d & 0x8000000000000000); // Must be normalized. - - const uint64_t d9 = d >> 55; - const uint32_t v0 = internal::reciprocal_table[d9 - 256]; - - const uint64_t d40 = (d >> 24) + 1; - const uint64_t v1 = (v0 << 11) - uint32_t(v0 * v0 * d40 >> 40) - 1; - - const uint64_t v2 = (v1 << 13) + (v1 * (0x1000000000000000 - v1 * d40) >> 47); - - const uint64_t d0 = d & 1; - const uint64_t d63 = (d >> 1) + d0; // ceil(d/2) - const uint64_t e = ((v2 >> 1) & (0 - d0)) - v2 * d63; - const uint64_t v3 = (umul(v2, e)[1] >> 1) + (v2 << 31); - - const uint64_t v4 = v3 - (umul(v3, d) + d)[1] - d; - return v4; -} - -inline uint64_t reciprocal_3by2(uint128 d) noexcept -{ - auto v = reciprocal_2by1(d[1]); - auto p = d[1] * v; - p += d[0]; - if (p < d[0]) - { - --v; - if (p >= d[1]) - { - --v; - p -= d[1]; - } - p -= d[1]; - } - - const auto t = umul(v, d[0]); - - p += t[1]; - if (p < t[1]) - { - --v; - if (p >= d[1]) - { - if (p > d[1] || t[0] >= d[0]) - --v; - } - } - return v; -} - -inline div_result udivrem_2by1(uint128 u, uint64_t d, uint64_t v) noexcept -{ - auto q = umul(v, u[1]); - q = fast_add(q, u); - - ++q[1]; - - auto r = u[0] - q[1] * d; - - if (r > q[0]) - { - --q[1]; - r += d; - } - - if (r >= d) - { - ++q[1]; - r -= d; - } - - return {q[1], r}; -} - -inline div_result udivrem_3by2( - uint64_t u2, uint64_t u1, uint64_t u0, uint128 d, uint64_t v) noexcept -{ - auto q = umul(v, u2); - q = fast_add(q, {u1, u2}); - - auto r1 = u1 - q[1] * d[1]; - - auto t = umul(d[0], q[1]); - - auto r = uint128{u0, r1} - t - d; - r1 = r[1]; - - ++q[1]; - - if (r1 >= q[0]) - { - --q[1]; - r += d; - } - - if (r >= d) - { - ++q[1]; - r -= d; - } - - return {q[1], r}; -} - -inline div_result udivrem(uint128 x, uint128 y) noexcept -{ - if (y[1] == 0) - { - INTX_REQUIRE(y[0] != 0); // Division by 0. - - const auto lsh = clz(y[0]); - const auto rsh = (64 - lsh) % 64; - const auto rsh_mask = uint64_t{lsh == 0} - 1; - - const auto yn = y[0] << lsh; - const auto xn_lo = x[0] << lsh; - const auto xn_hi = (x[1] << lsh) | ((x[0] >> rsh) & rsh_mask); - const auto xn_ex = (x[1] >> rsh) & rsh_mask; - - const auto v = reciprocal_2by1(yn); - const auto res1 = udivrem_2by1({xn_hi, xn_ex}, yn, v); - const auto res2 = udivrem_2by1({xn_lo, res1.rem}, yn, v); - return {{res2.quot, res1.quot}, res2.rem >> lsh}; - } - - if (y[1] > x[1]) - return {0, x}; - - const auto lsh = clz(y[1]); - if (lsh == 0) - { - const auto q = unsigned{y[1] < x[1]} | unsigned{y[0] <= x[0]}; - return {q, x - (q ? y : 0)}; - } - - const auto rsh = 64 - lsh; - - const auto yn_lo = y[0] << lsh; - const auto yn_hi = (y[1] << lsh) | (y[0] >> rsh); - const auto xn_lo = x[0] << lsh; - const auto xn_hi = (x[1] << lsh) | (x[0] >> rsh); - const auto xn_ex = x[1] >> rsh; - - const auto v = reciprocal_3by2({yn_lo, yn_hi}); - const auto res = udivrem_3by2(xn_ex, xn_hi, xn_lo, {yn_lo, yn_hi}, v); - - return {res.quot, res.rem >> lsh}; -} - -inline div_result sdivrem(uint128 x, uint128 y) noexcept -{ - constexpr auto sign_mask = uint128{1} << 127; - const auto x_is_neg = (x & sign_mask) != 0; - const auto y_is_neg = (y & sign_mask) != 0; - - const auto x_abs = x_is_neg ? -x : x; - const auto y_abs = y_is_neg ? -y : y; - - const auto q_is_neg = x_is_neg ^ y_is_neg; - - const auto res = udivrem(x_abs, y_abs); - - return {q_is_neg ? -res.quot : res.quot, x_is_neg ? -res.rem : res.rem}; -} - -inline uint128 operator/(uint128 x, uint128 y) noexcept -{ - return udivrem(x, y).quot; -} - -inline uint128 operator%(uint128 x, uint128 y) noexcept -{ - return udivrem(x, y).rem; -} - -inline uint128& operator/=(uint128& x, uint128 y) noexcept -{ - return x = x / y; -} - -inline uint128& operator%=(uint128& x, uint128 y) noexcept -{ - return x = x % y; -} - -/// @} - -} // namespace intx - - -namespace std -{ -template -struct numeric_limits> -{ - using type = intx::uint; - - static constexpr bool is_specialized = true; - static constexpr bool is_integer = true; - static constexpr bool is_signed = false; - static constexpr bool is_exact = true; - static constexpr bool has_infinity = false; - static constexpr bool has_quiet_NaN = false; - static constexpr bool has_signaling_NaN = false; - static constexpr float_denorm_style has_denorm = denorm_absent; - static constexpr bool has_denorm_loss = false; - static constexpr float_round_style round_style = round_toward_zero; - static constexpr bool is_iec559 = false; - static constexpr bool is_bounded = true; - static constexpr bool is_modulo = true; - static constexpr int digits = CHAR_BIT * sizeof(type); - static constexpr int digits10 = int(0.3010299956639812 * digits); - static constexpr int max_digits10 = 0; - static constexpr int radix = 2; - static constexpr int min_exponent = 0; - static constexpr int min_exponent10 = 0; - static constexpr int max_exponent = 0; - static constexpr int max_exponent10 = 0; - static constexpr bool traps = std::numeric_limits::traps; - static constexpr bool tinyness_before = false; - - static constexpr type min() noexcept { return 0; } - static constexpr type lowest() noexcept { return min(); } - static constexpr type max() noexcept { return ~type{0}; } - static constexpr type epsilon() noexcept { return 0; } - static constexpr type round_error() noexcept { return 0; } - static constexpr type infinity() noexcept { return 0; } - static constexpr type quiet_NaN() noexcept { return 0; } - static constexpr type signaling_NaN() noexcept { return 0; } - static constexpr type denorm_min() noexcept { return 0; } -}; -} // namespace std - -namespace intx -{ -template -[[noreturn]] inline void throw_(const char* what) -{ -#if __cpp_exceptions - throw T{what}; -#else - std::fputs(what, stderr); - std::abort(); -#endif -} - -inline constexpr int from_dec_digit(char c) -{ - if (c < '0' || c > '9') - throw_("invalid digit"); - return c - '0'; -} - -inline constexpr int from_hex_digit(char c) -{ - if (c >= 'a' && c <= 'f') - return c - ('a' - 10); - if (c >= 'A' && c <= 'F') - return c - ('A' - 10); - return from_dec_digit(c); -} - -template -inline constexpr Int from_string(const char* str) -{ - auto s = str; - auto x = Int{}; - int num_digits = 0; - - if (s[0] == '0' && s[1] == 'x') - { - s += 2; - while (const auto c = *s++) - { - if (++num_digits > int{sizeof(x) * 2}) - throw_(str); - x = (x << uint64_t{4}) | from_hex_digit(c); - } - return x; - } - - while (const auto c = *s++) - { - if (num_digits++ > std::numeric_limits::digits10) - throw_(str); - - const auto d = from_dec_digit(c); - x = x * Int{10} + d; - if (x < d) - throw_(str); - } - return x; -} - -template -inline constexpr Int from_string(const std::string& s) -{ - return from_string(s.c_str()); -} - -inline constexpr uint128 operator""_u128(const char* s) -{ - return from_string(s); -} - -template -inline std::string to_string(uint x, int base = 10) -{ - if (base < 2 || base > 36) - throw_("invalid base"); - - if (x == 0) - return "0"; - - auto s = std::string{}; - while (x != 0) - { - // TODO: Use constexpr udivrem_1? - const auto res = udivrem(x, uint{base}); - const auto d = int(res.rem); - const auto c = d < 10 ? '0' + d : 'a' + d - 10; - s.push_back(char(c)); - x = res.quot; - } - std::reverse(s.begin(), s.end()); - return s; -} - -template -inline std::string hex(uint x) -{ - return to_string(x, 16); -} -} // namespace intx - -struct Dqn_U128String -{ - // NOTE: Max value 340,282,366,920,938,463,463,374,607,431,768,211,455 - char str[51 + 1]; - int size; -}; - -Dqn_U128String Dqn_U128_String (intx::uint128 x, bool separate, char separate_ch = ','); -intx::uint128 Dqn_U128_FromString(const char *str, int size); -#endif // DQN_U128_H - -#if defined(DQN_U128_IMPLEMENTATION) -Dqn_U128String Dqn_U128_String(intx::uint128 x, bool separate, char separate_ch) -{ - Dqn_U128String val = {}; - if (x == 0) - { - val.str[val.size++] = '0'; - } - else - { - int insert_count = 0; - while (x != 0) - { - // TODO: Use constexpr udivrem_1? - const auto res = udivrem(x, intx::uint128{10 /*base*/}); - const auto d = int(res.rem); - const auto c = d < 10 ? '0' + d : 'a' + d - 10; - val.str[val.size++] = char(c); - x = res.quot; - - if (separate && x != 0) - { - insert_count++; - if (insert_count % 3 == 0) - val.str[val.size++] = separate_ch; - } - } - } - - - Dqn_U128String result = {}; - for (int i = val.size - 1; i >= 0; i--) - result.str[result.size++] = val.str[i]; - return result; -} - -intx::uint128 Dqn_U128_FromString(const char *str, int size) -{ - // TODO(dqn): Don't let this throw - auto x = intx::uint128{}; - int num_digits = 0; - - if (size > 2 && (str[0] == '0' && str[1] == 'x')) - { - for (int str_index = 2; str_index < size; str_index++) - { - char c = str[str_index]; - if (++num_digits > int{sizeof(x) * 2}) - intx::throw_(str); - x = (x << uint64_t{4}) | intx::from_hex_digit(c); - } - return x; - } - - for (int str_index = 0; str_index < size; str_index++) - { - char c = str[str_index]; - if (num_digits++ > std::numeric_limits::digits10) - intx::throw_(str); - - const auto d = intx::from_dec_digit(c); - x = x * intx::uint128{10} + d; - if (x < d) - intx::throw_(str); - } - return x; -} -#endif // DQN_U128_IMPLEMENTATION diff --git a/dqn_cpp_file.h b/dqn_cpp_file.h new file mode 100644 index 0000000..0e32559 --- /dev/null +++ b/dqn_cpp_file.h @@ -0,0 +1,185 @@ +#if !defined(DQN_CPP_FILE_H) +#define DQN_CPP_FILE_H +// ----------------------------------------------------------------------------- +// NOTE: Overview +// ----------------------------------------------------------------------------- +// Utility functions for creating C++ files at run-time. +// +// ----------------------------------------------------------------------------- +// NOTE: Macros +// ----------------------------------------------------------------------------- +// #define DQN_CPP_FILE_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. +// +// #define DQN_CPPF_ASSERT(expr) +// Define this macro to override the default assert used. + +#include +#include + +// ----------------------------------------------------------------------------- +// NOTE: Dqn_CppFile: Helper functions to generate formatted CPP files +// ----------------------------------------------------------------------------- +#if !defined(DQN_CPPF_ASSERT) + #define DQN_CPPF_ASSERT(expr) do { if (!(expr)) { *((volatile int *)0) = 0; } } while (0) +#endif + +struct Dqn_CppFile +{ + FILE *file; // The file to output to. + int indent; // The current indent level + int space_per_indent; // The number of spaces applied per indent. If zero- the functions give a default value. + bool append_extra_new_line; // If true, when code blocks are terminated, an additional new line will be appended for whitespacing. +}; + +// return: The number of spaces per indent at this point of invocation. If +// spaces-per-indent in the CppFile is set to 0 the indent defaults to 4 spaces. +int Dqn_CppFile_SpacePerIndent(Dqn_CppFile const *cpp); + +// Create a line in the C++ file. This is a piece-meal API where you can +// flexibly construct a line by calling {LineBegin, LineAdd, LineEnd}. Calling +// LineEnd terminates the line with a trailing new-line. Calling LineAdd is +// optional if the line can be constructed with just a LineAdd and LineEnd. +void Dqn_CppFile_LineBeginV (Dqn_CppFile *cpp, char const *fmt, va_list args); +void Dqn_CppFile_LineBegin (Dqn_CppFile *cpp, char const *fmt, ...); +void Dqn_CppFile_LineAdd (Dqn_CppFile *cpp, char const *fmt, ...); +void Dqn_CppFile_LineEnd (Dqn_CppFile *cpp, char const *fmt, ...); + +// Create a line in the C++ file and terminate it with a new-line. +void Dqn_CppFile_LineV (Dqn_CppFile *cpp, char const *fmt, va_list args); +void Dqn_CppFile_Line (Dqn_CppFile *cpp, char const *fmt, ...); + +void Dqn_CppFile_NewLine (Dqn_CppFile *cpp); +void Dqn_CppFile_Indent (Dqn_CppFile *cpp); +void Dqn_CppFile_Unindent (Dqn_CppFile *cpp); + +// Begin a C++ code block which is any block that utilises a opening and +// closing brace. +// fmt: (Optional) The format string to print at the beginning of the block. +// When the fmt string is given, it will place a new-line at the end of the fmt +// string. When fmt is nullptr, no new line will be appended. +/* + Dqn_CppFile_Line(&cpp, "void MyFunction(int x, int y)"); + Dqn_CppFile_BeginBlock(&cpp, nullptr); + Dqn_CppFile_Line(&cpp, "int result = x + y;"); + Dqn_CppFile_Line(&cpp, "return result;"); + Dqn_CppFile_EndFuncBlock(&cpp); + + // Generates + // + // void MyFunction(int x, int y) + // { + // int result = x + y; + // return result; + // } + // + */ +void Dqn_CppFile_BeginBlock (Dqn_CppFile *cpp, char const *fmt, ...); +void Dqn_CppFile_EndBlock (Dqn_CppFile *cpp, bool trailing_semicolon, bool new_line_on_next_block); + +#define Dqn_CppFile_EndEnumBlock(cpp) Dqn_CppFile_EndBlock(cpp, true /*trailing_semicolon*/, true /*new_line_on_next_block*/) +#define Dqn_CppFile_EndForBlock(cpp) Dqn_CppFile_EndBlock(cpp, false /*trailing_semicolon*/, false /*new_line_on_next_block*/) +#define Dqn_CppFile_EndIfBlock(cpp) Dqn_CppFile_EndBlock(cpp, false /*trailing_semicolon*/, false /*new_line_on_next_block*/) +#define Dqn_CppFile_EndFuncBlock(cpp) Dqn_CppFile_EndBlock(cpp, false /*trailing_semicolon*/, true /*new_line_on_next_block*/) +#define Dqn_CppFile_EndStructBlock(cpp) Dqn_CppFile_EndBlock(cpp, true /*trailing_semicolon*/, true /*new_line_on_next_block*/) +#endif // DQN_CPP_FILE_H + +#if defined(DQN_CPP_FILE_IMPLEMENTATION) +// ----------------------------------------------------------------------------- +// NOTE: Dqn_CppFile Implementation +// ----------------------------------------------------------------------------- +int Dqn_CppFile_SpacePerIndent(Dqn_CppFile const *cpp) +{ + int result = cpp->space_per_indent == 0 ? 4 : cpp->space_per_indent; + return result; +} + +void Dqn_CppFile_LineBeginV(Dqn_CppFile *cpp, char const *fmt, va_list args) +{ + int spaces = cpp->indent * Dqn_CppFile_SpacePerIndent(cpp); + fprintf(cpp->file, "%*s", spaces, ""); + vfprintf(cpp->file, fmt, args); +} + +void Dqn_CppFile_LineBegin(Dqn_CppFile *cpp, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_CppFile_LineBeginV(cpp, fmt, args); + va_end(args); +} + +void Dqn_CppFile_LineAdd(Dqn_CppFile *cpp, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(cpp->file, fmt, args); + va_end(args); +} + +void Dqn_CppFile_LineEnd(Dqn_CppFile *cpp, char const *fmt, ...) +{ + if (fmt) + { + va_list args; + va_start(args, fmt); + vfprintf(cpp->file, fmt, args); + va_end(args); + } + + fputc('\n', cpp->file); +} + +void Dqn_CppFile_LineV(Dqn_CppFile *cpp, char const *fmt, va_list args) +{ + Dqn_CppFile_LineBeginV(cpp, fmt, args); + Dqn_CppFile_LineEnd(cpp, nullptr); +} + +void Dqn_CppFile_Line(Dqn_CppFile *cpp, char const *fmt, ...) +{ + va_list args; + va_start(args, fmt); + Dqn_CppFile_LineBeginV(cpp, fmt, args); + Dqn_CppFile_LineEnd(cpp, nullptr); + va_end(args); +} + +void Dqn_CppFile_NewLine(Dqn_CppFile *cpp) +{ + fputc('\n', cpp->file); +} + +void Dqn_CppFile_Indent(Dqn_CppFile *cpp) +{ + cpp->indent++; +} + +void Dqn_CppFile_Unindent(Dqn_CppFile *cpp) +{ + cpp->indent--; + DQN_CPPF_ASSERT(cpp->indent >= 0); +} + +void Dqn_CppFile_BeginBlock(Dqn_CppFile *cpp, char const *fmt, ...) +{ + if (fmt) + { + va_list args; + va_start(args, fmt); + Dqn_CppFile_LineV(cpp, fmt, args); + va_end(args); + } + + Dqn_CppFile_Line(cpp, "{"); + Dqn_CppFile_Indent(cpp); +} + +void Dqn_CppFile_EndBlock(Dqn_CppFile *cpp, bool trailing_semicolon, bool new_line_on_next_block) +{ + Dqn_CppFile_Unindent(cpp); + Dqn_CppFile_Line(cpp, trailing_semicolon ? "};" : "}"); + if (new_line_on_next_block) fputc('\n', cpp->file); +} +#endif // DQN_CPP_FILE_IMPLEMENTATION diff --git a/dqn_keccak.h b/dqn_keccak.h new file mode 100644 index 0000000..4d79d7f --- /dev/null +++ b/dqn_keccak.h @@ -0,0 +1,965 @@ +#if !defined(DQN_KECCAK_H) +#define DQN_KECCAK_H +// ----------------------------------------------------------------------------- +// NOTE: Overview +// ----------------------------------------------------------------------------- +// Implementation of the Keccak hashing algorithms from the Keccak and SHA3 +// families (including the FIPS202 published algorithms and the non-finalized +// ones, i.e. the ones used in Ethereum and Monero which adopted SHA3 before it +// was finalized. The only difference between the 2 is a different delimited +// suffix). +// +// ----------------------------------------------------------------------------- +// NOTE: MIT License +// ----------------------------------------------------------------------------- +// Copyright (c) 2021 github.com/doy-lee +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// +// ----------------------------------------------------------------------------- +// NOTE: Macros +// ----------------------------------------------------------------------------- +// #define DQN_KECCAK_IMPLEMENTATION +// Define this in one and only one C++ file to enable the implementation +// code of the header file. +// +// #define DQN_KECCAK_MEMCOPY +// Define this macro to override the memcpy implementation and avoid pulling +// in string.h. +// +// #define DQN_KECCAK_MEMCMP +// Define this macro to override the memcmp implementation and avoid pulling +// in string.h. +// +// #define DQN_NO_MALLOC_FUNCTIONS +// Define this macro to disable the non-essential helper functions that use +// malloc. +// +// #define DQN_KECCAK_MALLOC +// Define this macro to override the malloc implementation. This library +// provides helper functions that use malloc if DQN_NO_MALLOC_FUNCTIONS is +// not defined. +// +// #define DQN_KECCAK_ASSERT +// Define this macro to override the assert implementation. + +#if !defined(DQN_KECCAK_MEMCOPY) + #include + #define DQN_KECCAK_MEMCOPY(dest, src, count) memcpy(dest, src, count) +#endif + +#if !defined(DQN_KECCAK_MEMCMP) + #include + #define DQN_KECCAK_MEMCMP(dest, src, count) memcmp(dest, src, count) +#endif + +#if !defined(DQN_NO_MALLOC_FUNCTIONS) + #if !defined(DQN_KECCAK_MALLOC) + #include + #define DQN_KECCAK_MALLOC(size) malloc(size) + #endif +#endif + +#if !defined(DQN_KECCAK_ASSERT) + #if defined(NDEBUG) + #define DQN_KECCAK_ASSERT(expr) + #else + #define DQN_KECCAK_ASSERT(expr) \ + do \ + { \ + if (!(expr)) \ + { \ + (*(volatile int *)0) = 0; \ + } \ + } while (0) + #endif +#endif + +// Use this macro in a printf-like function, +/* + Dqn_KeccakString64 string = {}; + printf("%.*s\n", DQN_KECCAK_STRING64_FMT(string)); +*/ +#define DQN_KECCAK_STRING56_FMT(string) 56, string +#define DQN_KECCAK_STRING64_FMT(string) 64, string +#define DQN_KECCAK_STRING96_FMT(string) 96, string +#define DQN_KECCAK_STRING128_FMT(string) 128, string + +// ----------------------------------------------------------------------------- +// NOTE: Data structures +// ----------------------------------------------------------------------------- +struct Dqn_KeccakBytes28 { char data[28]; }; // 224 bit +struct Dqn_KeccakBytes32 { char data[32]; }; // 256 bit +struct Dqn_KeccakBytes48 { char data[48]; }; // 384 bit +struct Dqn_KeccakBytes64 { char data[64]; }; // 512 bit + +struct Dqn_KeccakString56 { char str[(sizeof(Dqn_KeccakBytes28) * 2) + 1]; }; +struct Dqn_KeccakString64 { char str[(sizeof(Dqn_KeccakBytes32) * 2) + 1]; }; +struct Dqn_KeccakString96 { char str[(sizeof(Dqn_KeccakBytes48) * 2) + 1]; }; +struct Dqn_KeccakString128 { char str[(sizeof(Dqn_KeccakBytes64) * 2) + 1]; }; + +// ----------------------------------------------------------------------------- +// NOTE: API +// ----------------------------------------------------------------------------- +// TODO(dqn): Write a streaming API +typedef unsigned char Dqn_Keccak_u8; +typedef unsigned short Dqn_Keccak_u16; +typedef unsigned int Dqn_Keccak_u32; +typedef unsigned int Dqn_Keccak_uint; +#ifdef _MSC_VER + typedef unsigned __int64 Dqn_Keccak_u64; +#else + typedef unsigned long long Dqn_Keccak_u64; +#endif + +// ----------------------------------------------------------------------------- +// NOTE: SHA3 +// ----------------------------------------------------------------------------- +// Applies the FIPS 202 SHA3 algorithm on the supplied buffer. The size of +// the returned hash is (bit-rate/8) bytes, i.e. the dest_size must be at least +// 32 bytes for SHA-256 and so forth. + +// dest_size: The passed in destination buffer must be >= 28 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_SHA3_224(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes28 Dqn_Keccak_SHA3_224_ToBytes28(void *bytes, Dqn_Keccak_u64 bytes_size); + +// dest_size: The passed in destination buffer must be >= 32 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_SHA3_256(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes32 Dqn_Keccak_SHA3_256_ToBytes32(void *bytes, Dqn_Keccak_u64 bytes_size); + +// dest_size: The passed in destination buffer must be >= 48 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_SHA3_384(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes48 Dqn_Keccak_SHA3_384_ToBytes48(void *bytes, Dqn_Keccak_u64 bytes_size); + +// dest_size: The passed in destination buffer must be >= 64 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_SHA3_512(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes64 Dqn_Keccak_SHA3_512_ToBytes64(void *bytes, Dqn_Keccak_u64 bytes_size); + +#if defined(DQN_H) +// ----------------------------------------------------------------------------- +// NOTE: SHA3 - Helpers for Dqn data structures +// ----------------------------------------------------------------------------- +Dqn_KeccakBytes28 Dqn_Keccak_SHA3_224_StringToBytes28(Dqn_String string); +Dqn_KeccakBytes32 Dqn_Keccak_SHA3_256_StringToBytes32(Dqn_String string); +Dqn_KeccakBytes48 Dqn_Keccak_SHA3_384_StringToBytes48(Dqn_String string); +Dqn_KeccakBytes64 Dqn_Keccak_SHA3_512_StringToBytes64(Dqn_String string); + +Dqn_KeccakBytes28 Dqn_Keccak_SHA3_224_U8ArrayToBytes28(Dqn_Array array); +Dqn_KeccakBytes32 Dqn_Keccak_SHA3_256_U8ArrayToBytes32(Dqn_Array array); +Dqn_KeccakBytes48 Dqn_Keccak_SHA3_384_U8ArrayToBytes48(Dqn_Array array); +Dqn_KeccakBytes64 Dqn_Keccak_SHA3_512_U8ArrayToBytes64(Dqn_Array array); +#endif // DQN_H + +// ----------------------------------------------------------------------------- +// NOTE: Keccak +// ----------------------------------------------------------------------------- +// Applies the non-finalized SHA3 algorithm (i.e. a delimited suffix of 0x1 +// instead of 0x6 in SHA3). This is the version of the algorithm used by +// Ethereum and Monero as they adopted SHA3 before it was finalized. + +// dest_size: The passed in destination buffer must be >= 28 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_224(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes28 Dqn_Keccak_224_ToBytes28(void *bytes, Dqn_Keccak_u64 bytes_size); + +// dest_size: The passed in destination buffer must be >= 32 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_256(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes32 Dqn_Keccak_256_ToBytes32(void *bytes, Dqn_Keccak_u64 bytes_size); + +// dest_size: The passed in destination buffer must be >= 48 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_384(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes48 Dqn_Keccak_384_ToBytes48(void *bytes, Dqn_Keccak_u64 bytes_size); + +// dest_size: The passed in destination buffer must be >= 64 (bytes), otherwise +// the function asserts or does no-op in release. +void Dqn_Keccak_512(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size); +Dqn_KeccakBytes64 Dqn_Keccak_512_ToBytes64(void *bytes, Dqn_Keccak_u64 bytes_size); + +#if defined(DQN_H) +// ----------------------------------------------------------------------------- +// NOTE: Keccak - Helpers for Dqn data structures +// ----------------------------------------------------------------------------- +Dqn_KeccakBytes28 Dqn_Keccak_224_StringToBytes28(Dqn_String string); +Dqn_KeccakBytes32 Dqn_Keccak_256_StringToBytes32(Dqn_String string); +Dqn_KeccakBytes48 Dqn_Keccak_384_StringToBytes48(Dqn_String string); +Dqn_KeccakBytes64 Dqn_Keccak_512_StringToBytes64(Dqn_String string); + +Dqn_KeccakBytes28 Dqn_Keccak_224_U8ArrayToBytes28(Dqn_Array array); +Dqn_KeccakBytes32 Dqn_Keccak_256_U8ArrayToBytes32(Dqn_Array array); +Dqn_KeccakBytes48 Dqn_Keccak_384_U8ArrayToBytes48(Dqn_Array array); +Dqn_KeccakBytes64 Dqn_Keccak_512_U8ArrayToBytes64(Dqn_Array array); +#endif // DQN_H + +// ----------------------------------------------------------------------------- +// NOTE: Helper functions +// ----------------------------------------------------------------------------- +// Convert a binary buffer into its hex representation into dest. The dest +// buffer must be large enough to contain the hex representation, i.e. +// at least src_size * 2). This function does *not* null-terminate the buffer. +// The returned result does *not* include a leading 0x prefix. +void Dqn_Keccak_BytesToHex(void const *src, Dqn_Keccak_u64 src_size, char *dest, Dqn_Keccak_u64 dest_size); + +#if defined(DQN_NO_MALLOC_FUNCTIONS) +// Convert the src bytes into a null-terminated c-string using malloc. Calls +// into Dqn_Keccak_BytesToHex under the hood. +// src: If src is nullptr, the function returns an empty null-terminated +// string, otherwise, the bytes will be converted and returned as hex. +// return: A null-terminated c-string. This string must be freed by the user +// using the CRT free(..). If malloc fails the returned string is nullptr. +char *Dqn_Keccak_BytesToHexCString(void const *src, Dqn_Keccak_u64 src_size); +#endif // DQN_NO_MALLOC_FUNCTIONS + +// Converts a fixed amount of bytes into a hexadecimal string. Helper functions +// that call into Dqn_Keccak_BytesToHex. +// return: The hexadecimal string of the bytes, null-terminated. +Dqn_KeccakString56 Dqn_Keccak_Bytes28ToHex(Dqn_KeccakBytes28 const *bytes); +Dqn_KeccakString64 Dqn_Keccak_Bytes32ToHex(Dqn_KeccakBytes32 const *bytes); +Dqn_KeccakString96 Dqn_Keccak_Bytes48ToHex(Dqn_KeccakBytes48 const *bytes); +Dqn_KeccakString128 Dqn_Keccak_Bytes64ToHex(Dqn_KeccakBytes64 const *bytes); + +// Compares byte data structures for byte equality (via memcmp). +// return: 1 if the contents are equal otherwise 0. +int Dqn_Keccak_Bytes28Equals(Dqn_KeccakBytes28 const *a, Dqn_KeccakBytes28 const *b); +int Dqn_Keccak_Bytes32Equals(Dqn_KeccakBytes32 const *a, Dqn_KeccakBytes32 const *b); +int Dqn_Keccak_Bytes48Equals(Dqn_KeccakBytes48 const *a, Dqn_KeccakBytes48 const *b); +int Dqn_Keccak_Bytes64Equals(Dqn_KeccakBytes64 const *a, Dqn_KeccakBytes64 const *b); + +#if defined(DQN_H) +// ----------------------------------------------------------------------------- +// NOTE: Other helper functions for Dqn data structures +// ----------------------------------------------------------------------------- +// Converts a 64 character hex string into the 32 byte binary representation. +// Invalid hex characters in the string will be represented as 0. +// hex: Must be exactly a 64 character hex string. +Dqn_KeccakBytes32 Dqn_Keccak_Hex64StringToBytes(Dqn_String hex); +#endif // DQN_H +#endif // DQN_KECCAK_H + +#if defined(DQN_KECCAK_IMPLEMENTATION) +Dqn_Keccak_u64 const DQN_KECCAK_ROUNDS[] = { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808A, 0x8000000080008000, 0x000000000000808B, + 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008A, 0x0000000000000088, + 0x0000000080008009, 0x000000008000000A, 0x000000008000808B, 0x800000000000008B, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800A, 0x800000008000000A, + 0x8000000080008081, 0x8000000000008080, 0x0000000080000001, 0x8000000080008008, +}; + +Dqn_Keccak_u64 const DQN_KECCAK_ROTATIONS[][5] = +{ + {0, 36, 3, 41, 18}, + {1, 44, 10, 45, 2}, + {62, 6, 43, 15, 61}, + {28, 55, 25, 21, 56}, + {27, 20, 39, 8, 14}, +}; + +#define DQN_KECCAK_LANE_SIZE_U64 5 +#define DQN_KECCAK_ROL64(val, rotate) (((val) << (rotate)) | (((val) >> (64 - (rotate))))) + +static void Dqn_Keccak__ReferencePermute(Dqn_Keccak_u64 *A) +{ + // TODO(dqn): Do some more tests and remove. + // NOTE: Reference permutation taken for cross-reference and back verifying + // against from digestpp by kerukuro + // https://github.com/kerukuro/digestpp/blob/master/algorithm/detail/sha3_provider.hpp + + for (int round = 0; round < 24; round++) + { + Dqn_Keccak_u64 C[5], D[5]; + C[0] = A[0 * 5 + 0] ^ A[1 * 5 + 0] ^ A[2 * 5 + 0] ^ A[3 * 5 + 0] ^ A[4 * 5 + 0]; + C[1] = A[0 * 5 + 1] ^ A[1 * 5 + 1] ^ A[2 * 5 + 1] ^ A[3 * 5 + 1] ^ A[4 * 5 + 1]; + C[2] = A[0 * 5 + 2] ^ A[1 * 5 + 2] ^ A[2 * 5 + 2] ^ A[3 * 5 + 2] ^ A[4 * 5 + 2]; + C[3] = A[0 * 5 + 3] ^ A[1 * 5 + 3] ^ A[2 * 5 + 3] ^ A[3 * 5 + 3] ^ A[4 * 5 + 3]; + C[4] = A[0 * 5 + 4] ^ A[1 * 5 + 4] ^ A[2 * 5 + 4] ^ A[3 * 5 + 4] ^ A[4 * 5 + 4]; + + D[0] = C[4] ^ DQN_KECCAK_ROL64(C[1], 1); + D[1] = C[0] ^ DQN_KECCAK_ROL64(C[2], 1); + D[2] = C[1] ^ DQN_KECCAK_ROL64(C[3], 1); + D[3] = C[2] ^ DQN_KECCAK_ROL64(C[4], 1); + D[4] = C[3] ^ DQN_KECCAK_ROL64(C[0], 1); + + Dqn_Keccak_u64 B00 = A[0 * 5 + 0] ^ D[0]; + Dqn_Keccak_u64 B10 = DQN_KECCAK_ROL64(A[0 * 5 + 1] ^ D[1], 1); + Dqn_Keccak_u64 B20 = DQN_KECCAK_ROL64(A[0 * 5 + 2] ^ D[2], 62); + Dqn_Keccak_u64 B5 = DQN_KECCAK_ROL64(A[0 * 5 + 3] ^ D[3], 28); + Dqn_Keccak_u64 B15 = DQN_KECCAK_ROL64(A[0 * 5 + 4] ^ D[4], 27); + + Dqn_Keccak_u64 B16 = DQN_KECCAK_ROL64(A[1 * 5 + 0] ^ D[0], 36); + Dqn_Keccak_u64 B1 = DQN_KECCAK_ROL64(A[1 * 5 + 1] ^ D[1], 44); + Dqn_Keccak_u64 B11 = DQN_KECCAK_ROL64(A[1 * 5 + 2] ^ D[2], 6); + Dqn_Keccak_u64 B21 = DQN_KECCAK_ROL64(A[1 * 5 + 3] ^ D[3], 55); + Dqn_Keccak_u64 B6 = DQN_KECCAK_ROL64(A[1 * 5 + 4] ^ D[4], 20); + + Dqn_Keccak_u64 B7 = DQN_KECCAK_ROL64(A[2 * 5 + 0] ^ D[0], 3); + Dqn_Keccak_u64 B17 = DQN_KECCAK_ROL64(A[2 * 5 + 1] ^ D[1], 10); + Dqn_Keccak_u64 B2 = DQN_KECCAK_ROL64(A[2 * 5 + 2] ^ D[2], 43); + Dqn_Keccak_u64 B12 = DQN_KECCAK_ROL64(A[2 * 5 + 3] ^ D[3], 25); + Dqn_Keccak_u64 B22 = DQN_KECCAK_ROL64(A[2 * 5 + 4] ^ D[4], 39); + + Dqn_Keccak_u64 B23 = DQN_KECCAK_ROL64(A[3 * 5 + 0] ^ D[0], 41); + Dqn_Keccak_u64 B8 = DQN_KECCAK_ROL64(A[3 * 5 + 1] ^ D[1], 45); + Dqn_Keccak_u64 B18 = DQN_KECCAK_ROL64(A[3 * 5 + 2] ^ D[2], 15); + Dqn_Keccak_u64 B3 = DQN_KECCAK_ROL64(A[3 * 5 + 3] ^ D[3], 21); + Dqn_Keccak_u64 B13 = DQN_KECCAK_ROL64(A[3 * 5 + 4] ^ D[4], 8); + + Dqn_Keccak_u64 B14 = DQN_KECCAK_ROL64(A[4 * 5 + 0] ^ D[0], 18); + Dqn_Keccak_u64 B24 = DQN_KECCAK_ROL64(A[4 * 5 + 1] ^ D[1], 2); + Dqn_Keccak_u64 B9 = DQN_KECCAK_ROL64(A[4 * 5 + 2] ^ D[2], 61); + Dqn_Keccak_u64 B19 = DQN_KECCAK_ROL64(A[4 * 5 + 3] ^ D[3], 56); + Dqn_Keccak_u64 B4 = DQN_KECCAK_ROL64(A[4 * 5 + 4] ^ D[4], 14); + +#if 0 + printf("B00: %024llu\n", A[0 * 5 + 0] ^ D[0]); + printf("B10: %024llu\n", DQN_KECCAK_ROL64(A[0 * 5 + 1] ^ D[1], 1)); + printf("B20: %024llu\n", DQN_KECCAK_ROL64(A[0 * 5 + 2] ^ D[2], 62)); + printf("B05: %024llu\n", DQN_KECCAK_ROL64(A[0 * 5 + 3] ^ D[3], 28)); + printf("B15: %024llu\n\n", DQN_KECCAK_ROL64(A[0 * 5 + 4] ^ D[4], 27)); + + printf("B16: %024llu\n", DQN_KECCAK_ROL64(A[1 * 5 + 0] ^ D[0], 36)); + printf("B01: %024llu\n", DQN_KECCAK_ROL64(A[1 * 5 + 1] ^ D[1], 44)); + printf("B11: %024llu\n", DQN_KECCAK_ROL64(A[1 * 5 + 2] ^ D[2], 6)); + printf("B21: %024llu\n", DQN_KECCAK_ROL64(A[1 * 5 + 3] ^ D[3], 55)); + printf("B06: %024llu\n\n", DQN_KECCAK_ROL64(A[1 * 5 + 4] ^ D[4], 20)); + + printf("B07: %024llu\n", DQN_KECCAK_ROL64(A[2 * 5 + 0] ^ D[0], 3)); + printf("B17: %024llu\n", DQN_KECCAK_ROL64(A[2 * 5 + 1] ^ D[1], 10)); + printf("B02: %024llu\n", DQN_KECCAK_ROL64(A[2 * 5 + 2] ^ D[2], 43)); + printf("B12: %024llu\n", DQN_KECCAK_ROL64(A[2 * 5 + 3] ^ D[3], 25)); + printf("B22: %024llu\n\n", DQN_KECCAK_ROL64(A[2 * 5 + 4] ^ D[4], 39)); + + printf("B23: %024llu\n", DQN_KECCAK_ROL64(A[3 * 5 + 0] ^ D[0], 41)); + printf("B08: %024llu\n", DQN_KECCAK_ROL64(A[3 * 5 + 1] ^ D[1], 45)); + printf("B18: %024llu\n", DQN_KECCAK_ROL64(A[3 * 5 + 2] ^ D[2], 15)); + printf("B03: %024llu\n", DQN_KECCAK_ROL64(A[3 * 5 + 3] ^ D[3], 21)); + printf("B13: %024llu\n\n", DQN_KECCAK_ROL64(A[3 * 5 + 4] ^ D[4], 8)); + + printf("B14: %024llu\n", DQN_KECCAK_ROL64(A[4 * 5 + 0] ^ D[0], 18)); + printf("B24: %024llu\n", DQN_KECCAK_ROL64(A[4 * 5 + 1] ^ D[1], 2)); + printf("B09: %024llu\n", DQN_KECCAK_ROL64(A[4 * 5 + 2] ^ D[2], 61)); + printf("B19: %024llu\n", DQN_KECCAK_ROL64(A[4 * 5 + 3] ^ D[3], 56)); + printf("B04: %024llu\n\n", DQN_KECCAK_ROL64(A[4 * 5 + 4] ^ D[4], 14)); +#endif + + A[0 * 5 + 0] = B00 ^ ((~B1) & B2); + A[0 * 5 + 1] = B1 ^ ((~B2) & B3); + A[0 * 5 + 2] = B2 ^ ((~B3) & B4); + A[0 * 5 + 3] = B3 ^ ((~B4) & B00); + A[0 * 5 + 4] = B4 ^ ((~B00) & B1); + + A[1 * 5 + 0] = B5 ^ ((~B6) & B7); + A[1 * 5 + 1] = B6 ^ ((~B7) & B8); + A[1 * 5 + 2] = B7 ^ ((~B8) & B9); + A[1 * 5 + 3] = B8 ^ ((~B9) & B5); + A[1 * 5 + 4] = B9 ^ ((~B5) & B6); + + A[2 * 5 + 0] = B10 ^ ((~B11) & B12); + A[2 * 5 + 1] = B11 ^ ((~B12) & B13); + A[2 * 5 + 2] = B12 ^ ((~B13) & B14); + A[2 * 5 + 3] = B13 ^ ((~B14) & B10); + A[2 * 5 + 4] = B14 ^ ((~B10) & B11); + + A[3 * 5 + 0] = B15 ^ ((~B16) & B17); + A[3 * 5 + 1] = B16 ^ ((~B17) & B18); + A[3 * 5 + 2] = B17 ^ ((~B18) & B19); + A[3 * 5 + 3] = B18 ^ ((~B19) & B15); + A[3 * 5 + 4] = B19 ^ ((~B15) & B16); + + A[4 * 5 + 0] = B20 ^ ((~B21) & B22); + A[4 * 5 + 1] = B21 ^ ((~B22) & B23); + A[4 * 5 + 2] = B22 ^ ((~B23) & B24); + A[4 * 5 + 3] = B23 ^ ((~B24) & B20); + A[4 * 5 + 4] = B24 ^ ((~B20) & B21); + + A[0] ^= DQN_KECCAK_ROUNDS[round]; + +#if 0 + for (int y = 0; y < 5; y++) + { + for (int x = 0; x < 5; x++) + { + Dqn_Keccak_u64 lane = A[x + (y * 5)]; + printf("[%d,%d] %024llu ", x, y, lane); + } + + printf("\n"); + } +#endif + + } +} + +void Dqn_Keccak__Permute(void *state) +{ + // TODO(dqn): Do some profiling on unrolling and can we SIMD some part of + // this? Unroll loop, look at data dependencies and investigate. + +#if 1 + Dqn_Keccak_u64 *lanes_u64 = (Dqn_Keccak_u64 *)state; + for (int round_index = 0; round_index < 24; round_index++) + { + #define LANE_INDEX(x, y) ((x) + ((y) * DQN_KECCAK_LANE_SIZE_U64)) + // --------------------------------------------------------------------- + // θ step + // --------------------------------------------------------------------- +#if 1 + Dqn_Keccak_u64 c[DQN_KECCAK_LANE_SIZE_U64]; + for (int x = 0; x < DQN_KECCAK_LANE_SIZE_U64; x++) + { + c[x] = lanes_u64[LANE_INDEX(x, 0)] ^ + lanes_u64[LANE_INDEX(x, 1)] ^ + lanes_u64[LANE_INDEX(x, 2)] ^ + lanes_u64[LANE_INDEX(x, 3)] ^ + lanes_u64[LANE_INDEX(x, 4)]; + } + + Dqn_Keccak_u64 d[DQN_KECCAK_LANE_SIZE_U64]; + for (int x = 0; x < DQN_KECCAK_LANE_SIZE_U64; x++) + d[x] = c[(x + 4) % DQN_KECCAK_LANE_SIZE_U64] ^ DQN_KECCAK_ROL64(c[(x + 1) % DQN_KECCAK_LANE_SIZE_U64], 1); + + for (int y = 0; y < DQN_KECCAK_LANE_SIZE_U64; y++) + for (int x = 0; x < DQN_KECCAK_LANE_SIZE_U64; x++) + lanes_u64[LANE_INDEX(x, y)] ^= d[x]; +#else + Dqn_Keccak_u64 c[5], d[5]; + c[0] = lanes_u64[0 * 5 + 0] ^ lanes_u64[1 * 5 + 0] ^ lanes_u64[2 * 5 + 0] ^ lanes_u64[3 * 5 + 0] ^ lanes_u64[4 * 5 + 0]; + c[1] = lanes_u64[0 * 5 + 1] ^ lanes_u64[1 * 5 + 1] ^ lanes_u64[2 * 5 + 1] ^ lanes_u64[3 * 5 + 1] ^ lanes_u64[4 * 5 + 1]; + c[2] = lanes_u64[0 * 5 + 2] ^ lanes_u64[1 * 5 + 2] ^ lanes_u64[2 * 5 + 2] ^ lanes_u64[3 * 5 + 2] ^ lanes_u64[4 * 5 + 2]; + c[3] = lanes_u64[0 * 5 + 3] ^ lanes_u64[1 * 5 + 3] ^ lanes_u64[2 * 5 + 3] ^ lanes_u64[3 * 5 + 3] ^ lanes_u64[4 * 5 + 3]; + c[4] = lanes_u64[0 * 5 + 4] ^ lanes_u64[1 * 5 + 4] ^ lanes_u64[2 * 5 + 4] ^ lanes_u64[3 * 5 + 4] ^ lanes_u64[4 * 5 + 4]; + + d[0] = c[4] ^ DQN_KECCAK_ROL64(c[1], 1); + d[1] = c[0] ^ DQN_KECCAK_ROL64(c[2], 1); + d[2] = c[1] ^ DQN_KECCAK_ROL64(c[3], 1); + d[3] = c[2] ^ DQN_KECCAK_ROL64(c[4], 1); + d[4] = c[3] ^ DQN_KECCAK_ROL64(c[0], 1); +#endif + + // --------------------------------------------------------------------- + // ρ and π steps + // --------------------------------------------------------------------- + Dqn_Keccak_u64 b[DQN_KECCAK_LANE_SIZE_U64 * DQN_KECCAK_LANE_SIZE_U64]; + for (int y = 0; y < DQN_KECCAK_LANE_SIZE_U64; y++) + { + for (int x = 0; x < DQN_KECCAK_LANE_SIZE_U64; x++) + { + Dqn_Keccak_u64 lane = lanes_u64[LANE_INDEX(x, y)]; + Dqn_Keccak_u64 rotate_count = DQN_KECCAK_ROTATIONS[x][y]; + b[LANE_INDEX(y, (2 * x + 3 * y) % 5)] = DQN_KECCAK_ROL64(lane, rotate_count); + +#if 0 + int index = LANE_INDEX(y, (2 * x + 3 * y) % 5); + printf("B%02d: %024llu\n", index, b[index]); +#endif + } + +#if 0 + printf("\n"); +#endif + } + + // --------------------------------------------------------------------- + // χ step + // --------------------------------------------------------------------- + for (int y = 0; y < DQN_KECCAK_LANE_SIZE_U64; y++) + { + for (int x = 0; x < DQN_KECCAK_LANE_SIZE_U64; x++) + { + Dqn_Keccak_u64 rhs = ~b[LANE_INDEX((x + 1) % 5, y)] & + b[LANE_INDEX((x + 2) % 5, y)]; + + lanes_u64[LANE_INDEX(x, y)] = b[LANE_INDEX(x, y)] ^ rhs; + } + } + + // --------------------------------------------------------------------- + // ι step + // --------------------------------------------------------------------- + lanes_u64[LANE_INDEX(0, 0)] ^= DQN_KECCAK_ROUNDS[round_index]; + +#if 0 + for (int y = 0; y < 5; y++) + { + for (int x = 0; x < 5; x++) + { + Dqn_Keccak_u64 lane = lanes_u64[x + (y * 5)]; + printf("[%d,%d] %024llu ", x, y, lane); + } + + printf("\n"); + } +#endif + + #undef LANE_INDEX + #undef DQN_KECCAK_ROL64 + } +#else + Dqn_Keccak__ReferencePermute((Dqn_Keccak_u64 *)state); +#endif +} + +static void Dqn_Keccak__Construction(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size, int bitrate, char delimited_suffix) +{ + int const ABSORB_SIZE = bitrate / 8; + int const ABSORB_SIZE_U64 = ABSORB_SIZE / 8; + Dqn_Keccak_u64 state_u64[DQN_KECCAK_LANE_SIZE_U64 * DQN_KECCAK_LANE_SIZE_U64] = {}; + + // --------------------------------------------------------------------- + // Sponge Step: Absorb all the bytes into the state + // --------------------------------------------------------------------- + Dqn_Keccak_u64 const *block = (Dqn_Keccak_u64 const *)src; + int const absorb_count = src_size / ABSORB_SIZE; + for (int absorb_index = 0; absorb_index < absorb_count; absorb_index++, block += ABSORB_SIZE_U64) + { + for (int index = 0; index < ABSORB_SIZE_U64; index++) + state_u64[index] ^= block[index]; + Dqn_Keccak__Permute(state_u64); + } + + // --------------------------------------------------------------------- + // Sponge Finalization Step: Remaining source bytes + padding + // --------------------------------------------------------------------- + // NOTE: Do the remainder bytes not divisible in the block, then, also, + // complete the sponge by adding the padding bits and delimited suffix. + { + Dqn_Keccak_u8 * state_u8 = (Dqn_Keccak_u8 *) state_u64; + Dqn_Keccak_u8 const *block_u8 = (Dqn_Keccak_u8 const *) block; + int const remainder = src_size % ABSORB_SIZE; + for (int index = 0; index < remainder; index++) + state_u8[index] ^= block_u8[index]; + + // NOTE: (remainder + 1) can never point out of array bounds, the + // remainder is guaranteed to be less than the ABSORB_SIZE, since we + // processed all the full blocks above. + int const delimited_suffix_index = remainder; + int const INDEX_OF_0X80_BYTE = ABSORB_SIZE - 1; + state_u8[delimited_suffix_index] ^= delimited_suffix; + + // NOTE: In the reference implementation, it checks that if the + // delimited suffix is set to the padding bit (0x80), then we need to + // permute twice. Once for the delimited suffix, and a second time for + // the "padding" permute. + // + // However all standard algorithms either specify a 0x01, or 0x06, 0x04 + // delimited suffix and so forth- so this case is never hit. We can omit + // this from the implementation here. + + state_u8[INDEX_OF_0X80_BYTE] ^= 0x80; + Dqn_Keccak__Permute(state_u64); + } + + // --------------------------------------------------------------------- + // Squeeze Step: Squeeze bytes from the state into our hash + // --------------------------------------------------------------------- + Dqn_Keccak_u8 * dest_u8 = (Dqn_Keccak_u8 *) dest; + int const squeeze_count = dest_size / ABSORB_SIZE; + int squeeze_index = 0; + for (; squeeze_index < squeeze_count; squeeze_index++) + { + if (squeeze_index) Dqn_Keccak__Permute(state_u64); + DQN_KECCAK_MEMCOPY(dest_u8, state_u64, ABSORB_SIZE); + dest_u8 += ABSORB_SIZE; + } + + // --------------------------------------------------------------------- + // Squeeze Finalisation Step: Remainder bytes in hash + // --------------------------------------------------------------------- + { + int const remainder = dest_size % ABSORB_SIZE; + if (remainder) + { + if (squeeze_index) Dqn_Keccak__Permute(state_u64); + DQN_KECCAK_MEMCOPY(dest_u8, state_u64, remainder); + } + } +} + +#define DQN_KECCAK_SHA3_DELIMITED_SUFFIX 0x06 +#define DQN_KECCAK_DELIMITED_SUFFIX 0x01 + +// ----------------------------------------------------------------------------- +// NOTE: SHA3-224 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_SHA3_224(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 1152; + DQN_KECCAK_ASSERT(dest_size >= 224 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_SHA3_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes28 Dqn_Keccak_SHA3_224_ToBytes28(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes28 result; + Dqn_Keccak_SHA3_224(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +// ----------------------------------------------------------------------------- +// NOTE: SHA3-256 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_SHA3_256(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 1088; + DQN_KECCAK_ASSERT(dest_size >= 256 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_SHA3_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes32 Dqn_Keccak_SHA3_256_ToBytes32(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes32 result; + Dqn_Keccak_SHA3_256(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +// ----------------------------------------------------------------------------- +// NOTE: SHA3-384 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_SHA3_384(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 832; + DQN_KECCAK_ASSERT(dest_size >= 384 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_SHA3_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes48 Dqn_Keccak_SHA3_384_ToBytes48(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes48 result; + Dqn_Keccak_SHA3_384(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +// ----------------------------------------------------------------------------- +// NOTE: SHA3-512 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_SHA3_512(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 576; + DQN_KECCAK_ASSERT(dest_size >= 512 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_SHA3_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes64 Dqn_Keccak_SHA3_512_ToBytes64(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes64 result; + Dqn_Keccak_SHA3_512(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +#if defined(DQN_H) +// ----------------------------------------------------------------------------- +// NOTE: SHA3 - Helpers for Dqn data structures +// ----------------------------------------------------------------------------- +Dqn_KeccakBytes28 Dqn_Keccak_SHA3_224_StringToBytes28(Dqn_String string) +{ + Dqn_KeccakBytes28 result; + Dqn_Keccak_SHA3_224(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes28 Dqn_Keccak_SHA3_224_U8ArrayToBytes28(Dqn_Array array) +{ + Dqn_KeccakBytes28 result; + Dqn_Keccak_SHA3_224(array.data, array.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes32 Dqn_Keccak_SHA3_256_StringToBytes32(Dqn_String string) +{ + Dqn_KeccakBytes32 result; + Dqn_Keccak_SHA3_256(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes32 Dqn_Keccak_SHA3_256_U8ArrayToBytes32(Dqn_Array array) +{ + Dqn_KeccakBytes32 result; + Dqn_Keccak_SHA3_256(array.data, array.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes48 Dqn_Keccak_SHA3_384_StringToBytes48(Dqn_String string) +{ + Dqn_KeccakBytes48 result; + Dqn_Keccak_SHA3_384(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes48 Dqn_Keccak_SHA3_384_U8ArrayToBytes48(Dqn_Array array) +{ + Dqn_KeccakBytes48 result; + Dqn_Keccak_SHA3_384(array.data, array.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes64 Dqn_Keccak_SHA3_512_StringToBytes64(Dqn_String string) +{ + Dqn_KeccakBytes64 result; + Dqn_Keccak_SHA3_512(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes64 Dqn_Keccak_SHA3_512_U8ArrayToBytes64(Dqn_Array array) +{ + Dqn_KeccakBytes64 result; + Dqn_Keccak_SHA3_512(array.data, array.size, result.data, sizeof(result)); + return result; +} +#endif // DQN_H + +// ----------------------------------------------------------------------------- +// NOTE: Keccak-224 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_224(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 1152; + DQN_KECCAK_ASSERT(dest_size >= 224 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes28 Dqn_Keccak_224_ToBytes28(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes28 result; + Dqn_Keccak_224(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +// ----------------------------------------------------------------------------- +// NOTE: Keccak-256 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_256(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 1088; + DQN_KECCAK_ASSERT(dest_size >= 256 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes32 Dqn_Keccak_256_ToBytes32(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes32 result; + Dqn_Keccak_256(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +// ----------------------------------------------------------------------------- +// NOTE: Keccak-384 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_384(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 832; + DQN_KECCAK_ASSERT(dest_size >= 384 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes48 Dqn_Keccak_384_ToBytes48(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes48 result; + Dqn_Keccak_384(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +// ----------------------------------------------------------------------------- +// NOTE: Keccak-512 +// ----------------------------------------------------------------------------- +void Dqn_Keccak_512(void const *src, Dqn_Keccak_u64 src_size, void *dest, int dest_size) +{ + int const BITRATE = 576; + DQN_KECCAK_ASSERT(dest_size >= 512 / 8); + Dqn_Keccak__Construction(src, src_size, dest, dest_size, BITRATE, DQN_KECCAK_DELIMITED_SUFFIX); +} + +Dqn_KeccakBytes64 Dqn_Keccak_512_ToBytes64(void *bytes, Dqn_Keccak_u64 bytes_size) +{ + Dqn_KeccakBytes64 result; + Dqn_Keccak_512(bytes, bytes_size, result.data, sizeof(result)); + return result; +} + +#if defined(DQN_H) +// ----------------------------------------------------------------------------- +// NOTE: Keccak - Helpers for Dqn data structures +// ----------------------------------------------------------------------------- +Dqn_KeccakBytes28 Dqn_Keccak_224_StringToBytes28(Dqn_String string) +{ + Dqn_KeccakBytes28 result; + Dqn_Keccak_224(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes28 Dqn_Keccak_224_U8ArrayToBytes28(Dqn_Array array) +{ + Dqn_KeccakBytes28 result; + Dqn_Keccak_224(array.data, array.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes32 Dqn_Keccak_256_StringToBytes32(Dqn_String string) +{ + Dqn_KeccakBytes32 result; + Dqn_Keccak_256(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes32 Dqn_Keccak_256_U8ArrayToBytes32(Dqn_Array array) +{ + Dqn_KeccakBytes32 result; + Dqn_Keccak_256(array.data, array.size, result.data, sizeof(result)); + return result; +} + + +Dqn_KeccakBytes48 Dqn_Keccak_384_StringToBytes48(Dqn_String string) +{ + Dqn_KeccakBytes48 result; + Dqn_Keccak_384(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes48 Dqn_Keccak_384_U8ArrayToBytes48(Dqn_Array array) +{ + Dqn_KeccakBytes48 result; + Dqn_Keccak_384(array.data, array.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes64 Dqn_Keccak_512_StringToBytes64(Dqn_String string) +{ + Dqn_KeccakBytes64 result; + Dqn_Keccak_512(string.str, string.size, result.data, sizeof(result)); + return result; +} + +Dqn_KeccakBytes64 Dqn_Keccak_512_U8ArrayToBytes64(Dqn_Array array) +{ + Dqn_KeccakBytes64 result; + Dqn_Keccak_512(array.data, array.size, result.data, sizeof(result)); + return result; +} +#endif // DQN_H + +// ----------------------------------------------------------------------------- +// NOTE: Helper functions +// ----------------------------------------------------------------------------- +void Dqn_Keccak_BytesToHex(void const *src, Dqn_Keccak_u64 src_size, char *dest, Dqn_Keccak_u64 dest_size) +{ + (void)src_size; (void)dest_size; + DQN_KECCAK_ASSERT(dest_size >= src_size * 2); + + unsigned char *src_u8 = (unsigned char *)src; + for (Dqn_Keccak_u64 src_index = 0, dest_index = 0; + src_index < src_size; + src_index += 1, dest_index += 2) + { + char byte = src_u8[src_index]; + char hex01 = (byte >> 4) & 0b1111; + char hex02 = (byte >> 0) & 0b1111; + dest[dest_index + 0] = hex01 < 10 ? (hex01 + '0') : (hex01 - 10) + 'a'; + dest[dest_index + 1] = hex02 < 10 ? (hex02 + '0') : (hex02 - 10) + 'a'; + } +} + +#if defined(DQN_NO_MALLOC_FUNCTIONS) +char *Dqn_Keccak_BytesToHexCString(void const *src, Dqn_Keccak_u64 src_size) +{ + int result_size = (src_size * 2); + char *result = DQN_KECCAK_MALLOC(result_size + 1 /*null-terminator*/); + if (result) + { + Dqn_Keccak_BytesToHex(src, src_size, result, result_size); + result[result_size] = 0; + } + + return result; +} +#endif // DQN_NO_MALLOC_FUNCTIONS + +Dqn_KeccakString56 Dqn_Keccak_Bytes28ToHex(Dqn_KeccakBytes28 const *bytes) +{ + Dqn_KeccakString56 result; + Dqn_Keccak_BytesToHex(bytes->data, sizeof(bytes->data), result.str, sizeof(result.str)); + result.str[sizeof(result.str) - 1] = 0; + return result; +} + +Dqn_KeccakString64 Dqn_Keccak_Bytes32ToHex(Dqn_KeccakBytes32 const *bytes) +{ + Dqn_KeccakString64 result; + Dqn_Keccak_BytesToHex(bytes->data, sizeof(bytes->data), result.str, sizeof(result.str)); + result.str[sizeof(result.str) - 1] = 0; + return result; +} + +Dqn_KeccakString96 Dqn_Keccak_Bytes48ToHex(Dqn_KeccakBytes48 const *bytes) +{ + Dqn_KeccakString96 result; + Dqn_Keccak_BytesToHex(bytes->data, sizeof(bytes->data), result.str, sizeof(result.str)); + result.str[sizeof(result.str) - 1] = 0; + return result; +} + +Dqn_KeccakString128 Dqn_Keccak_Bytes64ToHex(Dqn_KeccakBytes64 const *bytes) +{ + Dqn_KeccakString128 result; + Dqn_Keccak_BytesToHex(bytes->data, sizeof(bytes->data), result.str, sizeof(result.str)); + result.str[sizeof(result.str) - 1] = 0; + return result; +} + +int Dqn_Keccak_Bytes28Equals(Dqn_KeccakBytes28 const *a, Dqn_KeccakBytes28 const *b) +{ + int result = DQN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +int Dqn_Keccak_Bytes32Equals(Dqn_KeccakBytes32 const *a, Dqn_KeccakBytes32 const *b) +{ + int result = DQN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +int Dqn_Keccak_Bytes48Equals(Dqn_KeccakBytes48 const *a, Dqn_KeccakBytes48 const *b) +{ + int result = DQN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +int Dqn_Keccak_Bytes64Equals(Dqn_KeccakBytes64 const *a, Dqn_KeccakBytes64 const *b) +{ + int result = DQN_KECCAK_MEMCMP(a->data, b->data, sizeof(*a)) == 0; + return result; +} + +#if defined(DQN_H) +// ----------------------------------------------------------------------------- +// NOTE: Other helper functions for Dqn data structures +// ----------------------------------------------------------------------------- +Dqn_KeccakBytes32 Dqn_Keccak_Hex64StringToBytes(Dqn_String hex) +{ + DQN_KECCAK_ASSERT(hex.size == 64); + Dqn_KeccakBytes32 result; + Dqn_Hex_HexToBytes(hex.str, hex.size, result.data, sizeof(result)); + return result; +} +#endif // DQN_H + +#endif // DQN_KECCAK_IMPLEMENTATION diff --git a/dqn_tests_helpers.cpp b/dqn_tests_helpers.cpp new file mode 100644 index 0000000..fa806ef --- /dev/null +++ b/dqn_tests_helpers.cpp @@ -0,0 +1,101 @@ +#if defined(DQN_KECCAK_H) +// ----------------------------------------------------------------------------- +// Dqn_Keccak Reference Implementation +// ----------------------------------------------------------------------------- +// A very compact Keccak implementation taken from the reference implementation +// repository +// +// https://github.com/XKCP/XKCP/blob/master/Standalone/CompactFIPS202/C/Keccak-more-compact.c +// + +#define FOR(i,n) for(i=0; i>1; } +#define ROL(a,o) ((((u64)a)<>(64-o))) +static u64 load64(const u8 *x) { ui i; u64 u=0; FOR(i,8) { u<<=8; u|=x[7-i]; } return u; } +static void store64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]=u; u>>=8; } } +static void xor64(u8 *x, u64 u) { ui i; FOR(i,8) { x[i]^=u; u>>=8; } } +#define rL(x,y) load64((u8*)s+8*(x+5*y)) +#define wL(x,y,l) store64((u8*)s+8*(x+5*y),l) +#define XL(x,y,l) xor64((u8*)s+8*(x+5*y),l) +void KeccakF1600(void *s) +{ + ui r,x,y,i,j,Y; u8 R=0x01; u64 C[5],D; + for(i=0; i<24; i++) { + /*θ*/ FOR(x,5) C[x]=rL(x,0)^rL(x,1)^rL(x,2)^rL(x,3)^rL(x,4); FOR(x,5) { D=C[(x+4)%5]^ROL(C[(x+1)%5],1); FOR(y,5) XL(x,y,D); } + /*ρπ*/ x=1; y=r=0; D=rL(x,y); FOR(j,24) { r+=j+1; Y=(2*x+3*y)%5; x=y; y=Y; C[0]=rL(x,y); wL(x,y,ROL(D,r%64)); D=C[0]; } + /*χ*/ FOR(y,5) { FOR(x,5) C[x]=rL(x,y); FOR(x,5) wL(x,y,C[x]^((~C[(x+1)%5])&C[(x+2)%5])); } + /*ι*/ FOR(j,7) if (LFSR86540(&R)) XL(0,0,(u64)1<<((1<0) { b=(inLen0) { b=(outLen0) KeccakF1600(s); } +} + + +// ----------------------------------------------------------------------------- +// PCG32 Random Number Generator +// ----------------------------------------------------------------------------- +// NOTE: https://github.com/imneme/pcg-c-basic + +struct pcg_state_setseq_64 +{ // Internals are *Private*. + Dqn_u64 state; // RNG state. All values are possible. + Dqn_u64 inc; // Controls which RNG sequence (stream) is + // selected. Must *always* be odd. +}; +typedef struct pcg_state_setseq_64 pcg32_random_t; + +// pcg32_random_r(rng) +// Generate a uniformly distributed 32-bit random number + +Dqn_u32 pcg32_random_r(pcg32_random_t* rng) +{ + Dqn_u64 oldstate = rng->state; + rng->state = oldstate * 6364136223846793005ULL + rng->inc; + Dqn_u32 xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + Dqn_u32 rot = oldstate >> 59u; + return (xorshifted >> rot) | (xorshifted << ((-rot) & 31)); +} + +// pcg32_srandom_r(rng, initstate, initseq): +// Seed the rng. Specified in two parts, state initializer and a +// sequence selection constant (a.k.a. stream id) + +void pcg32_srandom_r(pcg32_random_t* rng, Dqn_u64 initstate, Dqn_u64 initseq) +{ + rng->state = 0U; + rng->inc = (initseq << 1u) | 1u; + pcg32_random_r(rng); + rng->state += initstate; + pcg32_random_r(rng); +} + +// pcg32_boundedrand_r(rng, bound): +// Generate a uniformly distributed number, r, where 0 <= r < bound + +Dqn_u32 pcg32_boundedrand_r(pcg32_random_t* rng, Dqn_u32 bound) +{ + Dqn_u32 threshold = -bound % bound; + for (;;) { + Dqn_u32 r = pcg32_random_r(rng); + if (r >= threshold) + return r % bound; + } +} +#endif // DQN_KECCAK_H