From 24a03ba1268b8f1d7b31a759e59b647278d2a1b6 Mon Sep 17 00:00:00 2001 From: Jordan Bancino Date: Thu, 27 Apr 2023 18:00:26 +0000 Subject: [PATCH] Added some more header documentation. --- TODO.txt | 4 +- src/include/Base64.h | 51 ++++++ src/include/CanonicalJson.h | 64 +++++++- src/include/Cron.h | 90 +++++++++++ src/include/Db.h | 146 ++++++++++++++--- src/include/HashMap.h | 144 ++++++++++++++--- src/include/Http.h | 106 +++++++++++-- src/include/HttpServer.h | 204 ++++++++++++++++++++---- src/include/Json.h | 304 +++++++++++++++++++++++++++++------- src/include/Log.h | 80 ++++++++-- 10 files changed, 1022 insertions(+), 171 deletions(-) diff --git a/TODO.txt b/TODO.txt index 0cfc767..84f5f44 100644 --- a/TODO.txt +++ b/TODO.txt @@ -21,7 +21,7 @@ Milestone: v0.3.0 [x] Refactor TelodendriaConfig to just Config [ ] Documentation - [ ] Array + [x] Array [ ] Io [ ] Stream [ ] Tls @@ -34,7 +34,7 @@ Milestone: v0.3.0 [ ] send-patch [ ] Log [ ] TelodendriaConfig -> Config - [ ] HashMap + [x] HashMap [ ] HttpRouter [ ] Str [ ] HeaderParser diff --git a/src/include/Base64.h b/src/include/Base64.h index 6e1f04b..93509ea 100644 --- a/src/include/Base64.h +++ b/src/include/Base64.h @@ -25,23 +25,74 @@ #ifndef TELODENDRIA_BASE64_H #define TELODENDRIA_BASE64_H +/*** + * @Nm Base64 + * @Nd A simple base64 encoder/decoder with unpadded base64 support. + * @Dd September 30 2022 + * @Xr Sha2 + * + * This is an efficient yet simple base64 encoding and decoding API + * that supports regular base64, as well as the Matrix specification's + * extension to base64, called ``unpadded base64.'' This API provides + * the ability to convert between the two, instead of just implementing + * unpadded base64. + */ + #include +/** + * This function computes the amount of bytes needed to store a message + * of the specified number of bytes as base64. + */ extern size_t Base64EncodedSize(size_t); +/** + * This function computes the amount of bytes needed to store a decoded + * representation of the encoded message. It takes a pointer to the + * encoded string because it must read a few bytes off the end in order + * to accurately compute the size. + */ extern size_t Base64DecodedSize(const char *, size_t); +/** + * Encode the specified number of bytes from the specified buffer as + * base64. This function returns a string on the heap that should be + * freed with + * .Fn Free , + * or NULL if a memory allocation error ocurred. + */ extern char * Base64Encode(const char *, size_t); +/** + * Decode the specified number of bytes from the specified buffer of + * base64. This function returns a string on the heap that should be + * freed with + * .Fn Free , + * or NULL if a memory allocation error occured. + */ extern char * Base64Decode(const char *, size_t); +/** + * Remove the padding from a specified base64 string. This function + * modifies the specified string in place. It thus has no return value + * because it cannot fail. If the passed pointer is invalid, the + * behavior is undefined. + */ extern void Base64Unpad(char *, size_t); +/** + * Add padding to an unpadded base64 string. This function takes a + * pointer to a pointer because it may be necessary to grow the memory + * allocated to the string. This function returns a boolean value + * indicating whether the pad operation was successful. In practice, + * this means it will only fail if a bigger string is necessary, but it + * could not be automatically allocated on the heap. + */ extern int Base64Pad(char **, size_t); diff --git a/src/include/CanonicalJson.h b/src/include/CanonicalJson.h index a96e251..92eefa4 100644 --- a/src/include/CanonicalJson.h +++ b/src/include/CanonicalJson.h @@ -25,15 +25,71 @@ #ifndef TELODENDRIA_CANONICALJSON_H #define TELODENDRIA_CANONICALJSON_H +/*** + * @Nm CanonicalJson + * @Nd An extension of the JSON API that produces the Matrix spec's canonical JSON. + * @Dd November 30 2022 + * @Xr Json + * + * This API is an extension of + * .Xr json 3 + * that is specifically designed to produce the Matrix specification's + * "canonical" JSON. + * .Pp + * Canonical JSON is defined as JSON that: + * .Bl -bullet -offset indent + * .It + * Does not have any unecessary whitespace. + * .It + * Has all object key lexicographically sorted. + * .It + * Does not contain any floating point numerical values. + * .El + * .Pp + * The regular JSON encoder has no such rules, because normally they + * are not needed. However, Canonical JSON is needed in some cases to + * consistently sign JSON objects. + */ + #include #include #include -extern int - CanonicalJsonEncode(HashMap *, Stream *); +/** + * Encode a JSON object following the rules of Canonical JSON. See the + * documentation for + * .Fn JsonEncode , + * documented in + * .Xr Json 3 + * for more details on how JSON encoding operates. This function exists + * as an alternative to + * .Fn JsonEncode , + * but should not be preferred to it in most circumstances. It is a lot + * more costly, as it must lexicographically sort all keys and strip + * out float values. If at all possible, use + * .Fn JsonEncode , + * because it is much cheaper both in terms of memory and CPU time. + * .Pp + * This function returns a boolean value indicating whether or not the + * operation was sucessful. This function will fail only if NULL was + * given for any parameter. Otherwise, if an invalid pointer is given, + * undefined behavior results. + */ +extern int CanonicalJsonEncode(HashMap *, Stream *); -extern char * - CanonicalJsonEncodeToString(HashMap *); +/** + * This function encodes a JSON object to a string. + * .Xr Json 3 + * doesn't have any way to send JSON to a string, because there's + * absolutely no reason to handle JSON strings in most cases. However, + * the sole reason Canonical JSON exists is so that JSON objects can + * be signed in a consisten way. Thus, it is likely you need a string + * to pass to the signing function. + * .Pp + * This function returns a C string containing the canonical JSON + * representation of the given object, or NULL if the encoding failed. + */ +extern char * CanonicalJsonEncodeToString(HashMap *); #endif /* TELODENDRIA_CANONICALJSON_H */ diff --git a/src/include/Cron.h b/src/include/Cron.h index bb175d2..8cf0336 100644 --- a/src/include/Cron.h +++ b/src/include/Cron.h @@ -24,25 +24,115 @@ #ifndef TELODENDRIA_CRON_H #define TELODENDRIA_CRON_H +/*** + * @Nm Cron + * @Nd Basic periodic job scheduler. + * @Dd December 24 2022 + * + * This is an extremely basic job scheduler. So basic, in fact, that + * it currently runs all jobs on a single thread, which means that it + * is intended for short-lived jobs. In the future, it might be + * extended to support a one-thread-per-job model, but for now, jobs + * should take into consideration the fact that they are sharing their + * thread, so they should not be long-running. + * .Pp + * .Nm + * works by ``ticking'' at an interval defined by the caller of + * .Fn CronCreate . + * At each tick, all the registered jobs are queried, and if they are + * due to run again, their function is executed. As much as possible, + * .Nm + * tries to tick at constant intervals, however it is possible that one + * or more jobs may overrun the tick duration. If this happens, + * .Nm + * ticks again immediately after all the jobs for the previous tick + * have completed. This is in an effort to compensate for lost time, + * however it is important to note that when jobs overrun the tick + * interval, the interval is pushed back by the amount that it was + * overrun. Because of this, + * .Nm + * is best suited for scheduling jobs that should happen + * ``aproximately'' every so often; it is not a real-time scheduler + * by any means. + */ + +/** + * All functions defined here operate on a structure opaque to the + * caller. + */ typedef struct Cron Cron; +/** + * A job function is a function that takes a void pointer and returns + * nothing. The pointer is passed when the job is scheduled, and + * is expected to remain valid until the job is no longer registered. + * The pointer is passed each time the job executes. + */ typedef void (JobFunc) (void *); +/** + * Create a new + * .Nm + * object that all other functions operate on. Like most of the other + * APIs in this project, it must be freed with + * .Fn CronFree + * when it is no longer needed. + * .Pp + * This function takes the tick interval in milliseconds. + */ extern Cron * CronCreate(unsigned long); +/** + * Schedule a one-off job to be executed only at the next tick, and + * then immediately discarded. This is useful for scheduling tasks that + * only have to happen once, or very infrequently depending on + * conditions other than the current time, but don't have to happen + * immediately. The caller simply indicates that it wishes for the task + * to execute at some time in the future. How far into the future this + * practically ends up being is determined by how long it takes for + * other registered jobs to finish, and what the tick interval is. + * .Pp + * This function takes a job function and a pointer to pass to that + * function when it is executed. + */ extern void CronOnce(Cron *, JobFunc *, void *); +/** + * Schedule a repetitive task to be executed at aproximately the given + * interval. As stated above, this is as fuzzy interval; depending on + * the jobs being run and the tick interval, tasks may not execute at + * exactly the scheduled time, but they will eventually execute. + * .Pp + * This function takes an interval in milliseconds, a job function, + * and a pointer to pass to that function when it is executed. + */ extern void CronEvery(Cron *, unsigned long, JobFunc *, void *); +/** + * Start ticking the clock and executing registered jobs. + */ extern void CronStart(Cron *); +/** + * Stop ticking the clock. Jobs that are already executing will + * continue to execute, but when they are finished, no new jobs will + * be executed until + * .Fn CronStart + * is called. + */ extern void CronStop(Cron *); +/** + * Discard all job references and free all memory associated with the + * given + * .Nm Cron + * instance. + */ extern void CronFree(Cron *); diff --git a/src/include/Db.h b/src/include/Db.h index 9b6f6dc..7446264 100644 --- a/src/include/Db.h +++ b/src/include/Db.h @@ -24,48 +24,146 @@ #ifndef TELODENDRIA_DB_H #define TELODENDRIA_DB_H +/*** + * @Nm Db + * @Nd A minimal flat-file database with mutex locking and cache. + * @Dd April 27 2023 + * @Xr Json + * + * Telodendria operates on a flat-file database instead of a + * traditional relational database. This greatly simplifies the + * persistent storage code, and creates a relatively basic API, + * described here. + */ + #include #include #include +/** + * All functions in this API operate on a database structure that is + * opaque to the caller. + */ typedef struct Db Db; + +/** + * When an object is locked, a reference is returned. This reference + * is owned by the current thread, and the database is inaccessible to + * other threads until all references have been returned to the + * database. + * .Pp + * This reference is opaque, but can be manipulated by the functions + * defined here. + */ typedef struct DbRef DbRef; -extern Db * - DbOpen(char *, size_t); +/** + * Open a data directory. This function takes a path to open, and a + * cache size in bytes. If the cache size is 0, then caching is + * disabled and objects are loaded off the disk every time they are + * locked. Otherwise, objects are stored in the cache, and they are + * evicted in a least-recently-used manner. + */ +extern Db * DbOpen(char *, size_t); -extern void - DbMaxCacheSet(Db *, size_t); +/** + * Close the database. This function will flush anything in the cache + * to the disk, and then close the data directory. It assumes that + * all references have been unlocked. If a reference has not been + * unlocked, undefined behavior results. + */ +extern void DbClose(Db *); -extern void - DbClose(Db *); +/** + * Set the maximum cache size allowed before + * .Nm + * starts evicting old objects. If this is set to 0, everything in the + * cache is immediately evicted and caching is disabled. If the + * database was opened with a cache size of 0, setting this will + * initialize the cache, and subsequent calls to + * .Fn DbLock + * will begin caching objects. + */ +extern void DbMaxCacheSet(Db *, size_t); -extern DbRef * - DbCreate(Db *, size_t,...); +/** + * Create a new object in the database with the specified name. This + * function will fail if the object already exists in the database. It + * takes a variable number of C strings, with the exact number being + * specified by the second parameter. These C strings are used to + * generate a filesystem path at which to store the object. These paths + * ensure each object is uniquely identifiable, and provides semantic + * meaning to an object. + */ +extern DbRef * DbCreate(Db *, size_t,...); -extern int - DbDelete(Db *, size_t,...); +/** + * Lock an existing object in the database. This function will fail + * if the object does not exist. It takes a variable number of C + * strings, with the exact number being specified by the second + * parameter. These C strings are used to generate the filesystem path + * at which to load the object. These paths ensure each object is + * uniquely identifiable, and provides semantic meaning to an object. + */ +extern DbRef * DbLock(Db *, size_t,...); -extern DbRef * - DbLock(Db *, size_t,...); +/** + * Immediately and permanently remove an object from the database. + * This function assumes the object is not locked, otherwise undefined + * behavior will result. + */ +extern int DbDelete(Db *, size_t,...); -extern int - DbUnlock(Db *, DbRef *); +/** + * Unlock an object and return it back to the database. This function + * immediately syncs the object to the filesystem. The cache is a + * read cache; writes are always immediate to ensure data integrity in + * the event of a system failure. + */ +extern int DbUnlock(Db *, DbRef *); -extern int - DbExists(Db *, size_t,...); +/** + * Check the existence of the given database object in a more efficient + * manner than attempting to lock it with + * .Fn DbLock . + * This function does not lock the object, nor does it load it into + * memory if it exists. + */ +extern int DbExists(Db *, size_t,...); -extern Array * - DbList(Db *, size_t,...); +/** + * List all of the objects at a given path. Unlike the other varargs + * functions, this one does not take a path to a specific object; it + * takes a directory to be iterated, where each path part is its own + * C string. Note that the resulting list only contains the objects + * in the specified directory, it does not list any subdirectories. + * .Pp + * The array returned is an array of C strings containing the object + * name. + */ +extern Array * DbList(Db *, size_t,...); -extern void - DbListFree(Array *); +/** + * Free the list returned by + * .Fn DbListFree . + */ +extern void DbListFree(Array *); -extern HashMap * - DbJson(DbRef *); +/** + * Convert a database reference into JSON that can be manipulated. + * At this time, the database actually stores objects as JSON on the + * disk, so this function just returns an internal pointer, but in the + * future it may have to be generated by decompressing a binary blob, + * or something of that nature. + */ +extern HashMap * DbJson(DbRef *); -extern int - DbJsonSet(DbRef *, HashMap *); +/** + * Free the existing JSON associated with the given reference, and + * replace it with new JSON. This is more efficient than duplicating + * a separate object into the database reference. + */ +extern int DbJsonSet(DbRef *, HashMap *); #endif diff --git a/src/include/HashMap.h b/src/include/HashMap.h index c99936e..f3b7a83 100644 --- a/src/include/HashMap.h +++ b/src/include/HashMap.h @@ -25,35 +25,143 @@ #ifndef TELODENDRIA_HASHMAP_H #define TELODENDRIA_HASHMAP_H +/*** + * @Nm HashMap + * @Nd A simple hash map implementation. + * @Dd October 11 2022 + * @Xr Array Queue + * + * This is the public interface for Telodendria's hash map + * implementation. This hash map is designed to be simple, + * well-documented, and generally readable and understandable, yet also + * performant enough to be useful, because it is used extensively + * throughout the project. + * .Pp + * Fundamentally, this is an entirely generic map implementation. It + * can be used for many general purposes, but it is designed only to + * implement the features Telodendria needs to be functional. One + * example of a Telodendria-specific feature is that keys cannot be + * arbitrary data; they are NULL-terminated C strings. + */ + #include +/** + * These functions operate on an opaque structure, which the caller + * has no knowledge about. + */ typedef struct HashMap HashMap; -extern HashMap * - HashMapCreate(void); +/** + * Create a new hash map that is ready to be used with the rest of the + * functions defined here. + */ +extern HashMap * HashMapCreate(void); +/** + * Free the specified hash map such that it becomes invalid and any + * future use results in undefined behavior. Note that this function + * does not free the values stored in the hash map, but since it stores + * the keys internally, it will free the keys. You should use + * .Fn HashMapIterate + * to free the values stored in this map appropriately before calling + * this function. + */ +extern void HashMapFree(HashMap *); + +/** + * Control 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 they are full, because that + * makes them perform very poorly. The maximum load value is a + * percentage of how full the hash map is, and it should be between + * 0 and 1, where 0 means that no elements will cause the map to be + * expanded, and 1 means that the hash map must be completely full + * before it is expanded. The default maximum load on a new hash map + * is 0.75, which should be good enough for most purposes, however, + * this function exists specifically so that the maximum load can be + * fine-tuned. + */ +extern void HashMapMaxLoadSet(HashMap *, float); + +/** + * Use a custom hashing function with the given hash map. New hash + * maps have a sane hashing function that should work okay for most + * use cases, but if you have a better hashing function, it can be + * specified this way. Do not change the hash function after keys have + * been added; doing so results in undefined behavior. Only set a new + * hash function immediately after constructing a new hash map, before + * anything has been added to it. + * .Pp + * The hash function takes a pointer to a C string, and is expected + * to return a fairly unique numerical hash value which will be + * converted into an array index. + */ extern void - HashMapMaxLoadSet(HashMap *, float); +HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); -extern void - HashMapFunctionSet(HashMap *, unsigned long (*) (const char *)); +/** + * Set the given string key to the given value. Note that the key is + * copied into the hash map's own memory space, but the value is not. + * It is the caller's job to ensure that the value pointer remains + * valid for the life of the hash map, and are freed when no longer + * needed. + */ +extern void * HashMapSet(HashMap *, char *, void *); -extern void * - HashMapSet(HashMap *, char *, void *); +/** + * Retrieve the value for the given key, or return NULL if no such + * key exists in the hash map. + */ +extern void * HashMapGet(HashMap *, const char *); -extern void * - HashMapGet(HashMap *, const char *); +/** + * Remove a value specified by the given key from the hash map, and + * return it to the caller to deal with. This function returns NULL + * if no such key exists. + */ +extern void * HashMapDelete(HashMap *, const char *); -extern void * - HashMapDelete(HashMap *, const char *); +/** + * Iterate over all the keys and values of a hash map. This function + * works very similarly to + * .Xr getopt 3 , + * 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; it takes pointer pointers, and stores all + * necessary state inside the hash map itself. + * .Pp + * Note that this function is not thread-safe; two threads cannot be + * iterating over any given hash map at the same time, though they + * can each be iterating over different hash maps. + * .Pp + * 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 to go through in the hash map. If you are not iterating + * over the entire map in one go, and happen to break the loop, then + * the next time you attempt to iterate the hash map, you'll start + * somewhere in the middle, which is most likely not the intended + * behavior. Thus, it is always recommended to iterate over the entire + * hash map if you're going to use this function. + * .Pp + * Also note that the behavior of this function is undefined if + * insertions or deletions occur during the iteration. This + * functionality has not been tested, and will likely not work. + */ +extern int HashMapIterate(HashMap *, char **, void **); +/** + * A reentrant version of + * .Fn HashMapIterate + * that allows the caller to overcome the flaws of that function by + * storing the cursor outside of the hash map structure itself. This + * allows multiple threads to iterate over the same hash map at the + * same time, and it allows the iteration to be halted midway through + * without causing any unintended side effects. + * .Pp + * The cursor should be initialized to 0 at the start of iteration. + */ extern int - HashMapIterate(HashMap *, char **, void **); - -extern int - HashMapIterateReentrant(HashMap *, char **, void **, size_t *); - -extern void - HashMapFree(HashMap *); +HashMapIterateReentrant(HashMap *, char **, void **, size_t *); #endif /* TELODENDRIA_HASHMAP_H */ diff --git a/src/include/Http.h b/src/include/Http.h index 859bfcf..233ba93 100644 --- a/src/include/Http.h +++ b/src/include/Http.h @@ -24,6 +24,22 @@ #ifndef TELODENDRIA_HTTP_H #define TELODENDRIA_HTTP_H +/*** + * @Nm Http + * @Nd Encode and decode various parts of the HTTP protocol. + * @Dd March 12 2023 + * @Xr HttpClient HttpServer HashMap Queue Memory + * + * .Nm + * is a collection of utility functions and type definitions that are + * useful for dealing with HTTP. HTTP is not a complex protocol, but + * this API makes it a lot easier to work with. + * .Pp + * Note that this API doesn't target any particular HTTP version, but + * it is currently used with HTTP 1.0 clients and servers, and + * therefore may be lacking functionality added in later HTTP versions. + */ + #include #include @@ -32,6 +48,11 @@ #define HTTP_FLAG_NONE 0 #define HTTP_FLAG_TLS (1 << 0) +/** + * The request methods defined by the HTTP standard. These numeric + * constants should be preferred to strings when building HTTP APIs + * because they are more efficient. + */ typedef enum HttpRequestMethod { HTTP_METHOD_UNKNOWN, @@ -46,6 +67,10 @@ typedef enum HttpRequestMethod HTTP_PATCH } HttpRequestMethod; +/** + * An enumeration that corresponds to the actual integer values of the + * valid HTTP response codes. + */ typedef enum HttpStatus { HTTP_STATUS_UNKNOWN = 0, @@ -110,28 +135,77 @@ typedef enum HttpStatus HTTP_NETWORK_AUTH_REQUIRED = 511 } HttpStatus; -extern const char * - HttpStatusToString(const HttpStatus); +/** + * Convert an HTTP status enumeration value into a string description + * of the status, which is to be used in server response to a client, + * or a client response to a user. For example, calling + * .Fn HttpStatusToString "HTTP_GATEWAY_TIMEOUT" + * (or + * .Fn HttpStatusToString "504" ) + * produces the string "Gateway Timeout". Note that the returned + * pointers point to static space, so their manipulation is forbidden. + */ +extern const char * HttpStatusToString(const HttpStatus); -extern HttpRequestMethod - HttpRequestMethodFromString(const char *); +/** + * Convert a string into a numeric code that can be used throughout + * the code of a program in an efficient manner. See the definition + * of HttpRequestMethod. This function does case-sensitive matching, + * and does not trim or otherwise process the input string. + */ +extern HttpRequestMethod HttpRequestMethodFromString(const char *); -extern const char * - HttpRequestMethodToString(const HttpRequestMethod); +/** + * Convert a numeric code as defined by HttpRequestMethod into a + * string that can be sent to a server. Note that the returned pointers + * point to static space, so their manipulation is forbidden. + */ +extern const char * HttpRequestMethodToString(const HttpRequestMethod); -extern char * - HttpUrlEncode(char *); +/** + * Encode a C string such that it can safely appear in a URL by + * performing the necessary percent escaping. A new string on the + * heap is returned. It should be freed with + * .Fn Free , + * defined in the + * .Xr Memory 3 + * API. + */ +extern char * HttpUrlEncode(char *); -extern char * - HttpUrlDecode(char *); +/** + * Decode a percent-encoded string into a C string, ignoring encoded + * null characters entirely, because those would do nothing but cause + * problems. + */ +extern char * HttpUrlDecode(char *); -extern HashMap * - HttpParamDecode(char *); +/** + * Decode an encoded parameter string in the form of + * ``key=val&key2=val2'' into a hash map whose values are C strings. + * This function properly decodes keys and values using the functions + * defined above. + */ +extern HashMap * HttpParamDecode(char *); -extern char * - HttpParamEncode(HashMap *); +/** + * Encode a hash map whose values are strings as an HTTP parameter + * string suitable for GET or POST requests. + */ +extern char * HttpParamEncode(HashMap *); -extern HashMap * - HttpParseHeaders(Stream *); +/** + * Read HTTP headers from a stream and return a hash map whose values + * are strings. All keys are lowercased to make querying them + * consistent and not dependent on the case that was read from the + * stream. This is useful for both client and server code, since the + * headers are in the same format. This function should be used after + * parsing the HTTP status line, because it does not parse that line. + * It will stop when it encounters the first blank line, which + * indicates that the body is beginning. After this function completes, + * the body may be immediately read from the stream without any + * additional processing. + */ +extern HashMap * HttpParseHeaders(Stream *); #endif diff --git a/src/include/HttpServer.h b/src/include/HttpServer.h index a739017..868990f 100644 --- a/src/include/HttpServer.h +++ b/src/include/HttpServer.h @@ -24,6 +24,28 @@ #ifndef TELODENDRIA_HTTPSERVER_H #define TELODENDRIA_HTTPSERVER_H +/*** + * @Nm HttpServer + * @Nd Extremely simple HTTP server. + * @Dd December 13 2022 + * @Xr Http HttpClient + * + * .Nm + * builds on the + * .Xr Http 3 + * API, and provides a very simple, yet very functional API for + * creating an HTTP server. It aims at being easy to use and minimal, + * yet also efficient. It uses non-blocking I/O, is fully + * multi-threaded, and is very configurable. It can be set up in just + * two function calls and minimal supporting code. + * .Pp + * This API should be familar to those that have dealt with the HTTP + * server libraries of other programming languages, particularly Java. + * In fact, much of the terminology used in this API came from Java, + * and you'll notice that the way responses are sent and received very + * closely resembles Java. + */ + #include #include @@ -31,68 +53,182 @@ #include #include +/** + * The functions on this API operate on an opaque structure. + */ typedef struct HttpServer HttpServer; +/** + * Each request receives a context structure. It is opaque, so the + * functions defined in this API should be used to fetch data from + * it. These functions allow the handler to figure out the context of + * the request, which includes the path requested, any parameters, + * and the headers and method used to make the request. The context + * also provides the means by which the handler responds to the + * request, allowing it to set the status code, headers, and body. + */ typedef struct HttpServerContext HttpServerContext; + +/** + * The request handler function is executed when an HTTP request is + * received. It takes a request context, and a pointer as specified + * in the server configuration. + */ typedef void (HttpHandler) (HttpServerContext *, void *); +/** + * The number of arguments to + * .Fn HttpServerCreate + * has grown so large that arguments are not stuffed into a + * configuration structure, which is in turn passed to + * .Fn HttpServerCreate . + * This configuration is copied by value into the internal + * structures of the server. It is copied with very minimal + * validation, so ensure that all values are sensible. It may + * make sense to use + * .Fn memset + * to zero out everything in here before assigning values. + */ typedef struct HttpServerConfig { unsigned short port; unsigned int threads; unsigned int maxConnections; - int flags; - char *tlsCert; - char *tlsKey; + int flags; /* Http(3) flags */ + char *tlsCert; /* File path */ + char *tlsKey; /* File path */ HttpHandler *handler; void *handlerArgs; } HttpServerConfig; -extern HttpServer * - HttpServerCreate(HttpServerConfig *); +/** + * Create a new HTTP server using the specified configuration. + * This will set up all internal structures used by the server, + * and bind the socket and start listening for connections. However, + * it will not start accepting connections. + */ +extern HttpServer * HttpServerCreate(HttpServerConfig *); -extern HttpServerConfig * - HttpServerConfigGet(HttpServer *); +/** + * Retrieve the configuration that was used to instantiate the given + * server. Note that this configuration is not necessarily the exact + * one that was provided; even though its values are the same, it + * should be treated as an entirely separate configuration with no + * connection to the original. + */ +extern HttpServerConfig * HttpServerConfigGet(HttpServer *); -extern void - HttpServerFree(HttpServer *); +/** + * Free the resources associated with the given HTTP server. Note that + * the server can only be freed after it has been stopped. Calling this + * function while the server is still running results in undefined + * behavior. + */ +extern void HttpServerFree(HttpServer *); -extern int - HttpServerStart(HttpServer *); +/** + * Attempt to start the HTTP server, and return immediately with the + * status. This API is fully multi-threaded and asynchronous, so the + * caller can continue working while the HTTP server is running in a + * separate thread and managing a pool of threads to handle responses. + */ +extern int HttpServerStart(HttpServer *); -extern void - HttpServerJoin(HttpServer *); +/** + * Typically, at some point after calling + * .Fn HttpServerStart , + * the program will have no more work to do, so it will want to wait + * for the HTTP server to finish. This is accomplished via this + * function, which joins the HTTP worker thread to the calling thread, + * pausing the calling thread until the HTTP server has stopped. + */ +extern void HttpServerJoin(HttpServer *); -extern void - HttpServerStop(HttpServer *); +/** + * Stop the HTTP server. Only the execution of this function will + * cause the proper shutdown of the HTTP server. If the main program + * is joined to the HTTP thread, then either another thread or a + * signal handler will have to stop the server using this function. + * The typical use case is to install a signal handler that executes + * this function on a global HTTP server. + */ +extern void HttpServerStop(HttpServer *); -extern HashMap * - HttpRequestHeaders(HttpServerContext *); +/** + * Get the request headers for the request represented by the given + * context. The data in the returned hash map should be treated as + * read only and should not be freed; it is managed entirely by the + * server. + */ +extern HashMap * HttpRequestHeaders(HttpServerContext *); -extern HttpRequestMethod - HttpRequestMethodGet(HttpServerContext *); +/** + * Get the request method used to make the request represented by + * the given context. + */ +extern HttpRequestMethod HttpRequestMethodGet(HttpServerContext *); -extern char * - HttpRequestPath(HttpServerContext *); +/** + * Get the request path for the request represented by the given + * context. The return value of this function should be treated as + * read-only, and should not be freed; it is managed entirely by the + * server. + */ +extern char * HttpRequestPath(HttpServerContext *); -extern HashMap * - HttpRequestParams(HttpServerContext *); +/** + * Retrieve the parsed GET parameters for the request represented by + * the given context. The returned hash map should be treated as + * read-only, and should not be freed; it is managed entirely by the + * server. + */ +extern HashMap * HttpRequestParams(HttpServerContext *); -extern char * - HttpResponseHeader(HttpServerContext *, char *, char *); +/** + * Set a response header to return to the client. The old value for + * the given header is returned, if any, otherwise NULL is returned. + */ +extern char * HttpResponseHeader(HttpServerContext *, char *, char *); -extern void - HttpResponseStatus(HttpServerContext *, HttpStatus); +/** + * Set the response status to return to the client. + */ +extern void HttpResponseStatus(HttpServerContext *, HttpStatus); -extern HttpStatus - HttpResponseStatusGet(HttpServerContext *); +/** + * Get the current response status that will be sent to the client + * making the request represented by the given context. + */ +extern HttpStatus HttpResponseStatusGet(HttpServerContext *); -extern Stream * - HttpServerStream(HttpServerContext *); +/** + * Send the response headers to the client that made the request + * represented by the specified context. This function must be called + * before the response body can be written, otherwise a malformed + * response will be sent. + */ +extern void HttpSendHeaders(HttpServerContext *); -extern void - HttpSendHeaders(HttpServerContext *); +/** + * Get a stream that is both readable and writable. Reading from the + * stream reads the request body that the client sent, if there is one. + * Note that the rquest headers have already been read, so the stream + * is correctly positioned at the beginning of the body of the request. + * .Fn HttpSendHeaders + * must be called before the stream is written, otherwise a malformed + * HTTP response will be sent. An HTTP handler should properly set all + * the headers it itends to send, send those headers, and then write + * the response body to this stream. + * .Pp + * Note that the stream does not need to be closed by the HTTP + * handler; in fact doing so results in undefined behavior. The stream + * is managed entirely by the server itself, so it will close it when + * necessary. This allows the underlying protocol to differ: for + * instance, an HTTP/1.1 connection may stay for multiple requests and + * responses. + */ +extern Stream * HttpServerStream(HttpServerContext *); -#endif +#endif /* TELODENDRIA_HTTPSERVER_H */ diff --git a/src/include/Json.h b/src/include/Json.h index c76dea2..aa97ae1 100644 --- a/src/include/Json.h +++ b/src/include/Json.h @@ -25,6 +25,49 @@ #ifndef TELODENDRIA_JSON_H #define TELODENDRIA_JSON_H +/*** + * @Nm Json + * @Nd A fully-featured JSON API. + * @Dd March 12 2023 + * @Xr HashMap Array Stream + * + * .Nm + * is a fully-featured JSON API for C using the array and hash map + * APIs. It can parse JSON, ans serialize an in-memory structure to + * JSON. It build on the foundation of Array and HashMap because that's + * all JSON really is, just arrays and maps. + * .Nm + * also provides a structure for encapsulating an arbitrary value and + * identifying its type, making it easy for a strictly-typed language + * like C to work with loosely-typed JSON data. + * .Nm + * is very strict and tries to adhere as closely as possible to the + * proper definition of JSON. It will fail on syntax errors of any + * kind, which is fine for a Matrix homeserver that can just return + * M_BAD_JSON if anything in here fails, but this behavior may not be + * suitable for other purposes. + * .Pp + * This JSON implementation focuses primarily on serialization and + * deserialization to and from streams. It does not provide facilities + * for handling JSON strings; it only writes JSON to output streams, + * and reads them from input streams. Of course, you can use the + * POSIX + * .Xr fmemopen 3 + * and + * .Xr open_memstream 3 + * functions if you want to deal with JSON strings, but JSON is + * intended to be an exchange format. Data should be converted to JSON + * right when it is leaving the program, and converted from JSON to the + * in-memory format as soon as it is coming in. + * .Pp + * JSON objects are represented as hash maps consisting entirely of + * JsonValue structures, and arrays are represented as arrays + * consisting entirely of JsonValue structures. When generating a + * JSON object, any attempt to stuff a value into a hash map or array + * without first encoding it as a JsonValue will result in undefined + * behavior. + */ + #include #include #include @@ -35,90 +78,233 @@ #define JSON_DEFAULT -1 #define JSON_PRETTY 0 -typedef enum JsonType -{ - JSON_NULL, - JSON_OBJECT, - JSON_ARRAY, - JSON_STRING, - JSON_INTEGER, - JSON_FLOAT, - JSON_BOOLEAN -} JsonType; - +/** + * This opaque structure encapsulates all the possible types that can + * be stored in JSON. It is managed entirely by the functions defined + * in this API. It is important to note that strings, integers, floats, + * booleans, and the NULL value are all stored by value, but objects + * and arrays are stored by reference. That is, it doesn't store these + * itself, just pointers to them, however, the data + * .Em is + * freed when using + * .Fn JsonFree . + */ typedef struct JsonValue JsonValue; -extern JsonType - JsonValueType(JsonValue *); +/** + * These are the types that can be used to identify a JsonValue + * and act on it accordingly. + */ +typedef enum JsonType +{ + JSON_NULL, /* Maps to a C NULL */ + JSON_OBJECT, /* Maps to a HashMap of JsonValues */ + JSON_ARRAY, /* Maps to an Array of JsonValues */ + JSON_STRING, /* Maps to a null-terminated C string */ + JSON_INTEGER, /* Maps to a C long */ + JSON_FLOAT, /* Maps to a C double */ + JSON_BOOLEAN /* Maps to a C integer of either 0 or 1 */ +} JsonType; -extern JsonValue * - JsonValueObject(HashMap *); +/** + * Determine the type of the specified JSON value. + */ +extern JsonType JsonValueType(JsonValue *); -extern HashMap * - JsonValueAsObject(JsonValue *); +/** + * Encode a JSON object as a JSON value that can be added to another + * object, or an array. + */ +extern JsonValue * JsonValueObject(HashMap *); -extern JsonValue * - JsonValueArray(Array *); +/** + * Unwrap a JSON value that represents an object. This function will + * return NULL if the value is not actually an object. + */ +extern HashMap * JsonValueAsObject(JsonValue *); -extern Array * - JsonValueAsArray(JsonValue *); +/** + * Encode a JSON array as a JSON value that can be added to an object + * or another array. + */ +extern JsonValue * JsonValueArray(Array *); -extern JsonValue * - JsonValueString(char *); +/** + * Unwrap a JSON value that represents an array. This function will + * return NULL if the value is not actually an array. + */ +extern Array * JsonValueAsArray(JsonValue *); -extern char * - JsonValueAsString(JsonValue *); +/** + * Encode a C string as a JSON value that can be added to an object or + * an array. + */ +extern JsonValue * JsonValueString(char *); -extern JsonValue * - JsonValueInteger(long); +/** + * Unwrap a JSON value that represents a string. This function will + * return NULL if the value is not actually a string. + */ +extern char * JsonValueAsString(JsonValue *); -extern long - JsonValueAsInteger(JsonValue *); +/** + * Encode a number as a JSON value that can be added to an object or + * an array. + */ +extern JsonValue * JsonValueInteger(long); -extern JsonValue * - JsonValueFloat(double); +/** + * Unwrap a JSON value that represents a number. This function will + * return 0 if the value is not actually a number, which may be + * misleading. Check the type of the value before making assumptions + * about its value. + */ +extern long JsonValueAsInteger(JsonValue *); -extern double - JsonValueAsFloat(JsonValue *); +/** + * Encode a floating point number as a JSON value that can be added + * to an object or an array. + */ +extern JsonValue * JsonValueFloat(double); -extern JsonValue * - JsonValueBoolean(int); +/** + * Unwrap a JSON value that represents a floating point number. This + * function will return 0 if the value is not actually a floating + * point number, which may be misleading. Check the type of the value + * before making assumptions about its type. + */ +extern double JsonValueAsFloat(JsonValue *); -extern int - JsonValueAsBoolean(JsonValue *); +/** + * Encode a C integer according to the way C treats integers in boolean + * expressions as a JSON value that can be added to an object or an + * array. + */ +extern JsonValue * JsonValueBoolean(int); -extern JsonValue * - JsonValueNull(void); +/** + * Unwrap a JSON value that represents a boolean. This function will + * return 0 if the value is not actually a boolean, which may be + * misleading. Check the type of the value before making assumptions + * about its type. + */ +extern int JsonValueAsBoolean(JsonValue *); -extern void - JsonValueFree(JsonValue *); +/** + * This is a special case that represents a JSON null. Because the + * Array and HashMap APIs do not accept NULL values, this function + * should be used to represent NULL in JSON. Even though a small + * amount of memory is allocated just to be a placeholder for nothing, + * this keeps the APIs clean. + */ +extern JsonValue * JsonValueNull(void); -extern JsonValue * - JsonValueDuplicate(JsonValue *); +/** + * 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 the given value, including any strings attached to + * this value. + */ +extern void JsonValueFree(JsonValue *); -extern HashMap * - JsonDuplicate(HashMap *); +/** + * Recursively duplicate the given JSON value. This returns a new + * JSON value that is completely identical to the specified value, but + * in no way connected to it. + */ +extern JsonValue * JsonValueDuplicate(JsonValue *); -extern void - JsonFree(HashMap *); +/** + * Recursively duplicate the given JSON object. This returns a new + * JSON object that is completely identical to the specified object, + * but in no way connect to it. + */ +extern HashMap * JsonDuplicate(HashMap *); +/** + * Recursively free a JSON object by iterating over all of its values + * and freeing them using + * .Fn JsonValueFree . + */ +extern void JsonFree(HashMap *); -extern void - JsonEncodeString(const char *, Stream *); +/** + * Encode the given string in such a way that it can be safely + * embedded in a JSON stream. This entails: + * .Bl -bullet -offset indent + * .It + * Escaping quotes, backslashes, and other special characters using + * their backslash escape. + * .It + * Encoding bytes that are not UTF-8 using escapes. + * .It + * Wrapping the entire string in double quotes. + * .El + * .Pp + * This function is only provided via the public + * .Nm + * API so that it is accessible to custom JSON encoders, such as the + * CanonicalJson encoder. This will typically be used for encoding + * object keys; to encode values, just use + * .Fn JsonEncodeValue . + */ +extern void JsonEncodeString(const char *, Stream *); -extern void - JsonEncodeValue(JsonValue * value, Stream * out, int); +/** + * Serialize a JSON value as it would appear in JSON output. This is + * a recursive function that also encodes all child values reachable + * from the given value. This function is exposed via the public + * .Nm + * API so that it is accessible to custom JSON encoders. Normal users + * that are not writing custom encoders should in most cases just use + * .Fn JsonEncode + * to encode an entire object. + * .Pp + * The third parameter is an integer that represents the indent level + * of the value to be printed, or a negative number if pretty-printing + * should be disabled and JSON should be printed as minimized as + * possible. To pretty-print a JSON object, set this to + * .Va JSON_PRETTY . + * To get minified output, set it to + * .Va JSON_DEFAULT . + */ +extern void JsonEncodeValue(JsonValue *, Stream *, int); -extern int - JsonEncode(HashMap *, Stream *, int); +/** + * Encode a JSON object as it would appear in JSON output, writing it + * to the given output stream. This function is recursive; it will + * serialize everything accessible from the passed object. The third + * parameter has the same behavior as described above. + */ +extern int JsonEncode(HashMap *, Stream *, int); -extern HashMap * - JsonDecode(Stream *); +/** + * Decode a JSON object from the given input stream and parse it into + * a hash map of JSON values. + */ +extern HashMap * JsonDecode(Stream *); -extern JsonValue * - JsonGet(HashMap *, size_t,...); +/** + * A convenience function that allows the caller to retrieve and + * arbitrarily deep keys within a JSON object. It takes a root JSON + * object, the number of levels deep to go, and then that number of + * keys as a varargs list. All keys must have objects as values, with + * the exception of the last one, which is the value that will be + * returned. Otherwise, NULL indicates the specified path doas not + * exist. + */ +extern JsonValue * JsonGet(HashMap *, size_t,...); -extern JsonValue * - JsonSet(HashMap *, JsonValue *, size_t,...); +/** + * A convenience function that allows the caller to set arbitrarily + * deep keys within a JSON object. It takes a root JSON object, the + * number of levels deep to go, and then that number of keys as a + * varargs list. All keys must have object as values, with the + * exception of the last one, which is the value that will be set. + * The value currently at that key, if any, will be returned. + * This function will create any intermediate objects as necessary to + * set the proper key. + */ +extern JsonValue * JsonSet(HashMap *, JsonValue *, size_t,...); #endif /* TELODENDRIA_JSON_H */ diff --git a/src/include/Log.h b/src/include/Log.h index 148574c..42df5d0 100644 --- a/src/include/Log.h +++ b/src/include/Log.h @@ -25,6 +25,19 @@ #ifndef TELODENDRIA_LOG_H #define TELODENDRIA_LOG_H +/*** + * @Nm Log + * @Nd A simple logging framework for logging to multiple destinations. + * @Dd April 27 2023 + * @Xr Stream + * + * .Nm + * is a simple C logging library that allows for colorful outputs, + * timestamps, and custom log levels. It also features the ability to + * have multiple logs open at one time, although Telodendria primarily + * utilizes the global log. All logs are thread safe. + */ + #include #include #include @@ -34,28 +47,67 @@ #define LOG_FLAG_COLOR (1 << 0) #define LOG_FLAG_SYSLOG (1 << 1) +/** + * A log is defined as a configuration that describes the properties + * of the log. This opaque structure can be manipulated by the + * functions defined in this API. + */ typedef struct LogConfig LogConfig; -extern LogConfig * - LogConfigCreate(void); +/** + * Create a new log configuration with sane defaults that can be used + * immediately with the logging functions. + */ +extern LogConfig * LogConfigCreate(void); -extern LogConfig * - LogConfigGlobal(void); +/** + * Get the global log configuration, creating a new one with + * .Fn LogConfigCreate + * if necessary. + */ +extern LogConfig * LogConfigGlobal(void); -extern void - LogConfigFree(LogConfig *); +/** + * Free the given log configuration. Note that this does not close the + * underlying stream associated with the log, if there is one. Also + * note that to avoid memory leaks, the global log configuration must + * also be freed, but it cannot be used after it is freed. + */ +extern void LogConfigFree(LogConfig *); -extern void - LogConfigLevelSet(LogConfig *, int); +/** + * Set the current log level on the specified log configuration. + * This indicates that only messages at or above this level should be + * logged; all others are silently discarded. The passed log level + * should be one of the log levels defined by + * .Xr syslog 3 . + * Refer to that page for a complete list of acceptable log levels, + * and note that passing an invalid log level will result in undefined + * behavior. + */ +extern void LogConfigLevelSet(LogConfig *, int); -extern void - LogConfigIndent(LogConfig *); +/** + * Cause the log output to be indented two more spaces than it was + * previously. This can be helpful when generating stack traces or + * other hierarchical output. This is a simple convenience wrapper + * around + * .Fn LogConfigIndentSet . + */ +extern void LogConfigIndent(LogConfig *); -extern void - LogConfigUnindent(LogConfig *); +/** + * Cause the log output to be indented two less spaces than it was + * previously. This is a simple convenience wrapper around + * .Fn LogConfigIndentSet . + */ +extern void LogConfigUnindent(LogConfig *); -extern void - LogConfigIndentSet(LogConfig *, size_t); +/** + * Indent future log output using the specified config by some + * arbitrary amount. + */ +extern void LogConfigIndentSet(LogConfig *, size_t); extern void LogConfigOutputSet(LogConfig *, Stream *);