From 8e8ac0450579fd74d7807fc1dba69e341de19ec0 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Fri, 29 Jul 2022 12:32:52 -0400 Subject: [PATCH] Start documenting the headers. --- src/include/Array.h | 175 ++++++++++++++++++++++++++++-- src/include/Base64.h | 120 +++++++++++++++++++-- src/include/CanonicalJson.h | 55 +++++++++- src/include/Config.h | 166 +++++++++++++++++++++++++++-- src/include/HashMap.h | 125 +++++++++++++++++----- src/include/Json.h | 207 +++++++++++++++++++++++++++++++++--- src/include/Log.h | 34 +++++- 7 files changed, 817 insertions(+), 65 deletions(-) diff --git a/src/include/Array.h b/src/include/Array.h index 656bd74..6ff2062 100644 --- a/src/include/Array.h +++ b/src/include/Array.h @@ -21,6 +21,25 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/* + * Array.h: A simple array data structure that is automatically + * resized when new values are added. This implementation does not + * actually store the values of the items it; it only stores pointers + * to the data. As such, you will still have to manually maintain all + * your data. The advantage of this is that this array implementation + * doesn't have to copy items, and thus doesn't have to know how big + * they are. + * + * This array is optimized for storage space and appending. Deletions + * are expensive in that all the items of the list are moved down + * to fill the hole where the deletion occurred. Insertions are + * also expensive in that all the elements are shifted to make room + * for the new element. + * + * Due to these, this array implementation is really intended to be + * used primarily for linear writing and linear or random reading. + */ #ifndef TELODENDRIA_ARRAY_H #define TELODENDRIA_ARRAY_H @@ -28,31 +47,171 @@ typedef struct Array Array; +/* + * Create a new, empty array on the heap. + * + * Params: none + * + * Return: A pointer to an Array, or NULL if there was an error + * allocating memory for the Array. + */ extern Array * ArrayCreate(void); +/* + * Get the size of the provided array. Note that this is the number + * of elements in the array, not how much memory has been allocated + * for it. + * + * This is an extremely cheap operation, because the size does not + * need to be computed; rather it is just pulled right out of the + * Array and returned to the caller. + * + * Params: + * + * (Array *) The array to check the size of. + * + * Return: The number of elements in the provided array, or 0 if the + * provided array is NULL. + */ extern size_t - ArraySize(Array * array); + ArraySize(Array *); +/* + * Get an element out of the provided array at the provided index. + * + * Params: + * + * (Array *) The array to get an element from. + * (size_t) The index of the array where the desired element is + * located. + * + * Return: A pointer to the data located at the given index, or NULL + * if no array was provided, or the index is greater than or equal + * to the size of the array. + */ extern void * - ArrayGet(Array * array, size_t index); + ArrayGet(Array *, size_t); +/* + * Insert an element into the array at the given index. + * + * Params: + * + * (Array *) The array to get the element from. + * (void *) The value to insert into the array. + * (size_t) The index at which the given value. + * + * Return: A boolean value that indicates whether or not the insert + * was successful. A return value of 0 indicates that the insert was + * NOT successful, and a return value of anything else indicates that + * the insert was successful. + */ extern int - ArrayInsert(Array *, void *value, size_t index); + ArrayInsert(Array *, void *, size_t); +/* + * Append an element to the end of the array. This function actually + * uses ArrayInsert() under the hood, but it makes appending to an + * array more convenient because you don't necessarily have to keep + * track of the array's size. + * + * Params: + * + * (Array *) The array to append to. + * (void *) The value to append to the array. + * + * Return: The result of appending the element, which is the same as + * a call to ArrayInsert(). + */ extern int - ArrayAdd(Array * array, void *value); + ArrayAdd(Array *, void *); +/* + * Delete an element from an array by shifting all the elements that + * come after it down one index. + * + * Params: + * + * (Array *) The array to delete a value from. + * (size_t) The desired index to delete. All elements above this + * index are then shifted down to fill the gap. + * + * Return: A pointer to the deleted element, so that it can be freed + * or otherwise dealt with, or NULL if the array is NULL or the index + * is out of bounds. + */ extern void * - ArrayDelete(Array * array, size_t index); + ArrayDelete(Array *, size_t); +/* + * Sort the array using a simple quick-sort algorithm. This function + * works by taking a caller-specified compare function, so that no + * assumptions about the data stored in the array need to be made by + * this code. + * + * Params: + * + * (Array *) The array to sort. Note that the sort will be done + * in-place. + * (int (*)(void *, void *)) A function that takes in two void + * pointers and returns an integer. This function is + * responsible for comparing the passed items, and + * returning a code that indicates how they should be + * ordered. A return value of 0 indicates that the two + * items are identical. A return value greater than 0 + * indicates that the first item is "bigger" than the + * second item and should thus appear after it in the + * array, and a return value less than zero indicates + * the opposite: that the second element should appear + * after the first in the array. + * + */ extern void - ArraySort(Array *, int (*compare) (void *, void *)); + ArraySort(Array *, int (*) (void *, void *)); +/* + * Free all the memory associated with the given array. Note that this + * does not free any of the values themselves; you should explicitly + * iterate over the array and free all the values stored inside it + * before calling this function, otherwise you may lose all the + * pointers the array contains, and thus have a memory leak. + * + * Params: + * + * (Array *) The array to free. + * + */ extern void - ArrayFree(Array * array); + ArrayFree(Array *); +/* + * "Trim" the array by reallocating it with only the memory it needs + * to hold the items it currently has. This function might be beneficial + * to call on long-lived arrays that will be read-only, because it + * frees any memory on the end of the array that isn't being used. The + * array resizing algorithm will most likely allocate too much memory + * for most arrays as elements are added, because there's no way to + * know exactly how many elements will be stored. + * + * For example, a library that generates an Array to return to the + * user may wish to call this function on it right before returning to + * the caller if it is not expected that the caller will be modifying + * the array and may hang on to it for a long time. + * + * Params: + * + * (Array *) The array to trim extra memory (if any) off the end. + * Note that the array will still be fully-functional; if + * you add more elements, then more memory will be + * allocated like normal. + * + * Return: Whether or not the trim was successful. The trim may fail if + * realloc() fails, or NULL was passed for the array. If realloc() + * fails, this function is careful not to clobber the array. Upon + * failure of this function, the array is guaranteed to be unaltered. + */ extern int - ArrayTrim(Array * array); + ArrayTrim(Array *); #endif diff --git a/src/include/Base64.h b/src/include/Base64.h index ce1b1c9..bbb098d 100644 --- a/src/include/Base64.h +++ b/src/include/Base64.h @@ -21,27 +21,135 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/* + * Base64.h: An efficient base64 encoder and decoder that supports + * both regular base64, and the Matrix spec's "unpadded base64." + */ #ifndef TELODENDRIA_BASE64_H #define TELODENDRIA_BASE64_H #include +/* + * Compute the encoded size, including padding, of an input with the + * provided size. + * + * Params: + * + * (size_t) The size of the input data to be encoded. + * + * Return: The size of the string needed to hold the data in its + * encoded form. Note that base64 is not compression; base64 strings + * are actually 25% larger than the unencoded input. + */ extern size_t - Base64EncodedSize(size_t inputSize); + Base64EncodedSize(size_t); +/* + * Compute the decoded size of the provide base64 input and length. + * + * Note that both the size and the actual base64 string itself is + * needed for this computation, unlike Base64EncodedSize(). This is + * because base64 strings are padded, and that padding is used in the + * calculations. + * + * Params: + * + * (const char *) A padded base64 string. If you are dealing with + * potentially user-provided base64, you should call + * Base64Pad() on it to normalize it before computing + * the decoded size. + * (size_t) The length of the base64 string. Instead of scanning the + * string for a null terminator, and then working backwards, + * the length of the string must be passed here. + * + * Return: The number of bytes that can be decoded from this base64 + * string. Note that this will be smaller than the length of the base64 + * string because base64 is larger than the unencoded form. + */ extern size_t - Base64DecodedSize(const char *base64, size_t len); + Base64DecodedSize(const char *, size_t); +/* + * Copy the given input string to a new string, base64 encoding it in + * the process. This function will produce standard padded base64. If + * you want unpadded base64, call Base64Unpad() on the return value + * of this function. + * + * Params: + * + * (const char *) The raw, unencoded input to be encoded as base64. + * (size_t) The length of the unencoded input string. + * + * Return: A new string, allocated on the heap, that holds the base64 + * representation of the input. This string must be free()-ed when it + * is no longer needed. If the allocation of the proper size fails, + * or the input is inaccessible, then this function will return NULL. + */ extern char * - Base64Encode(const char *input, size_t len); + Base64Encode(const char *, size_t); +/* + * Decode a standard padded base64 string. This function expects that + * the input will be padded, so if you are recieving untrusted input, + * you should run Base64Pad() on it before attempting to decode it. + * + * Params: + * + * (const char *) The base64 string to decode. + * (size_t) The length of the base64 string to decode. + * + * Return: A new string, allocated on the heap, that contains the + * decoded string, or NULL if a decoding error occurred. + */ extern char * - Base64Decode(const char *input, size_t len); + Base64Decode(const char *, size_t); +/* + * Remove the padding from a base64 string. This is to implement the + * Matrix spec's "unpadded base64" functionality. When base64 strings + * are sent to other servers and clients, their padding must be + * stripped. + * + * Params: + * + * (char *) The base64 string to remove padding from. Note that + * this string is modified in place. + * (size_t) The length of the provided base64 string. + * + */ extern void - Base64Unpad(char *base64, size_t length); + Base64Unpad(char *, size_t); +/* + * Pad a base64 string in place. This is to implement the Matrix spec's + * "unpadded base64." As we will most likely be getting unpadded base64 + * from clients and other servers, we should pad it before attempting + * to decode it. + * + * I technically could have just had the decoder accept unpadded as + * well as padded strings, but Matrix is the only thing I know of that + * actually makes "unpadded" base64 a thing, so I thought it best to + * make it clear in this library that unpadded base64 is an extension, + * not the norm. + * + * Params: + * + * (char **) A pointer to a base64 string pointer. The reason we + * take a pointer pointer is because the string may need + * to be reallocated, as characters may be added to the + * end of it. If the string is reallocated, then the + * passed pointer must be updated. If the string is not + * reallocated, the original pointer is not touched. + * (size_t) The length of the given base64 string. + * + * Return: Whether or not the pad operation was successful. This + * function will fail if a larger string cannot be allocated when it + * is needed. Note that not all cases require the string to be + * reallocated. + */ extern int - Base64Pad(char **base64Ptr, size_t length); + Base64Pad(char **, size_t); #endif diff --git a/src/include/CanonicalJson.h b/src/include/CanonicalJson.h index c7f18e0..98af8e6 100644 --- a/src/include/CanonicalJson.h +++ b/src/include/CanonicalJson.h @@ -21,13 +21,66 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/* + * CanonicalJson.h: An expansion of the JSON encoding functionality + * that is specifically designed to produce the Matrix spec's + * "canonical" JSON. + * + * Canonical JSON is defined as JSON that: + * + * - Does not have any unecessary whitespace. + * - Has all object keys lexicographically sorted. + * - Does not contain any float values. + * + * The regular JSON encoder has no such rules, because normally they + * are not needed. However, Canonical JSON is needed to be able to + * sign JSON objects in a consistent way. + */ #ifndef TELODENDRIA_CANONICALJSON_H #define TELODENDRIA_CANONICALJSON_H #include #include +/* + * Encode a JSON object following the rules of canonical JSON. See + * JsonEncode() for more details on how JSON encoding operates. + * + * This function exists as an alternative to JsonEncode(), but should + * not be preferred to JsonEncode() in normal circumstances. It is + * a lot more costly, as it must lexicographically sort all keys and + * strip out float values. If at all possible, use JsonEncode(), + * because it is much cheaper in terms of memory and CPU time. + * + * Params: + * + * (HashMap *) The JSON object to encode. Note that all values must + * be JsonValues. + * (FILE *) The output stream to write the JSON object to. + * + * Return: Whether or not the JSON encoding was successful. This + * function may fail if NULL was given for any parameter. + */ extern int - CanonicalJsonEncode(HashMap * object, FILE * out); + CanonicalJsonEncode(HashMap *, FILE *); + +/* + * Encode the JSON object to a string. The regular JSON encoding + * library doesn't have a way to send JSON to strings, because there's + * absolutely no reason to handle JSON strings. However, the sole + * reason canonical JSON exists is so that JSON objects can be signed. + * Thus, you need a string to pass to the signing function. + * + * Params: + * + * (HashMap *) The JSON object to encode. Note that all values must + * be JsonValues. + * + * Return: A string containing the canonical JSON representation of + * the given object, or NULL if the encoding failed. + */ +extern char * + CanonicalJsonEncodeToString(HashMap *); #endif diff --git a/src/include/Config.h b/src/include/Config.h index b329e1b..9549644 100644 --- a/src/include/Config.h +++ b/src/include/Config.h @@ -21,6 +21,22 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/* + * Config.h: A heavily-modified version of Conifer2, a configuration + * file format specification and C parsing library written by Jordan + * Bancino. This library differs from Conifer2 in that the function + * naming convention has been updated to be consistent with Telodendria, + * and the underlying data structures have been overhauled to use the + * data structure libraries provided by Telodendria. + * + * Conifer2 was originally a learning project. It was very thoroughly + * debugged, however, and the configuration syntax was elegant, + * certainly more elegant than using JSON for a configuration file, + * so it was chosen to be the format for Telodendria's configuration + * file. The original Conifer2 project is now dead; Conifer2 lives on + * only as Telodendria's Config parsing library. + */ #ifndef TELODENDRIA_CONFIG_H #define TELODENDRIA_CONFIG_H @@ -29,32 +45,164 @@ #include #include +/* + * A configuration directive is a single key that may have at least one + * value, and any number of children. + */ typedef struct ConfigDirective ConfigDirective; +/* + * The parser returns a parse result object. This stores whether or + * not the parse was successful, and then also additional information + * about the parse, such as the line number on which parsing failed, + * or the collection of directives if the parsing succeeded. + * + * There are a number of ConfigParseResult methods that can be used + * to query the result of parsing. + */ typedef struct ConfigParseResult ConfigParseResult; +/* + * Parse a configuration file, and generate the structures needed to + * make it easy to read. + * + * Params: + * + * (FILE *) The input stream to read from. + * + * Return: A ConfigParseResult, which can be used to check whether or + * not the parsing was successful. If the parsing was sucessful, then + * this object contains the root directive, which can be used to + * retrieve configuration values out of. If the parsing failed, then + * this object contains the line number at which the parsing was + * aborted. + */ extern ConfigParseResult * - ConfigParse(FILE * stream); + ConfigParse(FILE *); +/* + * Get whether or not a parse result indicates that parsing was + * successful or not. This function should be used to determine what + * to do next. If the parsing failed, your program should terminate + * with an error, otherwise, you can proceed to parse the configuration + * file. + * + * Params: + * + * (ConfigParseResult *) The output of ConfigParse() to check. + * + * Return: 0 if the configuration file is malformed, or otherwise + * could not be parsed. Any non-zero return value indicates that the + * configuration file was successfully parsed. + */ extern unsigned int - ConfigParseResultOk(ConfigParseResult * result); + ConfigParseResultOk(ConfigParseResult *); +/* + * If, and only if, the configuration file parsing failed, then this + * function can be used to get the line number it failed at. Typically, + * this will be reported to the user and then the program will be + * terminated. + * + * Params: + * + * (ConfigParseResult *) The output of ConfigParse() to get the + * line number from. + * + * Return: The line number on which the configuration file parser + * choked, or 0 if the parsing was actually successful. + */ extern size_t - ConfigParseResultLineNumber(ConfigParseResult * result); + ConfigParseResultLineNumber(ConfigParseResult *); +/* + * Convert a ConfigParseResult into a HashMap containing the entire + * configuration file, if, and only if, the parsing was successful. + * + * Params: + * + * (ConfigParseResult *) The output of ConfigParse() to get the + * actual configuration data from. + * + * Return: A HashMap containing all the configuration data, or NULL + * if the parsing was not successful. This HashMap is a map of string + * keys to ConfigDirective objects. Use the standard HashMap methods + * to get ConfigDirectives, and then use the ConfigDirective functions + * to get information out of them. + */ extern HashMap * - ConfigParseResultGet(ConfigParseResult * result); + ConfigParseResultGet(ConfigParseResult *); +/* + * Free the memory being used by the given ConfigParseResult. Note that + * it is safe to free the ConfigParseResult immediately after you have + * retrieved either the line number or the configuration data from it. + * Freeing the parse result does not free the configuration data. + * + * Params: + * + * (ConfigParseResult *) The output of ConfigParse() to free. This + * object will be invalidated, but pointers to + * the actual configuration data will still be + * valid. + */ extern void - ConfigParseResultFree(ConfigParseResult * result); + ConfigParseResultFree(ConfigParseResult *); +/* + * Get an array of values associated with the given configuration + * directive. Directives can have any number of values, which are + * made accessible via the Array API. + * + * Params: + * + * (ConfigDirective *) The configuration directive to get the values + * for. + * + * Return: An array that contains at least 1 value. Configuration files + * cannot have value-less directives. If the passed directive is NULL, + * or there is an error allocating memory for an array, then NULL is + * returned. + */ extern Array * - ConfigValuesGet(ConfigDirective * directive); + ConfigValuesGet(ConfigDirective *); +/* + * Get a map of children associated with the given configuration + * directive. Configuration files can recurse with no practical limit, + * so directives can have any number of child directives. + * + * Params: + * + * (ConfigDirective *) The configuratio ndirective to get the + * children of. + * + * Return: A HashMap containing child directives, or NULL if the passed + * directive is NULL or has no children. + */ extern HashMap * - ConfigChildrenGet(ConfigDirective * directive); + ConfigChildrenGet(ConfigDirective *); +/* + * Free all the memory associated with the given configuration hash + * map. Note: this will free *everything*. All Arrays, HashMaps, + * ConfigDirectives, and even strings will be invalidated. As such, + * this should be done after you either copy the values you want, or + * are done using them. It is highly recommended to use this function + * near the end of your program's execution during cleanup, otherwise + * copy any values you need into your own buffers. + * + * Note that this should only be run on the root configuration object, + * not any children. Running on children will produce undefined + * behavior. This function is recursive; it will get all the children + * under it. + * + * Params: + * + * (HashMap *) The configuration data to free. + * + */ extern void - ConfigFree(HashMap * conf); + ConfigFree(HashMap *); -#endif /* TELODENDRIA_CONFIG_H */ +#endif diff --git a/src/include/HashMap.h b/src/include/HashMap.h index a0cd5c1..305bee7 100644 --- a/src/include/HashMap.h +++ b/src/include/HashMap.h @@ -21,6 +21,7 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + /* * HashMap.h: The public interface for Telodendria's hash map * implementation. This hash map is designed to be simple, well @@ -31,8 +32,6 @@ * Fundamentally, this is an entirely generic map implementation. It * can be used for many general purposes, but it is designed to only * implement the features that Telodendria needs to function well. - * - * Copyright (C) 2022 Jordan Bancino <@jordan:bancino.net> */ #ifndef TELODENDRIA_HASHMAP_H #define TELODENDRIA_HASHMAP_H @@ -48,61 +47,137 @@ typedef struct HashMap HashMap; /* - * HashMapCreate: Create a new HashMap object. + * Create a new HashMap object. * - * Returns: A HashMap object that is ready to be used by the rest of + * Return: A HashMap object that is ready to be used by the rest of * the HashMap functions, or NULL if memory could not be allocated on * the heap. */ extern HashMap * HashMapCreate(void); +/* + * Set the maximum load of the hash map before it is expanded. When the + * hash map reaches the given capacity, it is grown. You don't want + * to only grow hash maps when their full, because that makes them + * perform very poorly. + * + * The default max load on new HashMap objects is 0.75, which should be + * good enough for most purposes, but if you need finer tuning, feel + * free to play with the max load with this function. The changes take + * effect on the next insert. + * + * Params: + * + * (HashMap *) The HashMap to modify the maximum load for. + * (float) The new maximum load. This should be a value + * between 0 and 1, which specifies the percentange + * of "fullness" at which the HashMap will be + * expanded. If the new max load is out of bounds, + * this function does nothing. + */ extern void - HashMapMaxLoadSet(HashMap * map, float load); + HashMapMaxLoadSet(HashMap *, float); /* - * HashMapSet: Set the given key in the HashMap to the given value. Note - * that the value is not copied into the HashMap's own memory space; - * only the pointer is stored. It is the caller's job to ensure that the - * value's memory remains valid for the life of the HashMap. + * Set the given key in the HashMap to the given value. Note that the + * key nor the value is copied into the HashMap's own memory space; + * only pointers is stored. It is the caller's job to ensure that the + * key and value memory remains valid for the life of the HashMap, and + * are freed when they're no longer needed. * - * Returns: The previous value at the given key, or NULL if the key did + * Params: + * + * (HashMap *) The hash map to set a key in. + * (char *) The key to set. + * (void *) The value to set at the given key. + * + * Return: The previous value at the given key, or NULL if the key did * not previously exist or any of the parameters provided are NULL. All * keys must have values; you can't set a key to NULL. To delete a key, - * use HashMapDelete. + * use HashMapDelete(). */ extern void * - HashMapSet(HashMap * map, char *key, void *value); + HashMapSet(HashMap *, char *, void *); /* - * HashMapGet: Get the value for the given key. + * Get the value for the given key. * - * Returns: The value at the given key, or NULL if the key does not + * Params: + * + * (HashMap *) The hash map to check. + * (char *) The key to get the value for. + * + * Return: The value at the given key, or NULL if the key does not * exist, no map was provided, or no key was provided. */ extern void * - HashMapGet(HashMap * map, const char *key); + HashMapGet(HashMap *, const char *); /* - * HashMapDelete: Delete the value for the given key. + * Delete the value for the given key. * - * Returns: The value at the given key, or NULL if the key does not + * Params: + * + * (HashMap *) The map to delete the given key from. + * (const char *) The key to delete. + * + * Return: The value at the given key, or NULL if the key does not * exist or the map or key was not provided. */ extern void * - HashMapDelete(HashMap * map, const char *key); - -extern int - HashMapIterate(HashMap * map, char **key, void **value); + HashMapDelete(HashMap *, const char *); /* - * HashMapFree: Free the hash map, returning its memory to the operating - * system. Note that this function does not free the values stored in + * Iterate over all the keys and values of a hash map. This function + * works similarly to the POSIX getopt(), where calls are repeatedly + * made in a "while" loop until there are no more items to go over. + * + * The difference is that this function does not rely on globals. This + * function takes pointer pointers, and stores necessary state inside + * the hash map structure itself. + * + * This function can be tricky to use in some scenarios, as it + * continues where it left off on each call, until there are no more + * elements. Then it returns 0 and resets the iterator, so that it can + * start over for the next iteration. This means that if you are not + * iterating over the entire map at one, and break the loop, the next + * time you try to iterate the HashMap, you'll start somewhere in the + * middle. Thus, it's recommended to iterate over the entire map. For + * scenarios in which the entire map needs to be iterated, such as + * when freeing all the keys and values, this function does well. + * + * Params: + * + * (HashMap *) The hash map to iterate over. + * (char **) A character pointer that will be set to the current + * key. + * (void **) A void pointer that will be set to the current value. + * + * Return: 1 if there are still elements left in this iteration of the + * hash map, or 0 if no valid hash map was provided, or there are no + * more elements in it for this iteration. Note that after this + * function returns 0 on a hash map, subsequent iterations will start + * from the beginning. + */ +extern int + HashMapIterate(HashMap *, char **, void **); + +/* + * Free the hash map, returning its memory to the operating system. + * Note that this function does not free the keys or values stored in * the map since this hash map implementation has no way of knowing - * what actually is stored in it. You should use HashMapIterate to + * what actually is stored in it. You should use HashMapIterate() to * free the values using your own algorithm. + * + * Params: + * + * (HashMap *) The hash map to free. The pointer can be safely + * discarded when this function returns. In fact, + * accessing it after this function returns is undefined + * behavior. */ extern void HashMapFree(HashMap *); -#endif /* TELODENDRIA_HASHMAP_H */ +#endif diff --git a/src/include/Json.h b/src/include/Json.h index 78c8021..4bc839d 100644 --- a/src/include/Json.h +++ b/src/include/Json.h @@ -21,6 +21,30 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/* + * Json.h: A fully-featured JSON API for C using Arrays and HashMaps. + * This API builds on the foundations of Arrays and HashMaps, because + * that's all a JSON object really is. It provides a JsonValue, which + * is used to encapsulate arbitrary values while being able to identify + * them in the future, so that JSON can be effectively handled. + * + * This implementation is just to get the job done in parsing and + * generating JSON. It is extremely strict; it will fail on syntax + * errors. This is fine for Matrix, because we can just return + * M_BAD_JSON anything in here fails. + * + * One thing to note about this implementation is that it focuses + * primarily on serialization and deserialization to and from streams. + * What this means is that it does not provide facilities for handling + * JSON strings; it only writes JSON to output streams, and reading + * them from input streams. Of course, you could use the POSIX + * fmemopen() and open_memstream() functions if you really want to deal + * with JSON strings, but JSON is intended to be an exchange format. + * Data should be converted to JSON when it is leaving, and converted + * from JSON when it is coming in. Ideally, most of the program would + * have no idea what JSON actually is. + */ #ifndef TELODENDRIA_JSON_H #define TELODENDRIA_JSON_H @@ -30,27 +54,87 @@ #include #include +/* + * All the possible JSON types. This enumeration is used to identify + * the type of the value stored in a JsonValue. + */ typedef enum JsonType { - JSON_OBJECT, - JSON_ARRAY, - JSON_STRING, - JSON_INTEGER, - JSON_FLOAT, - JSON_BOOLEAN, - JSON_NULL + JSON_NULL, /* Maps to nothing. */ + JSON_OBJECT, /* Maps to a HashMap of JsonValues */ + JSON_ARRAY, /* Maps to an Array of JsonValues */ + JSON_STRING, /* Maps to a C string */ + JSON_INTEGER, /* Maps to a C long */ + JSON_FLOAT, /* Maps to a C double */ + JSON_BOOLEAN /* Maps to a C 1 or 0 */ } JsonType; +/* + * A JsonValue encapsulates all the possible values that can be stored + * in a JSON object as a single type, so as to provide a consistent + * API for accessing and setting them. It is an opaque structure that + * can be managed entirely by the functions defined in this API. + * + * Note that in the case of objects, arrays, and strings, this structure + * only stores pointers to allocated data, it doesn't store the data + * itself. JsonValues only store integers, floats, booleans, and NULL + * in their memory. Anything else must be freed separately. + */ typedef struct JsonValue JsonValue; +/* + * Get the type of a JsonValue. + * + * Params: + * + * (JsonValue *) The value to get the type of. + * + * Return: A JsonType that tells what the provided value is, or + * JSON_NULL if the passed value is NULL. Note that even a fully + * valid JsonValue may still be of type JSON_NULL, so this function + * should not be used to check whether or not the JSON value is valid. + */ extern JsonType - JsonValueType(JsonValue * value); + JsonValueType(JsonValue *); +/* + * Wrap a HashMap into a JsonValue that represents a JSON object. Note + * that the HashMap should contain only JsonValues. Any other contents + * are not supported and will lead to undefined behavior. + * + * Params: + * + * (HashMap *) The hash map of JsonValues to wrap in a JsonValue. + * + * Return: A JsonValue that holds a pointer to the given object, or + * NULL if there was an error allocating memory. + */ extern JsonValue * - JsonValueObject(HashMap * object); + JsonValueObject(HashMap *); +/* + * Get a HashMap from a JsonValue that represents a JSON object. + * + * Params: + * + * (JsonValue *) The value to extract the object from. + * + * Return: A HashMap of JsonValues, or NULL if no value was provided, + * or the value is not of type JSON_OBJECT. + */ extern HashMap * - JsonValueAsObject(JsonValue * value); + JsonValueAsObject(JsonValue *); + +/* + * The following methods very closely resemble the ones above, and + * behave pretty much the exact same. To save on time and effort, + * I'm choosing not to explicitly document all of these. If something + * is unclear about how these functions work, consult the source code, + * and then feel free to write the documentation yourself. + * + * Otherwise, reach out to the official Matrix room, and someone will + * be able to help you. + */ extern JsonValue * JsonValueArray(Array * array); @@ -70,25 +154,118 @@ extern JsonValue * extern JsonValue * JsonValueBoolean(int boolean); +/* + * Create a JsonValue that represents a JSON null. Because Arrays and + * HashMaps should not contain NULL values, I thought it appropriate + * to provide support for JSON nulls. Yes, a small amount of memory is + * allocated just to point to a NULL, but this keeps all the APIs + * clean. + * + * Return: A JsonValue that represents a JSON null, or NULL if memory + * could not be allocated. + */ extern JsonValue * JsonValueNull(void); +/* + * Free the memory being used by a JSON value. Note that this will + * recursively free all Arrays, HashMaps, and other JsonValues that + * are reachable from this one. It will invoke free() on strings as + * well, so make sure passed string pointers point to strings on the + * heap, not the stack. This will be the case for all strings returned + * by JsonDecode(), which is why this assumption is made. However, if + * you are manually creating JsonObjects and stitching them together, + * you'll have to manually free them as well. Calling this on a + * JsonValue that contains a pointer to a stack string is undefined. + * + * Params: + * + * (JsonValue *) The JsonValue to recursively free. + */ extern void - JsonValueFree(JsonValue * value); + JsonValueFree(JsonValue *); +/* + * Recursively free a HashMap of JsonValues. This iterates over all + * the JsonValues in a HashMap and frees them using JsonValueFree(), + * which will in turn call JsonFree() on values of type JSON_OBJECT. + * + * Params: + * + * (HashMap *) The hash map of JsonValues to recursively free. + */ extern void - JsonFree(HashMap * object); + JsonFree(HashMap *); +/* + * Encode the given string in such a way that it can be embedded in a + * JSON stream. This entails: + * + * - Escaping quotes, backslashes, and other special characters using + * their backslash escape + * - Encoding bytes that are not UTF-8 using \u escapes. + * - Wrapping the entire string in double quotes. + * + * This function is provided via the public API so it is accessible to + * custom JSON encoders, such as the CanonicalJson API. This will + * typically be used for encoding JSON keys; for values, just use + * JsonEncodeValue(). + * + * Params: + * + * (const char *) The C string to serialize as a JSON string. + * (FILE *) The output stream to write the encoded string to. + */ extern void - JsonEncodeString(const char *str, FILE * out); + JsonEncodeString(const char *, FILE *); +/* + * Serialize a JsonValue as it would appear in JSON output. This is + * a recursive function that will also encode all child values + * reachable from the given JsonValue. + * + * This is exposed via the public API so that custom JSON encoders + * such as CanonicalJson can take advantage of it. Normal users that + * are writing custom encoders should just use JsonEncode() to encode + * an entire object. + * + * Params: + * + * (JsonValue *) The value to encode. + * (FILE *) The output stream to write the given value to. + */ extern void JsonEncodeValue(JsonValue * value, FILE * out); +/* + * Encode a HashMap of JsonValues into a fully-valid, minimized JSON + * object. This function is recursive; it will serialize everything + * accessible from the passed object into JSON. + * + * Params: + * + * (HashMap *) The HashMap of JsonValues to encode and write to the + * output stream. + * (FILE *) The output stream to write the given HashMap to. + * + * Return: Whether or not the operation was successful. This function + * will fail if either the passed HashMap or file stream are NULL. In + * all other cases, this function succeeds. + */ extern int - JsonEncode(HashMap * object, FILE * out); + JsonEncode(HashMap *, FILE *); +/* + * Decode the given input stream into a HashMap of JsonValues. + * + * Params: + * + * (FILE *) The input stream to parse JSON from. + * + * Return: A HashMap of JsonValues, or NULL if there was an error + * parsing the JSON. + */ extern HashMap * - JsonDecode(FILE * in); + JsonDecode(FILE *); #endif diff --git a/src/include/Log.h b/src/include/Log.h index dcd652b..43cf5cb 100644 --- a/src/include/Log.h +++ b/src/include/Log.h @@ -21,12 +21,35 @@ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ + +/* + * Log.h: A heavily-modified version of Shlog, a simple C logging + * facility that allows for colorful output, timestamps, and custom + * log levels. This library differs from Shlog in that the naming + * conventions have been updated to be consistent with Telodendria. + * + * Shlog was originally a learning project. It worked well, however, + * and produced elegant logging output, so it was chosen to be the + * main logging mechanism of Telodendria. The original Shlog project + * is now dead; Shlog lives on now only as Telodendria's logging + * mechanism. + * + * In the name of simplicity and portability, I opted to use an + * in-house logging system instead of syslog(), or other system logging + * mechanisms. However, this API could easily be patched to allow + * logging via other mechanisms that support the same features. + */ #ifndef TELODENDRIA_LOG_H #define TELODENDRIA_LOG_H #include #include +/* + * There are five log "levels," each one showing more information than + * the previous one. A level of LOG_ERROR shows only errors, while a + * level of LOG_DEBUG shows all output possible. + */ typedef enum LogLevel { LOG_ERROR, @@ -36,11 +59,20 @@ typedef enum LogLevel LOG_DEBUG } LogLevel; +/* + * The possible flags that can be applied to alter the behavior of + * the logger + */ typedef enum LogFlag { - LOG_FLAG_COLOR = (1 << 0) + LOG_FLAG_COLOR = (1 << 0) /* Enable color output on TTYs */ } LogFlag; +/* + * The log configurations structure in which all settings exist. + * It's not super elegant to pass around a pointer to the logging + * configuration + */ typedef struct LogConfig LogConfig; extern LogConfig *