summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2023-12-29 00:20:57 +0300
committerOxore <oxore@protonmail.com>2023-12-29 00:20:57 +0300
commit5f8f5140eb6d7aa204d2565eec29fd7a2a934906 (patch)
treeae156bd1b7e982c8035c5cfa5546b587771232cb
parent18893127524d87e47a948b9a92d8d8b2ab869852 (diff)
Introduce runtime log level switching
-rw-r--r--CMakeLists.txt21
-rw-r--r--client.c63
-rw-r--r--debug.c39
-rw-r--r--debug.h15
-rw-r--r--lib/optparse/README.md241
-rw-r--r--lib/optparse/UNLICENSE24
-rw-r--r--lib/optparse/commit.txt1
-rw-r--r--lib/optparse/optparse.h403
-rw-r--r--proto.c37
-rw-r--r--proto_io.c5
-rw-r--r--server.c65
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}>)
diff --git a/client.c b/client.c
index 11f6156..f9e97ab 100644
--- a/client.c
+++ b/client.c
@@ -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;
diff --git a/debug.c b/debug.c
new file mode 100644
index 0000000..eff2654
--- /dev/null
+++ b/debug.c
@@ -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");
+ }
+}
diff --git a/debug.h b/debug.h
new file mode 100644
index 0000000..c1e8a22
--- /dev/null
+++ b/debug.h
@@ -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 */
diff --git a/proto.c b/proto.c
index b1f4e02..47af6c6 100644
--- a/proto.c
+++ b/proto.c
@@ -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;
diff --git a/proto_io.c b/proto_io.c
index a903546..8f1605d 100644
--- a/proto_io.c
+++ b/proto_io.c
@@ -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;
diff --git a/server.c b/server.c
index 8d00dc7..5e35f3e 100644
--- a/server.c
+++ b/server.c
@@ -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;