diff options
author | Oxore <oxore@protonmail.com> | 2023-12-29 00:20:57 +0300 |
---|---|---|
committer | Oxore <oxore@protonmail.com> | 2023-12-29 00:20:57 +0300 |
commit | 5f8f5140eb6d7aa204d2565eec29fd7a2a934906 (patch) | |
tree | ae156bd1b7e982c8035c5cfa5546b587771232cb | |
parent | 18893127524d87e47a948b9a92d8d8b2ab869852 (diff) |
Introduce runtime log level switching
-rw-r--r-- | CMakeLists.txt | 21 | ||||
-rw-r--r-- | client.c | 63 | ||||
-rw-r--r-- | debug.c | 39 | ||||
-rw-r--r-- | debug.h | 15 | ||||
-rw-r--r-- | lib/optparse/README.md | 241 | ||||
-rw-r--r-- | lib/optparse/UNLICENSE | 24 | ||||
-rw-r--r-- | lib/optparse/commit.txt | 1 | ||||
-rw-r--r-- | lib/optparse/optparse.h | 403 | ||||
-rw-r--r-- | proto.c | 37 | ||||
-rw-r--r-- | proto_io.c | 5 | ||||
-rw-r--r-- | server.c | 65 |
11 files changed, 849 insertions, 65 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 40a1814..4a33eef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,37 +35,30 @@ set(compile_flags ) set(compile_definitions $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2> - LOG_ERROR=1 - LOG_WARNING=1 - LOG_INFO=1 - $<$<CONFIG:Debug>:LOG_DEBUG=1> - $<$<CONFIG:Debug>:LOG_TRACE=0> - $<$<NOT:$<CONFIG:Debug>>:LOG_DEBUG=0> - $<$<NOT:$<CONFIG:Debug>>:LOG_TRACE=0> ) -add_library(timespec OBJECT lib/timespec/timespec.c lib/timespec/timespec.h) -target_compile_options(timespec PRIVATE ${compile_flags}) -target_compile_definitions(timespec PRIVATE ${compile_definitions}) -target_link_options(timespec PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) - add_library(common OBJECT + debug.c + debug.h proto.c proto.h proto_io.c proto_io.h + lib/timespec/timespec.c + lib/timespec/timespec.h + lib/optparse/optparse.h ) target_compile_options(common PRIVATE ${compile_flags}) target_compile_definitions(common PRIVATE ${compile_definitions}) target_link_options(common PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) -add_executable(server server.c $<TARGET_OBJECTS:common> $<TARGET_OBJECTS:timespec>) +add_executable(server server.c $<TARGET_OBJECTS:common>) target_compile_options(server PRIVATE ${compile_flags}) target_compile_definitions(server PRIVATE ${compile_definitions}) target_link_options(server PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) target_include_directories(server PRIVATE . lib) -add_executable(client client.c $<TARGET_OBJECTS:common> $<TARGET_OBJECTS:timespec>) +add_executable(client client.c $<TARGET_OBJECTS:common>) target_compile_options(client PRIVATE ${compile_flags}) target_compile_definitions(client PRIVATE ${compile_definitions}) target_link_options(client PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) @@ -1,9 +1,16 @@ /* SPDX-License-Identifier: Unlicense */ +#include "debug.h" #include "proto.h" #include "proto_io.h" #include "timespec/timespec.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#include "optparse/optparse.h" +#pragma GCC diagnostic pop #include <assert.h> #include <errno.h> @@ -46,7 +53,9 @@ static void HandleFrame( self->connection_id = frame->connection_id; self->connected = true; } else if (frame->type == FT_SYN && self->connected) { - printf("client_rt %5" PRIu32 "\n", self->client_roundtrip_prev); + if (g_log_level >= LOG_LEVEL_DEBUG) { + fprintf(stderr, "client_rt %5" PRIu32 "\n", self->client_roundtrip_prev); + } struct frame outgoing = *frame; outgoing.type = FT_SYNACK, outgoing.syn = (struct frame_data_syn){ @@ -123,17 +132,57 @@ static void sigintHandler(int a) (void) a; } -int main(int const argc, char *const argv[]) +static void PrintUsage(FILE *s, const char *argv0) { - if (argc < 3) { - fprintf(stderr, "Usage: %s <address> <port>\n", argv[0]); - return 1; + // Please, keep all lines in 80 columns range when printed. + fprintf(s, + "Usage: %s [options] <address> <port>\n" + "Options:\n" + " -h, --help, Show this message.\n" + " -d, --debug-level=<level>,\n" + " Set id of debug log level: 0 means no logs, \n" + " 1 means info, 2 means some debug,\n" + " 3 is for maximum trace\n" + , argv0); +} + +int main(int argc, char *argv[]) +{ + (void) argc; + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {"debug-level", 'd', OPTPARSE_REQUIRED}, + {0}, + }; + struct optparse options; + optparse_init(&options, argv); + // Parse opts + int option; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'h': + PrintUsage(stdout, argv[0]); + return EXIT_SUCCESS; + break; + case 'd': + g_log_level = atoi(options.optarg); + break; + case '?': + fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg); + return EXIT_FAILURE; + } + } + char const *const addr_arg = optparse_arg(&options); + char const *const port_arg = optparse_arg(&options); + if (addr_arg == NULL || port_arg == NULL) { + PrintUsage(stdout, argv[0]); + return EXIT_SUCCESS; } - unsigned const addr = ParseAddress(argv[1], stderr); + unsigned const addr = ParseAddress(addr_arg, stderr); if (addr == INADDR_NONE) { return 1; } - int const port = atoi(argv[2]); + int const port = atoi(port_arg); if (port <= 0 || port > UINT16_MAX) { fprintf(stderr, "Invalid port specified\n"); return 1; @@ -0,0 +1,39 @@ +/* SPDX-License-Identifier: Unlicense */ + +#include "debug.h" + +#include <stdint.h> + +int g_log_level = LOG_LEVEL_INFO; + +static char ByteToPrintableChar(uint32_t byte) +{ + return (byte >= 0x20 && byte <= 0x7f) ? byte : '.'; +} + +void FPrintRaw(FILE *s, void const *data_arg, size_t size) +{ + const size_t cols = 16; + const size_t section_width = 8; + if (size == 0) { + return; + } + uint8_t const *data = data_arg; + for (size_t line = 0; line < (size - 1) / cols + 1; line++) { + fprintf(s, "%08zx: ", line * cols); + for (size_t c = 0; c < cols; c++) { + fprintf(s, "%02x ", data[line * cols + c]); + if (c % section_width == section_width - 1 && c != cols - 1) { + fprintf(s, " "); + } + } + fprintf(s, " |"); + for (size_t c = 0; c < cols; c++) { + fprintf(s, "%c", ByteToPrintableChar(data[line * cols + c])); + if (c % section_width == section_width - 1 && c != cols - 1) { + fprintf(s, " "); + } + } + fprintf(s, "|\n"); + } +} @@ -0,0 +1,15 @@ +#pragma once + +/* SPDX-License-Identifier: Unlicense */ + +#include <stdio.h> +#include <stddef.h> + +#define LOG_LEVEL_OFF 0 +#define LOG_LEVEL_INFO 1 +#define LOG_LEVEL_DEBUG 2 +#define LOG_LEVEL_TRACE 3 + +void FPrintRaw(FILE *s, void const *data_arg, size_t size); + +extern int g_log_level; diff --git a/lib/optparse/README.md b/lib/optparse/README.md new file mode 100644 index 0000000..2a85d56 --- /dev/null +++ b/lib/optparse/README.md @@ -0,0 +1,241 @@ +# Optparse + +Optparse is a public domain, portable, reentrant, embeddable, getopt-like +option parser. As a single header file, it's trivially dropped into any +project. It supports POSIX getopt option strings, GNU-style long options, +argument permutation, and subcommand processing. + +To get the implementation, define `OPTPARSE_IMPLEMENTATION` before +including `optparse.h`. + +~~~c +#define OPTPARSE_IMPLEMENTATION +#include "optparse.h" +~~~ + +Optionally define `OPTPARSE_API` to control the API's visibility +and/or linkage (`static`, `__attribute__`, `__declspec`). + +~~~c +#define OPTPARSE_API static +#include "optparse.h" +~~~ + +## Why not getopt()? + +The POSIX getopt option parser has three fatal flaws. These flaws are +solved by Optparse. + +1. The getopt parser state is stored entirely in global variables, +some of which are static and inaccessible. This means only one thread +can use getopt. It also means it's not possible to recursively parse +nested sub-arguments while in the middle of argument parsing. Optparse +fixes this by storing all state on a local struct. + +2. The POSIX standard provides no way to properly reset the parser. +For portable code this means getopt is only good for one run, over one +argv with one option string. It also means subcommand options cannot +be reliably processed with getopt. Most implementations provide an +implementation-specific method to reset the parser, but this is not +portable. Optparse provides an `optparse_arg()` function for stepping +through non-option arguments, and parsing of options can continue +again at any time with a different option string. The Optparse struct +itself could be passed around to subcommand handlers for additional +subcommand option parsing. If a full parser reset is needed, +`optparse_init()` can be called again. + +3. In getopt, error messages are printed to stderr. This can be +disabled with opterr, but the messages themselves are still +inaccessible. Optparse solves this by writing the error message to its +errmsg field, which can be printed to anywhere. The downside to +Optparse is that this error message will always be in English rather +than the current locale. + +## Permutation + +By default, argv is permuted as it is parsed, moving non-option +arguments to the end of the array. This can be disabled by setting the +`permute` field to 0 after initialization. + +~~~c +struct optparse options; +optparse_init(&options, argv); +options.permute = 0; +~~~ + +## Drop-in Replacement + +Optparse's interface should be familiar with anyone accustomed to +getopt. It's nearly a drop-in replacement. The option string has the +same format and the parser struct fields have the same names as the +getopt global variables (optarg, optind, optopt). + +The long option parser `optparse_long()` API is very similar to GNU's +`getopt_long()` and can serve as a portable, embedded replacement. + +Optparse does not allocate memory. Furthermore, Optparse has no +dependencies, including libc itself, so it can be used in situations +where the standard C library cannot. + +See `optparse.h` for full API documentation. + +## Example Usage + +Here's a traditional getopt setup. + +~~~c +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <getopt.h> + +int main(int argc, char **argv) +{ + bool amend = false; + bool brief = false; + const char *color = "white"; + int delay = 0; + + int option; + while ((option = getopt(argc, argv, "abc:d::")) != -1) { + switch (option) { + case 'a': + amend = true; + break; + case 'b': + brief = true; + break; + case 'c': + color = optarg; + break; + case 'd': + delay = optarg ? atoi(optarg) : 1; + break; + case '?': + exit(EXIT_FAILURE); + } + } + + /* Print remaining arguments. */ + for (; optind < argc; optind++) + printf("%s\n", argv[optind]); + return 0; +} +~~~ + +Here's the same thing translated to Optparse. + +~~~c +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> + +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#include "optparse.h" + +int main(int argc, char **argv) +{ + bool amend = false; + bool brief = false; + const char *color = "white"; + int delay = 0; + + char *arg; + int option; + struct optparse options; + + optparse_init(&options, argv); + while ((option = optparse(&options, "abc:d::")) != -1) { + switch (option) { + case 'a': + amend = true; + break; + case 'b': + brief = true; + break; + case 'c': + color = options.optarg; + break; + case 'd': + delay = options.optarg ? atoi(options.optarg) : 1; + break; + case '?': + fprintf(stderr, "%s: %s\n", argv[0], options.errmsg); + exit(EXIT_FAILURE); + } + } + + /* Print remaining arguments. */ + while ((arg = optparse_arg(&options))) + printf("%s\n", arg); + return 0; +} +~~~ + +And here's a conversion to long options. + +~~~c +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> + +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#include "optparse.h" + +int main(int argc, char **argv) +{ + struct optparse_long longopts[] = { + {"amend", 'a', OPTPARSE_NONE}, + {"brief", 'b', OPTPARSE_NONE}, + {"color", 'c', OPTPARSE_REQUIRED}, + {"delay", 'd', OPTPARSE_OPTIONAL}, + {0} + }; + + bool amend = false; + bool brief = false; + const char *color = "white"; + int delay = 0; + + char *arg; + int option; + struct optparse options; + + optparse_init(&options, argv); + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'a': + amend = true; + break; + case 'b': + brief = true; + break; + case 'c': + color = options.optarg; + break; + case 'd': + delay = options.optarg ? atoi(options.optarg) : 1; + break; + case '?': + fprintf(stderr, "%s: %s\n", argv[0], options.errmsg); + exit(EXIT_FAILURE); + } + } + + /* Print remaining arguments. */ + while ((arg = optparse_arg(&options))) + printf("%s\n", arg); + + return 0; +} +~~~ + +## Subcommand Parsing + +To parse subcommands, first parse options with permutation disabled. These +are the "global" options that come before the subcommand. Then parse the +remainder, optionally permuting, as a new option array. + +See `examples/subcommands.c` for a complete, working example. diff --git a/lib/optparse/UNLICENSE b/lib/optparse/UNLICENSE new file mode 100644 index 0000000..68a49da --- /dev/null +++ b/lib/optparse/UNLICENSE @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +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 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. + +For more information, please refer to <http://unlicense.org/> diff --git a/lib/optparse/commit.txt b/lib/optparse/commit.txt new file mode 100644 index 0000000..53964f0 --- /dev/null +++ b/lib/optparse/commit.txt @@ -0,0 +1 @@ +https://github.com/skeeto/optparse/tree/cdc96b235343c8cf6475482075371b249b2187d6 diff --git a/lib/optparse/optparse.h b/lib/optparse/optparse.h new file mode 100644 index 0000000..8d6c0a9 --- /dev/null +++ b/lib/optparse/optparse.h @@ -0,0 +1,403 @@ +/* Optparse --- portable, reentrant, embeddable, getopt-like option parser + * + * This is free and unencumbered software released into the public domain. + * + * To get the implementation, define OPTPARSE_IMPLEMENTATION. + * Optionally define OPTPARSE_API to control the API's visibility + * and/or linkage (static, __attribute__, __declspec). + * + * The POSIX getopt() option parser has three fatal flaws. These flaws + * are solved by Optparse. + * + * 1) Parser state is stored entirely in global variables, some of + * which are static and inaccessible. This means only one thread can + * use getopt(). It also means it's not possible to recursively parse + * nested sub-arguments while in the middle of argument parsing. + * Optparse fixes this by storing all state on a local struct. + * + * 2) The POSIX standard provides no way to properly reset the parser. + * This means for portable code that getopt() is only good for one + * run, over one argv with one option string. It also means subcommand + * options cannot be processed with getopt(). Most implementations + * provide a method to reset the parser, but it's not portable. + * Optparse provides an optparse_arg() function for stepping over + * subcommands and continuing parsing of options with another option + * string. The Optparse struct itself can be passed around to + * subcommand handlers for additional subcommand option parsing. A + * full reset can be achieved by with an additional optparse_init(). + * + * 3) Error messages are printed to stderr. This can be disabled with + * opterr, but the messages themselves are still inaccessible. + * Optparse solves this by writing an error message in its errmsg + * field. The downside to Optparse is that this error message will + * always be in English rather than the current locale. + * + * Optparse should be familiar with anyone accustomed to getopt(), and + * it could be a nearly drop-in replacement. The option string is the + * same and the fields have the same names as the getopt() global + * variables (optarg, optind, optopt). + * + * Optparse also supports GNU-style long options with optparse_long(). + * The interface is slightly different and simpler than getopt_long(). + * + * By default, argv is permuted as it is parsed, moving non-option + * arguments to the end. This can be disabled by setting the `permute` + * field to 0 after initialization. + */ +#ifndef OPTPARSE_H +#define OPTPARSE_H + +#ifndef OPTPARSE_API +# define OPTPARSE_API +#endif + +struct optparse { + char **argv; + int permute; + int optind; + int optopt; + char *optarg; + char errmsg[64]; + int subopt; +}; + +enum optparse_argtype { + OPTPARSE_NONE, + OPTPARSE_REQUIRED, + OPTPARSE_OPTIONAL +}; + +struct optparse_long { + const char *longname; + int shortname; + enum optparse_argtype argtype; +}; + +/** + * Initializes the parser state. + */ +OPTPARSE_API +void optparse_init(struct optparse *options, char **argv); + +/** + * Read the next option in the argv array. + * @param optstring a getopt()-formatted option string. + * @return the next option character, -1 for done, or '?' for error + * + * Just like getopt(), a character followed by no colons means no + * argument. One colon means the option has a required argument. Two + * colons means the option takes an optional argument. + */ +OPTPARSE_API +int optparse(struct optparse *options, const char *optstring); + +/** + * Handles GNU-style long options in addition to getopt() options. + * This works a lot like GNU's getopt_long(). The last option in + * longopts must be all zeros, marking the end of the array. The + * longindex argument may be NULL. + */ +OPTPARSE_API +int optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex); + +/** + * Used for stepping over non-option arguments. + * @return the next non-option argument, or NULL for no more arguments + * + * Argument parsing can continue with optparse() after using this + * function. That would be used to parse the options for the + * subcommand returned by optparse_arg(). This function allows you to + * ignore the value of optind. + */ +OPTPARSE_API +char *optparse_arg(struct optparse *options); + +/* Implementation */ +#ifdef OPTPARSE_IMPLEMENTATION + +#define OPTPARSE_MSG_INVALID "invalid option" +#define OPTPARSE_MSG_MISSING "option requires an argument" +#define OPTPARSE_MSG_TOOMANY "option takes no arguments" + +static int +optparse_error(struct optparse *options, const char *msg, const char *data) +{ + unsigned p = 0; + const char *sep = " -- '"; + while (*msg) + options->errmsg[p++] = *msg++; + while (*sep) + options->errmsg[p++] = *sep++; + while (p < sizeof(options->errmsg) - 2 && *data) + options->errmsg[p++] = *data++; + options->errmsg[p++] = '\''; + options->errmsg[p++] = '\0'; + return '?'; +} + +OPTPARSE_API +void +optparse_init(struct optparse *options, char **argv) +{ + options->argv = argv; + options->permute = 1; + options->optind = argv[0] != 0; + options->subopt = 0; + options->optarg = 0; + options->errmsg[0] = '\0'; +} + +static int +optparse_is_dashdash(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] == '\0'; +} + +static int +optparse_is_shortopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] != '-' && arg[1] != '\0'; +} + +static int +optparse_is_longopt(const char *arg) +{ + return arg != 0 && arg[0] == '-' && arg[1] == '-' && arg[2] != '\0'; +} + +static void +optparse_permute(struct optparse *options, int index) +{ + char *nonoption = options->argv[index]; + int i; + for (i = index; i < options->optind - 1; i++) + options->argv[i] = options->argv[i + 1]; + options->argv[options->optind - 1] = nonoption; +} + +static int +optparse_argtype(const char *optstring, char c) +{ + int count = OPTPARSE_NONE; + if (c == ':') + return -1; + for (; *optstring && c != *optstring; optstring++); + if (!*optstring) + return -1; + if (optstring[1] == ':') + count += optstring[2] == ':' ? 2 : 1; + return count; +} + +OPTPARSE_API +int +optparse(struct optparse *options, const char *optstring) +{ + int type; + char *next; + char *option = options->argv[options->optind]; + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (!optparse_is_shortopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse(options, optstring); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + option += options->subopt + 1; + options->optopt = option[0]; + type = optparse_argtype(optstring, option[0]); + next = options->argv[options->optind + 1]; + switch (type) { + case -1: { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optind++; + return optparse_error(options, OPTPARSE_MSG_INVALID, str); + } + case OPTPARSE_NONE: + if (option[1]) { + options->subopt++; + } else { + options->subopt = 0; + options->optind++; + } + return option[0]; + case OPTPARSE_REQUIRED: + options->subopt = 0; + options->optind++; + if (option[1]) { + options->optarg = option + 1; + } else if (next != 0) { + options->optarg = next; + options->optind++; + } else { + char str[2] = {0, 0}; + str[0] = option[0]; + options->optarg = 0; + return optparse_error(options, OPTPARSE_MSG_MISSING, str); + } + return option[0]; + case OPTPARSE_OPTIONAL: + options->subopt = 0; + options->optind++; + if (option[1]) + options->optarg = option + 1; + else + options->optarg = 0; + return option[0]; + } + return 0; +} + +OPTPARSE_API +char * +optparse_arg(struct optparse *options) +{ + char *option = options->argv[options->optind]; + options->subopt = 0; + if (option != 0) + options->optind++; + return option; +} + +static int +optparse_longopts_end(const struct optparse_long *longopts, int i) +{ + return !longopts[i].longname && !longopts[i].shortname; +} + +static void +optparse_from_long(const struct optparse_long *longopts, char *optstring) +{ + char *p = optstring; + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + if (longopts[i].shortname && longopts[i].shortname < 127) { + int a; + *p++ = longopts[i].shortname; + for (a = 0; a < (int)longopts[i].argtype; a++) + *p++ = ':'; + } + } + *p = '\0'; +} + +/* Unlike strcmp(), handles options containing "=". */ +static int +optparse_longopts_match(const char *longname, const char *option) +{ + const char *a = option, *n = longname; + if (longname == 0) + return 0; + for (; *a && *n && *a != '='; a++, n++) + if (*a != *n) + return 0; + return *n == '\0' && (*a == '\0' || *a == '='); +} + +/* Return the part after "=", or NULL. */ +static char * +optparse_longopts_arg(char *option) +{ + for (; *option && *option != '='; option++); + if (*option == '=') + return option + 1; + else + return 0; +} + +static int +optparse_long_fallback(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int result; + char optstring[96 * 3 + 1]; /* 96 ASCII printable characters */ + optparse_from_long(longopts, optstring); + result = optparse(options, optstring); + if (longindex != 0) { + *longindex = -1; + if (result != -1) { + int i; + for (i = 0; !optparse_longopts_end(longopts, i); i++) + if (longopts[i].shortname == options->optopt) + *longindex = i; + } + } + return result; +} + +OPTPARSE_API +int +optparse_long(struct optparse *options, + const struct optparse_long *longopts, + int *longindex) +{ + int i; + char *option = options->argv[options->optind]; + if (option == 0) { + return -1; + } else if (optparse_is_dashdash(option)) { + options->optind++; /* consume "--" */ + return -1; + } else if (optparse_is_shortopt(option)) { + return optparse_long_fallback(options, longopts, longindex); + } else if (!optparse_is_longopt(option)) { + if (options->permute) { + int index = options->optind++; + int r = optparse_long(options, longopts, longindex); + optparse_permute(options, index); + options->optind--; + return r; + } else { + return -1; + } + } + + /* Parse as long option. */ + options->errmsg[0] = '\0'; + options->optopt = 0; + options->optarg = 0; + option += 2; /* skip "--" */ + options->optind++; + for (i = 0; !optparse_longopts_end(longopts, i); i++) { + const char *name = longopts[i].longname; + if (optparse_longopts_match(name, option)) { + char *arg; + if (longindex) + *longindex = i; + options->optopt = longopts[i].shortname; + arg = optparse_longopts_arg(option); + if (longopts[i].argtype == OPTPARSE_NONE && arg != 0) { + return optparse_error(options, OPTPARSE_MSG_TOOMANY, name); + } if (arg != 0) { + options->optarg = arg; + } else if (longopts[i].argtype == OPTPARSE_REQUIRED) { + options->optarg = options->argv[options->optind]; + if (options->optarg == 0) + return optparse_error(options, OPTPARSE_MSG_MISSING, name); + else + options->optind++; + } + return options->optopt; + } + } + return optparse_error(options, OPTPARSE_MSG_INVALID, option); +} + +#endif /* OPTPARSE_IMPLEMENTATION */ +#endif /* OPTPARSE_H */ @@ -1,6 +1,7 @@ /* SPDX-License-Identifier: Unlicense */ #include "proto.h" +#include "debug.h" #include <assert.h> #include <stdint.h> @@ -44,43 +45,11 @@ static inline void SetU32NE(void *buffer, uint32_t v) ((uint8_t *)buffer)[0] = (v >> 24) & 0xff; } -static char ByteToPrintableChar(uint32_t byte) -{ - return (byte >= 0x20 && byte <= 0x7f) ? byte : '.'; -} - -static void FPrintRaw(FILE *s, void const *data_arg, size_t size) -{ - const size_t cols = 16; - const size_t section_width = 8; - if (size == 0) { - return; - } - uint8_t const *data = data_arg; - for (size_t line = 0; line < (size - 1) / cols + 1; line++) { - fprintf(s, "%08zx: ", line * cols); - for (size_t c = 0; c < cols; c++) { - fprintf(s, "%02x ", data[line * cols + c]); - if (c % section_width == section_width - 1 && c != cols - 1) { - fprintf(s, " "); - } - } - fprintf(s, " |"); - for (size_t c = 0; c < cols; c++) { - fprintf(s, "%c", ByteToPrintableChar(data[line * cols + c])); - if (c % section_width == section_width - 1 && c != cols - 1) { - fprintf(s, " "); - } - } - fprintf(s, "|\n"); - } -} - struct frame ParseFrame(void const *buffer_arg, size_t buffer_size) { assert(buffer_size >= 8); uint8_t const *buffer = buffer_arg; - if (LOG_TRACE) { + if (g_log_level >= LOG_LEVEL_TRACE) { FPrintRaw(stderr, buffer, buffer_size); } if (GetU16NE(buffer) != 0) { @@ -189,7 +158,7 @@ size_t SerializeFrame(void *buffer_arg, size_t buffer_size, struct frame const * SetU16NE(buffer + 2, frame->type); SetU32NE(buffer + 4, frame->connection_id); size_t const size = 8 + serializeFrameData(buffer + 8, buffer_size - 8, frame); - if (LOG_TRACE) { + if (g_log_level >= LOG_LEVEL_TRACE) { FPrintRaw(stderr, buffer, size); } return size; @@ -1,5 +1,6 @@ /* SPDX-License-Identifier: Unlicense */ +#include "debug.h" #include "proto_io.h" #include <arpa/inet.h> @@ -39,7 +40,7 @@ int ReceiveFrame(int fd, struct frame *frame) frame->addr = addr; frame->port = port; } - if (LOG_TRACE) { + if (g_log_level >= LOG_LEVEL_TRACE) { uint8_t const a0 = addr >> 24; uint8_t const a1 = addr >> 16; uint8_t const a2 = addr >> 8; @@ -57,7 +58,7 @@ int SendFrame(int const fd, struct frame const *const frame) .sin_port = htons(frame->port), .sin_addr.s_addr = htonl(frame->addr), }; - if (LOG_TRACE) { + if (g_log_level >= LOG_LEVEL_TRACE) { uint32_t const addr = frame->addr; uint16_t const port = frame->port; uint8_t const a0 = addr >> 24; @@ -1,9 +1,16 @@ /* SPDX-License-Identifier: Unlicense */ +#include "debug.h" #include "proto_io.h" #include "proto.h" #include "timespec/timespec.h" +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" +#define OPTPARSE_IMPLEMENTATION +#define OPTPARSE_API static +#include "optparse/optparse.h" +#pragma GCC diagnostic pop #include <assert.h> #include <errno.h> @@ -55,7 +62,7 @@ static void ConnectionHandleFrame( int fd) { if (frame->type == FT_SYNACK) { - if (LOG_TRACE) { + if (g_log_level >= LOG_LEVEL_TRACE) { fprintf(stderr, "FT_SYNACK(tick=%u, lost=%d, rt_prev=%u, repeat=%u)\n", frame->syn.tick, frame->syn.prev_is_lost, frame->syn.roundtrip_prev, frame->syn.repeat); @@ -70,8 +77,10 @@ static void ConnectionHandleFrame( long const srt_ms = timespec_to_ms(timespec_sub(now, self->last_syn)); assert(srt_ms >= 0); self->server_roundtrip_prev = srt_ms; - fprintf(stderr, "%5" PRIu32 " server_rt %5lu ms, client_rt %5" PRIu32 " ms\n", - self->tick, (unsigned long)srt_ms, frame->syn.roundtrip_prev); + if (g_log_level >= LOG_LEVEL_DEBUG) { + fprintf(stderr, "%5" PRIu32 " server_rt %5lu ms, client_rt %5" PRIu32 " ms\n", + self->tick, (unsigned long)srt_ms, frame->syn.roundtrip_prev); + } self->tick++; } } @@ -99,7 +108,7 @@ static void HandleFrame(struct frame const *frame, struct timespec now, int fd) return; } if (frame->type == FT_HANDSHAKE) { - if (LOG_DEBUG) { + if (g_log_level >= LOG_LEVEL_DEBUG) { fprintf(stderr, "FT_HANDSHAKE(tick_period=%" PRIu32 ")\n", frame->handshake.tick_period); } struct frame outgoing = *frame; @@ -124,7 +133,7 @@ static void HandleFrame(struct frame const *frame, struct timespec now, int fd) outgoing.type = FT_HANDSHAKE_RESP; outgoing.connection_id = i; SendFrame(fd, &outgoing); - if (LOG_DEBUG) { + if (g_log_level >= LOG_LEVEL_DEBUG) { fprintf(stderr, "New connection id=%" PRIu32 "\n", connection_id); } return; @@ -139,14 +148,52 @@ static void HandleFrame(struct frame const *frame, struct timespec now, int fd) } if (frame->type == FT_RST) { g_connections[connection_id].exist = false; - if (LOG_DEBUG) { + if (g_log_level >= LOG_LEVEL_DEBUG) { fprintf(stderr, "Connection closed id=%" PRIu32 "\n", connection_id); } } } -int main(void) +static void PrintUsage(FILE *s, const char *argv0) +{ + // Please, keep all lines in 80 columns range when printed. + fprintf(s, + "Usage: %s [options]\n" + "Options:\n" + " -h, --help, Show this message.\n" + " -d, --debug-level=<level>,\n" + " Set id of debug log level: 0 means no logs, \n" + " 1 means info, 2 means some debug,\n" + " 3 is for maximum trace\n" + , argv0); +} + +int main(int argc, char *argv[]) { + (void) argc; + struct optparse_long longopts[] = { + {"help", 'h', OPTPARSE_NONE}, + {"debug-level", 'd', OPTPARSE_REQUIRED}, + {0}, + }; + struct optparse options; + optparse_init(&options, argv); + // Parse opts + int option; + while ((option = optparse_long(&options, longopts, NULL)) != -1) { + switch (option) { + case 'h': + PrintUsage(stdout, argv[0]); + return EXIT_SUCCESS; + break; + case 'd': + g_log_level = atoi(options.optarg); + break; + case '?': + fprintf(stderr, "main: optparse_long: Error: \"%s\"\n", options.errmsg); + return EXIT_FAILURE; + } + } int const fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) { perror("socket failed"); @@ -173,7 +220,9 @@ int main(void) uint8_t const a1 = addr >> 16; uint8_t const a2 = addr >> 8; uint8_t const a3 = (uint8_t)addr; - fprintf(stderr, "Server is listening on %u.%u.%u.%u:%u\n", a0, a1, a2, a3, port); + if (g_log_level >= LOG_LEVEL_INFO) { + fprintf(stderr, "Server is listening on %u.%u.%u.%u:%u\n", a0, a1, a2, a3, port); + } } struct pollfd fds[1] = { { .fd = fd, .events = POLLIN, }, }; int timeout_ms = 10000u; |