diff options
author | Oxore <oxore@protonmail.com> | 2023-12-28 00:04:20 +0300 |
---|---|---|
committer | Oxore <oxore@protonmail.com> | 2023-12-28 00:04:20 +0300 |
commit | 18893127524d87e47a948b9a92d8d8b2ab869852 (patch) | |
tree | 1491660a439221174a776b9c7f38f05234590cdb | |
parent | 4faf778b6ec4b9d8ab0c19e22d27c76f4c67ab28 (diff) |
WIP
-rw-r--r-- | CMakeLists.txt | 37 | ||||
-rw-r--r-- | client.c | 166 | ||||
-rw-r--r-- | lib/timespec/README.md | 123 | ||||
-rw-r--r-- | lib/timespec/commit.txt | 1 | ||||
-rw-r--r-- | lib/timespec/timespec.c | 979 | ||||
-rw-r--r-- | lib/timespec/timespec.h | 71 | ||||
-rw-r--r-- | proto.c | 196 | ||||
-rw-r--r-- | proto.h | 140 | ||||
-rw-r--r-- | proto_io.c | 78 | ||||
-rw-r--r-- | proto_io.h | 8 | ||||
-rw-r--r-- | server.c | 258 |
11 files changed, 1945 insertions, 112 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8b0a77a..40a1814 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,15 +33,40 @@ set(compile_flags -Wcast-align -Wshadow ) +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 + proto.c + proto.h + proto_io.c + proto_io.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) +add_executable(server server.c $<TARGET_OBJECTS:common> $<TARGET_OBJECTS:timespec>) target_compile_options(server PRIVATE ${compile_flags}) -target_compile_definitions(server PRIVATE $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>) +target_compile_definitions(server PRIVATE ${compile_definitions}) target_link_options(server PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) -target_include_directories(server PRIVATE .) +target_include_directories(server PRIVATE . lib) -add_executable(client client.c) +add_executable(client client.c $<TARGET_OBJECTS:common> $<TARGET_OBJECTS:timespec>) target_compile_options(client PRIVATE ${compile_flags}) -target_compile_definitions(client PRIVATE $<$<CONFIG:Debug>:_FORTIFY_SOURCE=2>) +target_compile_definitions(client PRIVATE ${compile_definitions}) target_link_options(client PRIVATE $<$<CONFIG:Debug>:${common_debug_flags}>) -target_include_directories(client PRIVATE .) +target_include_directories(client PRIVATE . lib) @@ -1,19 +1,71 @@ -// Initially based on https://stackoverflow.com/a/35570418 +/* SPDX-License-Identifier: Unlicense */ +#include "proto.h" +#include "proto_io.h" + +#include "timespec/timespec.h" + +#include <assert.h> +#include <errno.h> +#include <fcntl.h> +#include <inttypes.h> #include <netinet/in.h> +#include <poll.h> +#include <signal.h> #include <stdbool.h> #include <stdint.h> -#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> +#include <time.h> #include <unistd.h> -static unsigned char udp_buffer[UINT16_MAX]; +struct connection { + int fd; + uint16_t port; + uint32_t addr; + bool connected; + uint32_t connection_id; + struct timespec last_syn; + uint32_t server_roundtrip_prev; + uint32_t client_roundtrip_prev; +}; + +static volatile bool g_should_exit = false; +struct connection g_conn = { .fd = -1, }; static inline bool IsDigit(char c) { return c >= '0' && c <= '9'; } +static void HandleFrame( + struct connection *self, + struct frame const *frame, + struct timespec now) +{ + if (frame->type == FT_HANDSHAKE_RESP) { + 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); + struct frame outgoing = *frame; + outgoing.type = FT_SYNACK, + outgoing.syn = (struct frame_data_syn){ + .tick = frame->syn.tick, + .roundtrip_prev = self->client_roundtrip_prev, + .prev_is_lost = false, + }; + SendFrame(self->fd, &outgoing); + self->last_syn = now; + self->server_roundtrip_prev = frame->syn.roundtrip_prev; + } else if (frame->type == FT_ACK && self->connected) { + long const ms = timespec_to_ms(timespec_sub(now, self->last_syn)); + assert(ms >= 0); + self->client_roundtrip_prev = ms; + } else if (frame->type == FT_RST) { + // TODO + } +} + static uint32_t ParseAddress(char const *str, FILE *log_stream) { uint32_t addr = 0; @@ -66,6 +118,11 @@ static uint32_t ParseAddress(char const *str, FILE *log_stream) return addr; } +static void sigintHandler(int a) +{ + (void) a; +} + int main(int const argc, char *const argv[]) { if (argc < 3) { @@ -86,34 +143,83 @@ int main(int const argc, char *const argv[]) perror("socket failed"); return 1; } - struct sockaddr_in const serveraddr = { - .sin_family = AF_INET, - .sin_port = htons(port), - .sin_addr.s_addr = htonl(addr), + fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + g_conn.addr = addr; + g_conn.port = port; + g_conn.fd = fd; + struct frame const handshake = { + .addr = addr, + .port = port, + .type = FT_HANDSHAKE, + .handshake = { + .tick_period = 200, + }, }; - ssize_t const ret = sendto( - fd, "\x2a", 1, 0, (struct sockaddr const *)&serveraddr, sizeof(serveraddr)); - if (ret < 0) { - perror("sendto failed"); - return 1; + SendFrame(fd, &handshake); + struct sigaction sa = { .sa_handler = sigintHandler, }; + sigaction(SIGINT, &sa, NULL); + struct pollfd fds[1] = { { .fd = fd, .events = POLLIN, }, }; + while (!g_should_exit) { + int const pollret = poll(fds, sizeof(fds)/sizeof(*fds), -1); + int const poll_err = errno; + struct timespec start; + if (-1 == clock_gettime(CLOCK_MONOTONIC, &start)) { + int const err = errno; + fprintf(stderr, + "clock_gettime(CLOCK_MONOTONIC, &start) failed (%d): \"%s\"", + err, strerror(err)); + } + if (pollret > 0) { + if (fds[0].revents & POLLIN) { + while (1) { + struct frame frame = { .type = FT_NONE, }; + int const ret = ReceiveFrame(fds[0].fd, &frame); + if (ret) { + break; + } + HandleFrame(&g_conn, &frame, start); + } + } + if (fds[0].revents & POLLRDNORM) { + fprintf(stderr, "POLLRDNORM, ignoring...\n"); + } + if (fds[0].revents & POLLRDBAND) { + fprintf(stderr, "POLLRDBAND, ignoring...\n"); + } + if (fds[0].revents & POLLPRI) { + fprintf(stderr, "POLLPRI, ignoring...\n"); + } + if (fds[0].revents & POLLPRI) { + fprintf(stderr, "POLLPRI, ignoring...\n"); + } + if (fds[0].revents & POLLHUP) { + fprintf(stderr, "POLLHUP, ignoring...\n"); + } + if (fds[0].revents & POLLERR) { + fprintf(stderr, "POLLERR, ignoring...\n"); + } + if (fds[0].revents & POLLNVAL) { + fprintf(stderr, "POLLNVAL, exiting...\n"); + return 1; + } + } else if (pollret < 0) { + if (poll_err == EINTR) { + fprintf(stderr, "SIGINT received\n"); + if (g_conn.connected) { + struct frame const rst = { + .addr = g_conn.addr, + .port = g_conn.port, + .type = FT_RST, + }; + SendFrame(g_conn.fd, &rst); + } + break; + } + fprintf(stderr, + "poll() failed (%d): \"%s\"", + poll_err, strerror(poll_err)); + } } - printf("message sent\n"); - struct sockaddr_in source_address; - socklen_t source_address_len = sizeof(source_address); - int const length = recvfrom( - fd, - udp_buffer, - sizeof(udp_buffer) - 1, - 0, - (struct sockaddr*)&source_address, - &source_address_len); - (void) length; - uint32_t const addr2 = ntohl(source_address.sin_addr.s_addr); - uint8_t const a0 = addr2 >> 24; - uint8_t const a1 = addr2 >> 16; - uint8_t const a2 = addr2 >> 8; - uint8_t const a3 = addr2; - uint16_t const port2 = ntohs(source_address.sin_port); - printf("Received: %u from %u.%u.%u.%u:%u\n", udp_buffer[0], a0, a1, a2, a3, port2); close(fd); } + diff --git a/lib/timespec/README.md b/lib/timespec/README.md new file mode 100644 index 0000000..5578518 --- /dev/null +++ b/lib/timespec/README.md @@ -0,0 +1,123 @@ +# Introduction + +This library provides functions for working with timespec structures. + +It aims to provide a comprehensive set of functions with well-defined behaviour +that handle all edge cases (e.g. negative values) in a sensible manner. + +Negative values are allowed in the tv_sec and/or tv_usec field of timespec +structures, tv_usec is always relative to tv_sec, so mixing positive and +negative values will produce consistent results: + + { tv_sec = 1, tv_nsec = 500000000 } == 1.5 seconds + { tv_sec = 1, tv_nsec = 0 } == 1.0 seconds + { tv_sec = 1, tv_nsec = -500000000 } == 0.5 seconds + { tv_sec = 0, tv_nsec = 500000000 } == 0.5 seconds + { tv_sec = 0, tv_nsec = 0 } == 0.0 seconds + { tv_sec = 0, tv_nsec = -500000000 } == -0.5 seconds + { tv_sec = -1, tv_nsec = 500000000 } == -0.5 seconds + { tv_sec = -1, tv_nsec = 0 } == -1.0 seconds + { tv_sec = -1, tv_nsec = -500000000 } == -1.5 seconds + +Furthermore, any timespec structures processed or returned by library functions +are normalised according to the rules defined by `timespec_normalise()`. + +A test program for your platform can be produced by compiling with `-DTEST`. + +This is public domain. Feel free to embed it in your software if it meets your +needs. + +# Functions + +## Maths functions + +`struct timespec timespec_add(struct timespec ts1, struct timespec ts2)` + +Returns the result of adding two timespec structures. + +`struct timespec timespec_sub(struct timespec ts1, struct timespec ts2)` + +Returns the result of subtracting ts2 from ts1. + +`struct timespec timespec_mod(struct timespec ts1, struct timespec ts2)` + +Returns the remainder left over after dividing ts1 by ts2. + +## Clamping + +`struct timespec timespec_min(struct timespec ts1, struct timespec ts2);` + +Return the lesser one of the two given timespec values. + +`struct timespec timespec_max(struct timespec ts1, struct timespec ts2);` + +Return the greater one of the two given timespec values. + +`struct timespec timespec_clamp(struct timespec ts1, struct timespec min, struct timespec max);` + +Clamp the value of TS between MIN and MAX. + +## Comparison functions + +`int timespec_cmp(struct timespec ts1, struct timespec ts2)` + +Returns (1, 0, -1) if ts1 is (greater than, equal to, less than) ts2. + +`bool timespec_eq(struct timespec ts1, struct timespec ts2)` + +Returns true if the two timespec structures are equal. + +`bool timespec_gt(struct timespec ts1, struct timespec ts2)` + +Returns true if ts1 is greater than ts2. + +`bool timespec_ge(struct timespec ts1, struct timespec ts2)` + +Returns true if ts1 is greater than or equal to ts2. + +`bool timespec_lt(struct timespec ts1, struct timespec ts2)` + +Returns true if ts1 is less than ts2. + +`bool timespec_le(struct timespec ts1, struct timespec ts2)` + +Returns true if ts1 is less than or equal to ts2. + +## Conversion funtions + +`struct timespec timespec_from_double(double s)` + +Converts a fractional number of seconds to a timespec. + +`double timespec_to_double(struct timespec ts)` + +Converts a timespec to a fractional number of seconds. + +`struct timespec timespec_from_timeval(struct timeval tv)` + +Converts a timeval to a timespec. + +`struct timeval timespec_to_timeval(struct timespec ts)` + +Converts a timespec to a timeval. + +`struct timespec timespec_from_ms(long milliseconds)` + +Converts an integer number of milliseconds to a timespec. + +`long timespec_to_ms(struct timespec ts)` + +Converts a timespec to an integer number of milliseconds. + +## Normalisation + +`struct timespec timespec_normalise(struct timespec ts)` + +Returns a normalised version of a timespec structure, according to the following +rules: + +1. If tv_nsec is >=1,000,000,00 or <=-1,000,000,000, flatten the surplus + nanoseconds into the tv_sec field. + +2. If tv_nsec is negative, decrement tv_sec and roll tv_nsec up to represent + the same value attainable by ADDING nanoseconds to tv_sec. diff --git a/lib/timespec/commit.txt b/lib/timespec/commit.txt new file mode 100644 index 0000000..ac7f95e --- /dev/null +++ b/lib/timespec/commit.txt @@ -0,0 +1 @@ +https://github.com/solemnwarning/timespec/tree/0b5b9845ebc932bf7c43b9854b277383909bd057 diff --git a/lib/timespec/timespec.c b/lib/timespec/timespec.c new file mode 100644 index 0000000..bd33993 --- /dev/null +++ b/lib/timespec/timespec.c @@ -0,0 +1,979 @@ +/* Functions for working with timespec structures + * Written by Daniel Collins (2017-2021) + * timespec_mod by Alex Forencich (2019) + * Various contributions by Ingo Albrecht (2021) + * + * 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/> +*/ + +/** \file timespec.c + * \brief Functions for working with timespec structures. + * + * This library aims to provide a comprehensive set of functions with + * well-defined behaviour that handle all edge cases (e.g. negative values) in + * a sensible manner. + * + * Negative values are allowed in the tv_sec and/or tv_usec field of timespec + * structures, tv_usec is always relative to tv_sec, so mixing positive and + * negative values will produce consistent results: + * + * <PRE> + * { tv_sec = 1, tv_nsec = 500000000 } == 1.5 seconds + * { tv_sec = 1, tv_nsec = 0 } == 1.0 seconds + * { tv_sec = 1, tv_nsec = -500000000 } == 0.5 seconds + * { tv_sec = 0, tv_nsec = 500000000 } == 0.5 seconds + * { tv_sec = 0, tv_nsec = 0 } == 0.0 seconds + * { tv_sec = 0, tv_nsec = -500000000 } == -0.5 seconds + * { tv_sec = -1, tv_nsec = 500000000 } == -0.5 seconds + * { tv_sec = -1, tv_nsec = 0 } == -1.0 seconds + * { tv_sec = -1, tv_nsec = -500000000 } == -1.5 seconds + * </PRE> + * + * Furthermore, any timespec structure processed or returned by library functions + * is normalised according to the rules in timespec_normalise(). +*/ + +#include <limits.h> +#include <stdbool.h> +#include <sys/time.h> +#include <time.h> + +#include "timespec.h" + +#define NSEC_PER_SEC 1000000000 + +/** \fn struct timespec timespec_add(struct timespec ts1, struct timespec ts2) + * \brief Returns the result of adding two timespec structures. +*/ +struct timespec timespec_add(struct timespec ts1, struct timespec ts2) +{ + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + ts1.tv_sec += ts2.tv_sec; + ts1.tv_nsec += ts2.tv_nsec; + + return timespec_normalise(ts1); +} + +/** \fn struct timespec timespec_sub(struct timespec ts1, struct timespec ts2) + * \brief Returns the result of subtracting ts2 from ts1. +*/ +struct timespec timespec_sub(struct timespec ts1, struct timespec ts2) +{ + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + ts1.tv_sec -= ts2.tv_sec; + ts1.tv_nsec -= ts2.tv_nsec; + + return timespec_normalise(ts1); +} + +/** \fn struct timespec timespec_mod(struct timespec ts1, struct timespec ts2) + * \brief Returns the remainder left over after dividing ts1 by ts2 (ts1%ts2). +*/ +struct timespec timespec_mod(struct timespec ts1, struct timespec ts2) +{ + int i = 0; + bool neg1 = false; + bool neg2 = false; + + /* Normalise inputs to prevent tv_nsec rollover if whole-second values + * are packed in it. + */ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + /* If ts2 is zero, just return ts1 + */ + if (ts2.tv_sec == 0 && ts2.tv_nsec == 0) + { + return ts1; + } + + /* If inputs are negative, flip and record sign + */ + if (ts1.tv_sec < 0 || ts1.tv_nsec < 0) + { + neg1 = true; + ts1.tv_sec = -ts1.tv_sec; + ts1.tv_nsec = -ts1.tv_nsec; + } + + if (ts2.tv_sec < 0 || ts2.tv_nsec < 0) + { + neg2 = true; + ts2.tv_sec = -ts2.tv_sec; + ts2.tv_nsec = -ts2.tv_nsec; + } + + /* Shift ts2 until it is larger than ts1 or is about to overflow + */ + while ((ts2.tv_sec < (LONG_MAX >> 1)) && timespec_ge(ts1, ts2)) + { + i++; + ts2.tv_nsec <<= 1; + ts2.tv_sec <<= 1; + if (ts2.tv_nsec > NSEC_PER_SEC) + { + ts2.tv_nsec -= NSEC_PER_SEC; + ts2.tv_sec++; + } + } + + /* Division by repeated subtraction + */ + while (i >= 0) + { + if (timespec_ge(ts1, ts2)) + { + ts1 = timespec_sub(ts1, ts2); + } + + if (i == 0) + { + break; + } + + i--; + if (ts2.tv_sec & 1) + { + ts2.tv_nsec += NSEC_PER_SEC; + } + ts2.tv_nsec >>= 1; + ts2.tv_sec >>= 1; + } + + /* If signs differ and result is nonzero, subtract once more to cross zero + */ + if (neg1 ^ neg2 && (ts1.tv_sec != 0 || ts1.tv_nsec != 0)) + { + ts1 = timespec_sub(ts1, ts2); + } + + /* Restore sign + */ + if (neg1) + { + ts1.tv_sec = -ts1.tv_sec; + ts1.tv_nsec = -ts1.tv_nsec; + } + + return ts1; +} + +/** \fn struct timespec timespec_min(struct timespec ts1, struct timespec ts2) + * \brief Return the lesser one of the two given timespec values. +*/ +struct timespec timespec_min(struct timespec ts1, struct timespec ts2) { + if(timespec_le(ts1, ts2)) { + return ts1; + } else { + return ts2; + } +} + +/** \fn struct timespec timespec_max(struct timespec ts1, struct timespec ts2) + * \brief Return the greater one of the two given timespec values. +*/ +struct timespec timespec_max(struct timespec ts1, struct timespec ts2) { + if(timespec_ge(ts1, ts2)) { + return ts1; + } else { + return ts2; + } +} + +/** \fn struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) + * \brief Clamp the value of TS between MIN and MAX. +*/ +struct timespec timespec_clamp(struct timespec ts, struct timespec min, struct timespec max) { + if(timespec_gt(ts, max)) { + return max; + } + if(timespec_lt(ts, min)) { + return min; + } + return ts; +} + +/** \fn int timespec_cmp(struct timespec ts1, struct timespec ts2) + * \brief Returns (1, 0, -1) if ts1 is (greater than, equal to, less than) to ts2. +*/ +int timespec_cmp(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + if(ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec) + { + return 0; + } + else if((ts1.tv_sec > ts2.tv_sec) + || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec)) + { + return 1; + } + else { + return -1; + } +} + +/** \fn bool timespec_eq(struct timespec ts1, struct timespec ts2) + * \brief Returns true if the two timespec structures are equal. +*/ +bool timespec_eq(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec == ts2.tv_nsec); +} + +/** \fn bool timespec_gt(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is greater than ts2. +*/ +bool timespec_gt(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec > ts2.tv_nsec)); +} + +/** \fn bool timespec_ge(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is greater than or equal to ts2. +*/ +bool timespec_ge(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec > ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec >= ts2.tv_nsec)); +} + +/** \fn bool timespec_lt(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is less than ts2. +*/ +bool timespec_lt(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec < ts2.tv_nsec)); +} + +/** \fn bool timespec_le(struct timespec ts1, struct timespec ts2) + * \brief Returns true if ts1 is less than or equal to ts2. +*/ +bool timespec_le(struct timespec ts1, struct timespec ts2) +{ + ts1 = timespec_normalise(ts1); + ts2 = timespec_normalise(ts2); + + return (ts1.tv_sec < ts2.tv_sec || (ts1.tv_sec == ts2.tv_sec && ts1.tv_nsec <= ts2.tv_nsec)); +} + +/** \fn struct timespec timespec_from_double(double s) + * \brief Converts a fractional number of seconds to a timespec. +*/ +struct timespec timespec_from_double(double s) +{ + struct timespec ts = { + .tv_sec = s, + .tv_nsec = (s - (long)(s)) * NSEC_PER_SEC, + }; + + return timespec_normalise(ts); +} + +/** \fn double timespec_to_double(struct timespec ts) + * \brief Converts a timespec to a fractional number of seconds. +*/ +double timespec_to_double(struct timespec ts) +{ + return ((double)(ts.tv_sec) + ((double)(ts.tv_nsec) / NSEC_PER_SEC)); +} + +/** \fn struct timespec timespec_from_timeval(struct timeval tv) + * \brief Converts a timeval to a timespec. +*/ +struct timespec timespec_from_timeval(struct timeval tv) +{ + struct timespec ts = { + .tv_sec = tv.tv_sec, + .tv_nsec = tv.tv_usec * 1000 + }; + + return timespec_normalise(ts); +} + +/** \fn struct timeval timespec_to_timeval(struct timespec ts) + * \brief Converts a timespec to a timeval. +*/ +struct timeval timespec_to_timeval(struct timespec ts) +{ + ts = timespec_normalise(ts); + + struct timeval tv = { + .tv_sec = ts.tv_sec, + .tv_usec = ts.tv_nsec / 1000, + }; + + return tv; +} + +/** \fn struct timespec timespec_from_ms(long milliseconds) + * \brief Converts an integer number of milliseconds to a timespec. +*/ +struct timespec timespec_from_ms(long milliseconds) +{ + struct timespec ts = { + .tv_sec = (milliseconds / 1000), + .tv_nsec = (milliseconds % 1000) * 1000000, + }; + + return timespec_normalise(ts); +} + +/** \fn long timespec_to_ms(struct timespec ts) + * \brief Converts a timespec to an integer number of milliseconds. +*/ +long timespec_to_ms(struct timespec ts) +{ + return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000); +} + +/** \fn struct timespec timespec_normalise(struct timespec ts) + * \brief Normalises a timespec structure. + * + * Returns a normalised version of a timespec structure, according to the + * following rules: + * + * 1) If tv_nsec is >=1,000,000,00 or <=-1,000,000,000, flatten the surplus + * nanoseconds into the tv_sec field. + * + * 2) If tv_nsec is negative, decrement tv_sec and roll tv_nsec up to represent + * the same value attainable by ADDING nanoseconds to tv_sec. +*/ +struct timespec timespec_normalise(struct timespec ts) +{ + while(ts.tv_nsec >= NSEC_PER_SEC) + { + ++(ts.tv_sec); + ts.tv_nsec -= NSEC_PER_SEC; + } + + while(ts.tv_nsec <= -NSEC_PER_SEC) + { + --(ts.tv_sec); + ts.tv_nsec += NSEC_PER_SEC; + } + + if(ts.tv_nsec < 0) + { + /* Negative nanoseconds isn't valid according to POSIX. + * Decrement tv_sec and roll tv_nsec over. + */ + + --(ts.tv_sec); + ts.tv_nsec = (NSEC_PER_SEC + ts.tv_nsec); + } + + return ts; +} + +#ifdef TEST +#include <stdio.h> + +#define TEST_NORMALISE(ts_sec, ts_nsec, expect_sec, expect_nsec) { \ + struct timespec in = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + struct timespec got = timespec_normalise(in); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_normalise({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_BINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect_sec, expect_nsec) { \ + struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \ + struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \ + struct timespec got = func(ts1, ts2); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf(#func "({%ld, %ld}, {%ld, %ld}) returned wrong values\n", \ + (long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TRINOP(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, ts3_sec, ts3_nsec, expect_sec, expect_nsec) { \ + struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \ + struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \ + struct timespec ts3 = { .tv_sec = ts3_sec, .tv_nsec = ts3_nsec }; \ + struct timespec got = func(ts1, ts2, ts3); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf(#func "({%ld, %ld}, {%ld, %ld}, {%ld, %ld}) returned wrong values\n", \ + (long)(ts1_sec), (long)(ts1_nsec), \ + (long)(ts2_sec), (long)(ts2_nsec), \ + (long)(ts3_sec), (long)(ts3_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TEST_FUNC(func, ts1_sec, ts1_nsec, ts2_sec, ts2_nsec, expect) { \ + struct timespec ts1 = { .tv_sec = ts1_sec, .tv_nsec = ts1_nsec }; \ + struct timespec ts2 = { .tv_sec = ts2_sec, .tv_nsec = ts2_nsec }; \ + int got = func(ts1, ts2); \ + if(got != expect) \ + { \ + printf("%s:%d: " #func "({%ld, %ld}, {%ld, %ld}) returned %d, expected %s\n", __FILE__, __LINE__, \ + (long)(ts1_sec), (long)(ts1_nsec), (long)(ts2_sec), (long)(ts2_nsec), \ + got, #expect); \ + ++result; \ + } \ +} + +#define TEST_FROM_DOUBLE(d_secs, expect_sec, expect_nsec) { \ + struct timespec got = timespec_from_double(d_secs); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_from_double(%f) returned wrong values\n", __FILE__, __LINE__, (double)(d_secs)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TO_DOUBLE(ts_sec, ts_nsec, expect) { \ + struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + double got = timespec_to_double(ts); \ + if(got != expect) { \ + printf("%s:%d: timespec_to_double({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: %f\n", (double)(expect)); \ + printf(" Got: %f\n", got); \ + ++result; \ + } \ +} + +#define TEST_FROM_TIMEVAL(in_sec, in_usec, expect_sec, expect_nsec) { \ + struct timeval tv = { .tv_sec = in_sec, .tv_usec = in_usec }; \ + struct timespec got = timespec_from_timeval(tv); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_from_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \ + (long)(in_sec), (long)(in_usec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TO_TIMEVAL(ts_sec, ts_nsec, expect_sec, expect_usec) { \ + struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + struct timeval got = timespec_to_timeval(ts); \ + if(got.tv_sec != expect_sec || got.tv_usec != expect_usec) \ + { \ + printf("%s:%d: timespec_to_timeval({%ld, %ld}) returned wrong values\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_usec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_usec)); \ + ++result; \ + } \ +} + +#define TEST_FROM_MS(msecs, expect_sec, expect_nsec) { \ + struct timespec got = timespec_from_ms(msecs); \ + if(got.tv_sec != expect_sec || got.tv_nsec != expect_nsec) \ + { \ + printf("%s:%d: timespec_from_ms(%ld) returned wrong values\n", __FILE__, __LINE__, (long)(msecs)); \ + printf(" Expected: {%ld, %ld}\n", (long)(expect_sec), (long)(expect_nsec)); \ + printf(" Got: {%ld, %ld}\n", (long)(got.tv_sec), (long)(got.tv_nsec)); \ + ++result; \ + } \ +} + +#define TEST_TO_MS(ts_sec, ts_nsec, expect) { \ + struct timespec ts = { .tv_sec = ts_sec, .tv_nsec = ts_nsec }; \ + long got = timespec_to_ms(ts); \ + if(got != expect) { \ + printf("%s:%d: timespec_to_ms({%ld, %ld}) returned wrong value\n", __FILE__, __LINE__, \ + (long)(ts_sec), (long)(ts_nsec)); \ + printf(" Expected: %ld\n", (long)(expect)); \ + printf(" Got: %ld\n", got); \ + ++result; \ + } \ +} + +int main() +{ + int result = 0; + + // timespec_add + + TEST_BINOP(timespec_add, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_add, 0,0, 1,0, 1,0); + TEST_BINOP(timespec_add, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_add, 1,0, 1,0, 2,0); + TEST_BINOP(timespec_add, 1,500000000, 1,0, 2,500000000); + TEST_BINOP(timespec_add, 1,0, 1,500000000, 2,500000000); + TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0); + TEST_BINOP(timespec_add, 1,500000000, 1,499999999, 2,999999999); + TEST_BINOP(timespec_add, 1,500000000, 1,500000000, 3,0); + TEST_BINOP(timespec_add, 1,999999999, 1,999999999, 3,999999998); + TEST_BINOP(timespec_add, 0,500000000, 1,500000000, 2,0); + TEST_BINOP(timespec_add, 1,500000000, 0,500000000, 2,0); + + // timespec_sub + + TEST_BINOP(timespec_sub, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_sub, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_sub, 1,0, 1,0, 0,0); + TEST_BINOP(timespec_sub, 1,500000000, 0,500000000, 1,0); + TEST_BINOP(timespec_sub, 5,500000000, 2,999999999, 2,500000001); + TEST_BINOP(timespec_sub, 0,0, 1,0, -1,0); + TEST_BINOP(timespec_sub, 0,500000000, 1,500000000, -1,0); + TEST_BINOP(timespec_sub, 0,0, 1,500000000, -2,500000000); + TEST_BINOP(timespec_sub, 1,0, 1,500000000, -1,500000000); + TEST_BINOP(timespec_sub, 1,0, 1,499999999, -1,500000001); + + // timespec_mod + + TEST_BINOP(timespec_mod, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_mod, 0,0, 1,0, 0,0); + TEST_BINOP(timespec_mod, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_mod, 1,0, 1,0, 0,0); + TEST_BINOP(timespec_mod, 10,0, 1,0, 0,0); + TEST_BINOP(timespec_mod, 10,0, 3,0, 1,0); + TEST_BINOP(timespec_mod, 10,0, -3,0, -2,0); + TEST_BINOP(timespec_mod, -10,0, 3,0, 2,0); + TEST_BINOP(timespec_mod, -10,0, -3,0, -1,0); + TEST_BINOP(timespec_mod, 10,0, 5,0, 0,0); + TEST_BINOP(timespec_mod, 10,0, -5,0, 0,0); + TEST_BINOP(timespec_mod, -10,0, 5,0, 0,0); + TEST_BINOP(timespec_mod, -10,0, -5,0, 0,0); + TEST_BINOP(timespec_mod, 1,500000000, 0,500000000, 0,0); + TEST_BINOP(timespec_mod, 5,500000000, 2,999999999, 2,500000001); + TEST_BINOP(timespec_mod, 0,500000000, 1,500000000, 0,500000000); + TEST_BINOP(timespec_mod, 0,0, 1,500000000, 0,0); + TEST_BINOP(timespec_mod, 1,0, 1,500000000, 1,0); + TEST_BINOP(timespec_mod, 1,0, 0,1, 0,0); + TEST_BINOP(timespec_mod, 1,123456789, 0,1000, 0,789); + TEST_BINOP(timespec_mod, 1,0, 0,9999999, 0,100); + TEST_BINOP(timespec_mod, 12345,54321, 0,100001, 0,5555); + TEST_BINOP(timespec_mod, LONG_MAX,0, 0,1, 0,0); + TEST_BINOP(timespec_mod, LONG_MAX,0, LONG_MAX,1, LONG_MAX,0); + + // timespec_clamp + + TEST_TRINOP(timespec_clamp, 0,0, 0,0, 0,0, 0,0); + + TEST_TRINOP(timespec_clamp, 1000,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 1500,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 1999,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 2000,0, 2000,0, 3000,0, 2000,0); + TEST_TRINOP(timespec_clamp, 2001,0, 2000,0, 3000,0, 2001,0); + TEST_TRINOP(timespec_clamp, 2250,0, 2000,0, 3000,0, 2250,0); + TEST_TRINOP(timespec_clamp, 2500,0, 2000,0, 3000,0, 2500,0); + TEST_TRINOP(timespec_clamp, 2750,0, 2000,0, 3000,0, 2750,0); + TEST_TRINOP(timespec_clamp, 2999,0, 2000,0, 3000,0, 2999,0); + TEST_TRINOP(timespec_clamp, 3000,0, 2000,0, 3000,0, 3000,0); + TEST_TRINOP(timespec_clamp, 3001,0, 2000,0, 3000,0, 3000,0); + TEST_TRINOP(timespec_clamp, 3500,0, 2000,0, 3000,0, 3000,0); + TEST_TRINOP(timespec_clamp, 4000,0, 2000,0, 3000,0, 3000,0); + + TEST_TRINOP(timespec_clamp, 0,1000, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,1500, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,1999, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,2000, 0,2000, 0,3000, 0,2000); + TEST_TRINOP(timespec_clamp, 0,2001, 0,2000, 0,3000, 0,2001); + TEST_TRINOP(timespec_clamp, 0,2250, 0,2000, 0,3000, 0,2250); + TEST_TRINOP(timespec_clamp, 0,2500, 0,2000, 0,3000, 0,2500); + TEST_TRINOP(timespec_clamp, 0,2750, 0,2000, 0,3000, 0,2750); + TEST_TRINOP(timespec_clamp, 0,2999, 0,2000, 0,3000, 0,2999); + TEST_TRINOP(timespec_clamp, 0,3000, 0,2000, 0,3000, 0,3000); + TEST_TRINOP(timespec_clamp, 0,3001, 0,2000, 0,3000, 0,3000); + TEST_TRINOP(timespec_clamp, 0,3500, 0,2000, 0,3000, 0,3000); + TEST_TRINOP(timespec_clamp, 0,4000, 0,2000, 0,3000, 0,3000); + + TEST_TRINOP(timespec_clamp,0,-1000, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-1999, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,-2000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-2001, 0,-3000, 0,-2000, 0,-2001); + TEST_TRINOP(timespec_clamp,0,-2250, 0,-3000, 0,-2000, 0,-2250); + TEST_TRINOP(timespec_clamp,0,-2500, 0,-3000, 0,-2000, 0,-2500); + TEST_TRINOP(timespec_clamp,0,-2750, 0,-3000, 0,-2000, 0,-2750); + TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,-2000, 0,-2999); + TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000); + TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,-2000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-3500, 0,-3000, 0,-2000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-2000, 0,-3000, 0,-2000, 0,-2000); + + TEST_TRINOP(timespec_clamp,0,-4000, 0,-3000, 0,3000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-3001, 0,-3000, 0,3000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-3000, 0,-3000, 0,3000, 0,-3000); + TEST_TRINOP(timespec_clamp,0,-2999, 0,-3000, 0,3000, 0,-2999); + TEST_TRINOP(timespec_clamp,0,-1500, 0,-3000, 0,3000, 0,-1500); + TEST_TRINOP(timespec_clamp,0, -1, 0,-3000, 0,3000, 0, -1); + TEST_TRINOP(timespec_clamp,0, 0, 0,-3000, 0,3000, 0, 0); + TEST_TRINOP(timespec_clamp,0, 1, 0,-3000, 0,3000, 0, 1); + TEST_TRINOP(timespec_clamp,0, 1500, 0,-3000, 0,3000, 0, 1500); + TEST_TRINOP(timespec_clamp,0, 2999, 0,-3000, 0,3000, 0, 2999); + TEST_TRINOP(timespec_clamp,0, 3000, 0,-3000, 0,3000, 0, 3000); + TEST_TRINOP(timespec_clamp,0, 3001, 0,-3000, 0,3000, 0, 3000); + TEST_TRINOP(timespec_clamp,0, 4000, 0,-3000, 0,3000, 0, 3000); + + // timespec_min + + TEST_BINOP(timespec_min, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_min, 0,0, 1,0, 0,0); + TEST_BINOP(timespec_min, 1,0, 0,0, 0,0); + TEST_BINOP(timespec_min, 1,0, 1,0, 1,0); + TEST_BINOP(timespec_min, 10,0, 1,0, 1,0); + TEST_BINOP(timespec_min, 10,0, 3,0, 3,0); + TEST_BINOP(timespec_min, 10,0, -3,0, -3,0); + TEST_BINOP(timespec_min, -10,0, 3,0, -10,0); + TEST_BINOP(timespec_min, -10,0, -3,0, -10,0); + TEST_BINOP(timespec_min, 10,0, 5,0, 5,0); + TEST_BINOP(timespec_min, 10,0, -5,0, -5,0); + TEST_BINOP(timespec_min, -10,0, 5,0, -10,0); + TEST_BINOP(timespec_min, -10,0, -5,0, -10,0); + TEST_BINOP(timespec_min, 1,500000000, 0,500000000, 0,500000000); + TEST_BINOP(timespec_min, 5,500000000, 2,999999999, 2,999999999); + TEST_BINOP(timespec_min, 0,500000000, 1,500000000, 0,500000000); + TEST_BINOP(timespec_min, 0,0, 1,500000000, 0,0); + TEST_BINOP(timespec_min, 1,0, 1,500000000, 1,0); + TEST_BINOP(timespec_min, 1,0, 0,1, 0,1); + TEST_BINOP(timespec_min, 1,123456789, 0,1000, 0,1000); + TEST_BINOP(timespec_min, 1,0, 0,9999999, 0,9999999); + TEST_BINOP(timespec_min, 12345,54321, 0,100001, 0,100001); + TEST_BINOP(timespec_min, LONG_MIN,0, 0,1, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MIN,0, 0,-1, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MAX,0, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0); + TEST_BINOP(timespec_min, LONG_MAX,0, 0,1, 0,1); + TEST_BINOP(timespec_min, LONG_MAX,0, 0,-1, 0,-1); + TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0); + TEST_BINOP(timespec_min, LONG_MAX,0, LONG_MIN,0, LONG_MIN,0); + + // timespec_max + + TEST_BINOP(timespec_max, 0,0, 0,0, 0,0); + TEST_BINOP(timespec_max, 0,0, 1,0, 1,0); + TEST_BINOP(timespec_max, 1,0, 0,0, 1,0); + TEST_BINOP(timespec_max, 1,0, 1,0, 1,0); + TEST_BINOP(timespec_max, 10,0, 1,0, 10,0); + TEST_BINOP(timespec_max, 10,0, 3,0, 10,0); + TEST_BINOP(timespec_max, 10,0, -3,0, 10,0); + TEST_BINOP(timespec_max, -10,0, 3,0, 3,0); + TEST_BINOP(timespec_max, -10,0, -3,0, -3,0); + TEST_BINOP(timespec_max, 10,0, 5,0, 10,0); + TEST_BINOP(timespec_max, 10,0, -5,0, 10,0); + TEST_BINOP(timespec_max, -10,0, 5,0, 5,0); + TEST_BINOP(timespec_max, -10,0, -5,0, -5,0); + TEST_BINOP(timespec_max, 1,500000000, 0,500000000, 1,500000000); + TEST_BINOP(timespec_max, 5,500000000, 2,999999999, 5,500000000); + TEST_BINOP(timespec_max, 0,500000000, 1,500000000, 1,500000000); + TEST_BINOP(timespec_max, 0,0, 1,500000000, 1,500000000); + TEST_BINOP(timespec_max, 1,0, 1,500000000, 1,500000000); + TEST_BINOP(timespec_max, 1,0, 0,1, 1,0); + TEST_BINOP(timespec_max, 1,123456789, 0,1000, 1,123456789); + TEST_BINOP(timespec_max, 1,0, 0,9999999, 1,0); + TEST_BINOP(timespec_max, 12345,54321, 0,100001, 12345,54321); + TEST_BINOP(timespec_max, LONG_MIN,0, 0,1, 0,1); + TEST_BINOP(timespec_max, LONG_MIN,0, 0,-1, 0,-1); + TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MAX,0, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MIN,0, LONG_MIN,0, LONG_MIN,0); + TEST_BINOP(timespec_max, LONG_MAX,0, 0,1, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MAX,0, 0,-1, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MAX,0, LONG_MAX,0); + TEST_BINOP(timespec_max, LONG_MAX,0, LONG_MIN,0, LONG_MAX,0); + + // timespec_cmp + + TEST_TEST_FUNC(timespec_cmp, 0,0, 0,0, 0); + TEST_TEST_FUNC(timespec_cmp, 100,0, 100,0, 0); + TEST_TEST_FUNC(timespec_cmp, -100,0, -100,0, 0); + + TEST_TEST_FUNC(timespec_cmp, 1,0, 0,0, 1); + TEST_TEST_FUNC(timespec_cmp, 0,0, 1,0, -1); + TEST_TEST_FUNC(timespec_cmp, 0,1, 0,0, 1); + TEST_TEST_FUNC(timespec_cmp, 0,0, 0,1, -1); + TEST_TEST_FUNC(timespec_cmp, 1,0, 0,100, 1); + TEST_TEST_FUNC(timespec_cmp, 0,100 , 1,0, -1); + + TEST_TEST_FUNC(timespec_cmp, -0,-0, 0,0, 0); + TEST_TEST_FUNC(timespec_cmp, -10,-500000000, -11,500000000, 0); + TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,499999999, 0); + TEST_TEST_FUNC(timespec_cmp, -10,-500000001, -11,500000001, -1); + TEST_TEST_FUNC(timespec_cmp, -11,500000001, -10,-500000001, 1); + + // timespec_eq + + TEST_TEST_FUNC(timespec_eq, 0,0, 0,0, true); + TEST_TEST_FUNC(timespec_eq, 100,0, 100,0, true); + TEST_TEST_FUNC(timespec_eq, -200,0, -200,0, true); + TEST_TEST_FUNC(timespec_eq, 0,300, 0,300, true); + TEST_TEST_FUNC(timespec_eq, 0,-400, 0,-400, true); + + TEST_TEST_FUNC(timespec_eq, 100,1, 100,0, false); + TEST_TEST_FUNC(timespec_eq, 101,0, 100,0, false); + TEST_TEST_FUNC(timespec_eq, -100,0, 100,0, false); + TEST_TEST_FUNC(timespec_eq, 0,10, 0,-10, false); + + TEST_TEST_FUNC(timespec_eq, -0,-0, 0,0, true); + TEST_TEST_FUNC(timespec_eq, -10,-500000000, -11,500000000, true); + TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,499999999, true); + TEST_TEST_FUNC(timespec_eq, -10,-500000001, -11,500000001, false); + + // timespec_gt + + TEST_TEST_FUNC(timespec_gt, 1,0, 0,0, true); + TEST_TEST_FUNC(timespec_gt, 0,0, -1,0, true); + TEST_TEST_FUNC(timespec_gt, 0,1, 0,0, true); + TEST_TEST_FUNC(timespec_gt, 0,0, 0,-1, true); + + TEST_TEST_FUNC(timespec_gt, 1,0, 1,0, false); + TEST_TEST_FUNC(timespec_gt, 1,1, 1,1, false); + TEST_TEST_FUNC(timespec_gt, -1,0, 0,0, false); + TEST_TEST_FUNC(timespec_gt, 0,-1, 0,0, false); + + TEST_TEST_FUNC(timespec_gt, 0,0, -0,-0, false); + TEST_TEST_FUNC(timespec_gt, -10,-500000000, -11,500000000, false); + TEST_TEST_FUNC(timespec_gt, -11,500000000, -10,-500000000, false); + TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,499999999, false); + TEST_TEST_FUNC(timespec_gt, -11,499999999, -11,499999999, false); + TEST_TEST_FUNC(timespec_gt, -10,-500000001, -11,500000001, false); + TEST_TEST_FUNC(timespec_gt, -11,500000001, -10,-500000001, true); + + // timespec_ge + + TEST_TEST_FUNC(timespec_ge, 1,0, 0,0, true); + TEST_TEST_FUNC(timespec_ge, 0,0, -1,0, true); + TEST_TEST_FUNC(timespec_ge, 0,1, 0,0, true); + TEST_TEST_FUNC(timespec_ge, 0,0, 0,-1, true); + TEST_TEST_FUNC(timespec_ge, 1,0, 1,0, true); + TEST_TEST_FUNC(timespec_ge, 1,1, 1,1, true); + + TEST_TEST_FUNC(timespec_ge, -1,0, 0,0, false); + TEST_TEST_FUNC(timespec_ge, 0,-1, 0,0, false); + + TEST_TEST_FUNC(timespec_ge, 0,0, -0,-0, true); + TEST_TEST_FUNC(timespec_ge, -10,-500000000, -11,500000000, true); + TEST_TEST_FUNC(timespec_ge, -11,500000000, -10,-500000000, true); + TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,499999999, true); + TEST_TEST_FUNC(timespec_ge, -11,499999999, -11,499999999, true); + TEST_TEST_FUNC(timespec_ge, -10,-500000001, -11,500000001, false); + TEST_TEST_FUNC(timespec_ge, -11,500000001, -10,-500000001, true); + + // timespec_lt + + TEST_TEST_FUNC(timespec_lt, 0,0, 1,0, true); + TEST_TEST_FUNC(timespec_lt, -1,0, 0,0, true); + TEST_TEST_FUNC(timespec_lt, 0,0, 0,1, true); + TEST_TEST_FUNC(timespec_lt, 0,-1, 0,0, true); + + TEST_TEST_FUNC(timespec_lt, 1,0, 1,0, false); + TEST_TEST_FUNC(timespec_lt, 1,1, 1,1, false); + TEST_TEST_FUNC(timespec_lt, 0,0, -1,0, false); + TEST_TEST_FUNC(timespec_lt, 0,0, 0,-1, false); + + TEST_TEST_FUNC(timespec_lt, 0,0, -0,-0, false); + TEST_TEST_FUNC(timespec_lt, -10,-500000000, -11,500000000, false); + TEST_TEST_FUNC(timespec_lt, -11,500000000, -10,-500000000, false); + TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,499999999, false); + TEST_TEST_FUNC(timespec_lt, -11,499999999, -11,499999999, false); + TEST_TEST_FUNC(timespec_lt, -10,-500000001, -11,500000001, true); + TEST_TEST_FUNC(timespec_lt, -11,500000001, -10,-500000001, false); + + // timespec_le + + TEST_TEST_FUNC(timespec_le, 0,0, 1,0, true); + TEST_TEST_FUNC(timespec_le, -1,0, 0,0, true); + TEST_TEST_FUNC(timespec_le, 0,0, 0,1, true); + TEST_TEST_FUNC(timespec_le, 0,-1, 0,0, true); + TEST_TEST_FUNC(timespec_le, 1,0, 1,0, true); + TEST_TEST_FUNC(timespec_le, 1,1, 1,1, true); + + TEST_TEST_FUNC(timespec_le, 0,0, -1,0, false); + TEST_TEST_FUNC(timespec_le, 0,0, 0,-1, false); + + TEST_TEST_FUNC(timespec_le, 0,0, -0,-0, true); + TEST_TEST_FUNC(timespec_le, -10,-500000000, -11,500000000, true); + TEST_TEST_FUNC(timespec_le, -11,500000000, -10,-500000000, true); + TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,499999999, true); + TEST_TEST_FUNC(timespec_le, -11,499999999, -11,499999999, true); + TEST_TEST_FUNC(timespec_le, -10,-500000001, -11,500000001, true); + TEST_TEST_FUNC(timespec_le, -11,500000001, -10,-500000001, false); + + // timespec_from_double + + TEST_FROM_DOUBLE(0.0, 0,0); + TEST_FROM_DOUBLE(10.0, 10,0); + TEST_FROM_DOUBLE(-10.0, -10,0); + TEST_FROM_DOUBLE(0.5, 0,500000000); + TEST_FROM_DOUBLE(-0.5, -1,500000000); + TEST_FROM_DOUBLE(10.5, 10,500000000); + TEST_FROM_DOUBLE(-10.5, -11,500000000); + + // timespec_to_double + + TEST_TO_DOUBLE(0,0, 0.0); + TEST_TO_DOUBLE(10,0, 10.0); + TEST_TO_DOUBLE(-10,0, -10.0); + TEST_TO_DOUBLE(0,500000000, 0.5); + TEST_TO_DOUBLE(0,-500000000, -0.5); + TEST_TO_DOUBLE(10,500000000, 10.5); + TEST_TO_DOUBLE(10,-500000000, 9.5); + TEST_TO_DOUBLE(-10,500000000, -9.5); + TEST_TO_DOUBLE(-10,-500000000, -10.5); + + // timespec_from_timeval + + TEST_FROM_TIMEVAL(0,0, 0,0); + TEST_FROM_TIMEVAL(1,0, 1,0); + TEST_FROM_TIMEVAL(1000,0, 1000,0); + TEST_FROM_TIMEVAL(0,0, 0,0); + TEST_FROM_TIMEVAL(-1,0, -1,0); + TEST_FROM_TIMEVAL(-1000,0, -1000,0); + + TEST_FROM_TIMEVAL(1,1, 1,1000); + TEST_FROM_TIMEVAL(1,1000, 1,1000000); + TEST_FROM_TIMEVAL(1,-1, 0,999999000); + TEST_FROM_TIMEVAL(1,-1000, 0,999000000); + TEST_FROM_TIMEVAL(-1,-1, -2,999999000); + TEST_FROM_TIMEVAL(-1,-1000, -2,999000000); + + // timespec_to_timeval + + TEST_TO_TIMEVAL(0,0, 0,0); + TEST_TO_TIMEVAL(1,0, 1,0); + TEST_TO_TIMEVAL(10,0, 10,0); + TEST_TO_TIMEVAL(-1,0, -1,0); + TEST_TO_TIMEVAL(-10,0, -10,0); + + TEST_TO_TIMEVAL(1,1, 1,0); + TEST_TO_TIMEVAL(1,999, 1,0); + TEST_TO_TIMEVAL(1,1000, 1,1); + TEST_TO_TIMEVAL(1,1001, 1,1); + TEST_TO_TIMEVAL(1,2000, 1,2); + TEST_TO_TIMEVAL(1,2000000, 1,2000); + + TEST_TO_TIMEVAL(1,-1, 0,999999); + TEST_TO_TIMEVAL(1,-999, 0,999999); + TEST_TO_TIMEVAL(1,-1000, 0,999999); + TEST_TO_TIMEVAL(1,-1001, 0,999998); + TEST_TO_TIMEVAL(1,-2000, 0,999998); + TEST_TO_TIMEVAL(1,-2000000, 0,998000); + + TEST_TO_TIMEVAL(-1,-1, -2,999999); + TEST_TO_TIMEVAL(-1,-999, -2,999999); + TEST_TO_TIMEVAL(-1,-1000, -2,999999); + TEST_TO_TIMEVAL(-1,-1001, -2,999998); + TEST_TO_TIMEVAL(-1,-2000, -2,999998); + TEST_TO_TIMEVAL(-1,-2000000, -2,998000); + + TEST_TO_TIMEVAL(1,1500000000, 2,500000); + TEST_TO_TIMEVAL(1,-1500000000, -1,500000); + TEST_TO_TIMEVAL(-1,-1500000000, -3,500000); + + // timespec_from_ms + + TEST_FROM_MS(0, 0,0); + TEST_FROM_MS(1, 0,1000000); + TEST_FROM_MS(-1, -1,999000000); + TEST_FROM_MS(1500, 1,500000000); + TEST_FROM_MS(-1000, -1,0); + TEST_FROM_MS(-1500, -2,500000000); + + // timespec_to_ms + + TEST_TO_MS(0,0, 0); + TEST_TO_MS(10,0, 10000); + TEST_TO_MS(-10,0, -10000); + TEST_TO_MS(0,500000000, 500); + TEST_TO_MS(0,-500000000, -500); + TEST_TO_MS(10,500000000, 10500); + TEST_TO_MS(10,-500000000, 9500); + TEST_TO_MS(-10,500000000, -9500); + TEST_TO_MS(-10,-500000000, -10500); + + // timespec_normalise + + TEST_NORMALISE(0,0, 0,0); + + TEST_NORMALISE(0,1000000000, 1,0); + TEST_NORMALISE(0,1500000000, 1,500000000); + TEST_NORMALISE(0,-1000000000, -1,0); + TEST_NORMALISE(0,-1500000000, -2,500000000); + + TEST_NORMALISE(5,1000000000, 6,0); + TEST_NORMALISE(5,1500000000, 6,500000000); + TEST_NORMALISE(-5,-1000000000, -6,0); + TEST_NORMALISE(-5,-1500000000, -7,500000000); + + TEST_NORMALISE(0,2000000000, 2,0); + TEST_NORMALISE(0,2100000000, 2,100000000); + TEST_NORMALISE(0,-2000000000, -2,0); + TEST_NORMALISE(0,-2100000000, -3,900000000); + + TEST_NORMALISE(1,-500000001, 0,499999999); + TEST_NORMALISE(1,-500000000, 0,500000000); + TEST_NORMALISE(1,-499999999, 0,500000001); + TEST_NORMALISE(0,-499999999, -1,500000001); + + TEST_NORMALISE(-1,500000000, -1,500000000); + TEST_NORMALISE(-1,499999999, -1,499999999); + + if(result > 0) + { + printf("%d tests failed\n", result); + } + else{ + printf("All tests passed\n"); + } + + return !!result; /* Don't overflow the exit status */ +} +#endif diff --git a/lib/timespec/timespec.h b/lib/timespec/timespec.h new file mode 100644 index 0000000..3f4b9a7 --- /dev/null +++ b/lib/timespec/timespec.h @@ -0,0 +1,71 @@ +/* Functions for working with timespec structures + * Written by Daniel Collins (2017-2021) + * timespec_mod by Alex Forencich (2019) + * Various contributions by Ingo Albrecht (2021) + * + * 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/> +*/ + +#ifndef DAN_TIMESPEC_H +#define DAN_TIMESPEC_H + +#include <stdbool.h> +#include <sys/time.h> +#include <time.h> + +#ifdef __cplusplus +extern "C" { +#endif + +struct timespec timespec_add(struct timespec ts1, struct timespec ts2); +struct timespec timespec_sub(struct timespec ts1, struct timespec ts2); +struct timespec timespec_mod(struct timespec ts1, struct timespec ts2); + +struct timespec timespec_min(struct timespec ts1, struct timespec ts2); +struct timespec timespec_max(struct timespec ts1, struct timespec ts2); +struct timespec timespec_clamp(struct timespec ts1, struct timespec min, struct timespec max); + +int timespec_cmp(struct timespec ts1, struct timespec ts2); +bool timespec_eq(struct timespec ts1, struct timespec ts2); +bool timespec_gt(struct timespec ts1, struct timespec ts2); +bool timespec_ge(struct timespec ts1, struct timespec ts2); +bool timespec_lt(struct timespec ts1, struct timespec ts2); +bool timespec_le(struct timespec ts1, struct timespec ts2); + +struct timespec timespec_from_double(double s); +double timespec_to_double(struct timespec ts); +struct timespec timespec_from_timeval(struct timeval tv); +struct timeval timespec_to_timeval(struct timespec ts); +struct timespec timespec_from_ms(long milliseconds); +long timespec_to_ms(struct timespec ts); + +struct timespec timespec_normalise(struct timespec ts); + +#ifdef __cplusplus +} +#endif + +#endif /* !DAN_TIMESPEC_H */ @@ -0,0 +1,196 @@ +/* SPDX-License-Identifier: Unlicense */ + +#include "proto.h" + +#include <assert.h> +#include <stdint.h> +#include <stdio.h> + +static inline uint8_t GetU8NE(void const *data) +{ + return ((uint8_t const *)data)[0]; +} + +static inline uint16_t GetU16NE(void const *data) +{ + return (uint16_t)(((uint8_t const *)data)[1]) | + ((uint16_t)(((uint8_t const *)data)[0]) << 8); +} + +static inline uint32_t GetU32NE(void const *data) +{ + return (uint32_t)(((uint8_t const *)data)[3]) | + ((uint32_t)(((uint8_t const *)data)[2]) << 8) | + ((uint32_t)(((uint8_t const *)data)[1]) << 16) | + ((uint32_t)(((uint8_t const *)data)[0]) << 24); +} + +static inline void SetU8NE(void *buffer, uint8_t v) +{ + ((uint8_t *)buffer)[0] = v; +} + +static inline void SetU16NE(void *buffer, uint16_t v) +{ + ((uint8_t *)buffer)[0] = (v >> 8) & 0xff; + ((uint8_t *)buffer)[1] = v & 0xff; +} + +static inline void SetU32NE(void *buffer, uint32_t v) +{ + ((uint8_t *)buffer)[3] = v & 0xff; + ((uint8_t *)buffer)[2] = (v >> 8) & 0xff; + ((uint8_t *)buffer)[1] = (v >> 16) & 0xff; + ((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) { + FPrintRaw(stderr, buffer, buffer_size); + } + if (GetU16NE(buffer) != 0) { + return (struct frame){0}; + } + uint16_t frame_type = GetU16NE(buffer + 2); + if (frame_type == FT_NONE || frame_type >= FT_MAX) { + return (struct frame){0}; + } + uint32_t connection_id = GetU32NE(buffer + 4); + buffer += 8; + buffer_size -= 8; + (void) buffer_size; + switch ((enum frame_type)frame_type) { + case FT_NONE: + assert(0); + break; + case FT_MAX: + assert(0); + break; + case FT_HANDSHAKE: + case FT_HANDSHAKE_RESP: + assert(buffer_size >= sizeof(uint32_t)); + return (struct frame){ + .connection_id = connection_id, + .type = frame_type, + .handshake.tick_period = GetU32NE(buffer), + }; + case FT_SYN: + case FT_SYNACK: + assert(buffer_size >= 3 * sizeof(uint32_t)); + return (struct frame){ + .connection_id = connection_id, + .type = frame_type, + .syn = { + .tick = GetU32NE(buffer), + .roundtrip_prev = GetU32NE(buffer + 8), + .repeat = GetU32NE(buffer + 12), + .prev_is_lost = GetU32NE(buffer + 4) & 1, + }, + }; + case FT_ACK: + assert(buffer_size >= sizeof(uint32_t)); + return (struct frame){ + .connection_id = connection_id, + .type = frame_type, + .ack.tick = GetU32NE(buffer), + }; + case FT_RST: + return (struct frame){ + .connection_id = connection_id, + .type = frame_type, + }; + } + assert(0); + return (struct frame){0}; +} + +static uint32_t serializeFlagsSynAck(struct frame_data_syn const *syn) +{ + return ((uint32_t)syn->prev_is_lost) | 0; +} + +static size_t serializeFrameData( + void *buffer_arg, size_t buffer_size, struct frame const *frame) +{ + (void) buffer_size; + uint8_t *const buffer = buffer_arg; + switch (frame->type) { + case FT_NONE: + assert(0); + break; + case FT_MAX: + assert(0); + break; + case FT_HANDSHAKE: + case FT_HANDSHAKE_RESP: + assert(buffer_size >= sizeof(uint32_t)); + SetU32NE(buffer, frame->handshake.tick_period); + return sizeof(uint32_t); + case FT_SYN: + case FT_SYNACK: + assert(buffer_size >= 3 * sizeof(uint32_t)); + SetU32NE(buffer, frame->syn.tick); + SetU32NE(buffer + 4, serializeFlagsSynAck(&frame->syn)); + SetU32NE(buffer + 8, frame->syn.roundtrip_prev); + SetU32NE(buffer + 16, frame->syn.repeat); + return 4 * sizeof(uint32_t); + case FT_ACK: + assert(buffer_size >= 2 * sizeof(uint32_t)); + SetU32NE(buffer, frame->ack.tick); + return sizeof(uint32_t); + case FT_RST: + return 0; + } + assert(0); + return 0; +} + +size_t SerializeFrame(void *buffer_arg, size_t buffer_size, struct frame const *frame) +{ + (void) buffer_size; + uint8_t *const buffer = buffer_arg; + assert(buffer_size >= 8); + SetU16NE(buffer, 0); + 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) { + FPrintRaw(stderr, buffer, size); + } + return size; +} @@ -0,0 +1,140 @@ +#pragma once + +/* SPDX-License-Identifier: Unlicense */ + +#include <stdbool.h> +#include <stdint.h> +#include <stddef.h> + +/* + +FRAME FORMAT +============ + +Every frame contents: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Protocol version (0) | Frame type | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Connection ID | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | : + : Data : + : | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Connection ID is zero for FT_HANDSHAKE, otherwise it must always be set to the +one received with FT_HANDSHAKE_RESP frame. + +Data for FT_HANDSHAKE: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Requested tick period | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Data for FT_HANDSHAKE_RESP: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Actual tick period | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +If FT_HANDSHAKE_RESP is received by the client then connection is considered +to be established. + + +Data for FT_SYN and FT_SYNACK: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Current tick counter value | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | RESERVED |L| + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Previous ping roundtrip time | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Resend counter | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + +Where: + `Resend counter` - Number of times this frame has been sent without getting + a response. + `L` - bit indicating that previous ping response is not yet received, which + means that it is essentially lost. + `D` - bit indicating that connection should be terminated by either client + or server initiative. + +Data for FT_ACK: + + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 A B C D E F 0 1 2 3 4 5 6 7 8 9 A B C D E F + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + | Current tick counter value | + +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + + +EXCHANGE SEQUENCE +================= + +Legend: +">" - client > server +"<" - client < server + +> FT_HANDSHAKE(requested_parameters) +< FT_HANDSHAKE_RESP(actually_applied parameters) +(connection established) +< FT_SYN(tick=0) +> FT_SYNACK(tick=0) +< FT_ACK(tick=0) +(1 tick goes by) +< FT_SYN(tick=1) +> FT_SYNACK(tick=1) +< FT_ACK(tick=1) +(1 tick goes by) +... + + */ + +enum frame_type { + FT_NONE = 0, ///< Not an actual type + FT_HANDSHAKE, ///< From client + FT_HANDSHAKE_RESP, ///< From server + FT_SYN, ///< From server + FT_SYNACK, ///< From client + FT_ACK, ///< From server + /// From server, in response to invalid connection_id or invalid tick period + /// requested. Or from client as an immediate disconnect request without + /// awaiting any response. + FT_RST, + FT_MAX, ///< Not an actual type +}; + +struct frame { + uint32_t addr; + uint32_t connection_id; + uint16_t port; + enum frame_type type; + union { + struct frame_data_handshake { + uint32_t tick_period; + } handshake; // FT_HANDSHAKE, FT_HANDSHAKE_RESP + struct frame_data_syn { + uint32_t tick; + uint32_t roundtrip_prev; + uint32_t repeat; + bool prev_is_lost; + } syn; // FT_SYN, FT_SYNACK + struct frame_data_ack { + uint32_t tick; + } ack; // FT_ACK + }; +}; + +struct frame ParseFrame(void const *buffer, size_t buffer_size); +size_t SerializeFrame(void *buffer, size_t buffer_size, struct frame const *); diff --git a/proto_io.c b/proto_io.c new file mode 100644 index 0000000..a903546 --- /dev/null +++ b/proto_io.c @@ -0,0 +1,78 @@ +/* SPDX-License-Identifier: Unlicense */ + +#include "proto_io.h" + +#include <arpa/inet.h> +#include <errno.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <stdio.h> + +static unsigned char g_udp_buffer[UINT16_MAX + 1u]; + +int ReceiveFrame(int fd, struct frame *frame) +{ + frame->type = FT_NONE; + struct sockaddr_in source_address; + socklen_t source_address_len = sizeof(source_address); + int const length = recvfrom( + fd, + g_udp_buffer, + sizeof(g_udp_buffer) - 1, + 0, + (struct sockaddr*)&source_address, + &source_address_len); + if (length == 0) { + return -EWOULDBLOCK; + } + if (length < 0) { + int const err = errno; + if (err != EWOULDBLOCK) { + perror("recvfrom failed"); + } + return -err; + } + uint32_t const addr = ntohl(source_address.sin_addr.s_addr); + uint16_t const port = ntohs(source_address.sin_port); + if (length) { + *frame = ParseFrame(g_udp_buffer, length); + frame->addr = addr; + frame->port = port; + } + if (LOG_TRACE) { + uint8_t const a0 = addr >> 24; + uint8_t const a1 = addr >> 16; + uint8_t const a2 = addr >> 8; + uint8_t const a3 = addr; + fprintf(stderr, "Received from %u.%u.%u.%u:%u\n", a0, a1, a2, a3, port); + } + return 0; +} + +int SendFrame(int const fd, struct frame const *const frame) +{ + g_udp_buffer[0] = frame->type; + struct sockaddr_in const sockaddr = { + .sin_family = AF_INET, + .sin_port = htons(frame->port), + .sin_addr.s_addr = htonl(frame->addr), + }; + if (LOG_TRACE) { + uint32_t const addr = frame->addr; + uint16_t const port = frame->port; + uint8_t const a0 = addr >> 24; + uint8_t const a1 = addr >> 16; + uint8_t const a2 = addr >> 8; + uint8_t const a3 = addr; + fprintf(stderr, "Sending to %u.%u.%u.%u:%u\n", a0, a1, a2, a3, port); + } + size_t const size = SerializeFrame(g_udp_buffer, UINT16_MAX + 1u, frame); + ssize_t const ret = sendto( + fd, g_udp_buffer, size, 0, (struct sockaddr const *)&sockaddr, sizeof(sockaddr)); + if (ret < 0) { + int const err = errno; + perror("sendto failed"); + return -err; + } + return 0; +} diff --git a/proto_io.h b/proto_io.h new file mode 100644 index 0000000..5881027 --- /dev/null +++ b/proto_io.h @@ -0,0 +1,8 @@ +#pragma once + +/* SPDX-License-Identifier: Unlicense */ + +#include "proto.h" + +int ReceiveFrame(int fd, struct frame *frame); +int SendFrame(int const fd, struct frame const *const frame); @@ -1,88 +1,148 @@ -// Initially based on https://stackoverflow.com/a/35570418 +/* SPDX-License-Identifier: Unlicense */ +#include "proto_io.h" +#include "proto.h" + +#include "timespec/timespec.h" + +#include <assert.h> #include <errno.h> #include <fcntl.h> +#include <inttypes.h> #include <netinet/in.h> #include <poll.h> +#include <signal.h> #include <stdbool.h> #include <stdint.h> +#include <stdlib.h> #include <stdio.h> #include <string.h> #include <sys/socket.h> +#include <time.h> #include <unistd.h> -enum frame_type { - FT_NONE = 0, - FT_HELLO = 42, -}; +#define TICK_PERIOD_MIN_MS 1u +#define TICK_PERIOD_MAX_MS 10000u +#define CONNECTIONS_MAX 256u -struct frame { - uint32_t addr; +struct connection { + bool exist; + bool prev_is_lost; uint16_t port; - enum frame_type type; + uint32_t connection_id; + uint32_t addr; + struct timespec last_syn; + struct timespec tick_period; + uint32_t tick; + uint32_t server_roundtrip_prev; + uint32_t client_roundtrip_prev; + uint32_t repeat; }; -static unsigned char udp_buffer[UINT16_MAX]; +static struct connection g_connections[CONNECTIONS_MAX] = {{0}, }; +static volatile bool g_should_exit = false; -static int ReceiveFrame(int const fd, struct frame *const frame) +static void sigintHandler(int a) { - frame->type = FT_NONE; - struct sockaddr_in source_address; - socklen_t source_address_len = sizeof(source_address); - int const length = recvfrom( - fd, - udp_buffer, - sizeof(udp_buffer) - 1, - 0, - (struct sockaddr*)&source_address, - &source_address_len); - if (length == 0) { - return -EWOULDBLOCK; - } - if (length < 0) { - int const err = errno; - if (err != EWOULDBLOCK) { - perror("recvfrom failed"); - } - return -err; - } - uint32_t const addr = ntohl(source_address.sin_addr.s_addr); - uint32_t const port = ntohs(source_address.sin_port); - if (length) { - unsigned char frame_type = udp_buffer[0]; - if (frame_type == FT_HELLO) { - *frame = (struct frame){ - .addr = addr, - .port = port, - .type = frame_type, - }; + (void) a; + g_should_exit = true; +} + +static void ConnectionHandleFrame( + struct connection *self, + struct frame const *frame, + struct timespec now, + int fd) +{ + if (frame->type == FT_SYNACK) { + if (LOG_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); } + self->client_roundtrip_prev = frame->syn.roundtrip_prev; + struct frame outgoing = *frame; + outgoing.type = FT_ACK, + outgoing.ack = (struct frame_data_ack){ + .tick = self->tick, + }; + SendFrame(fd, &outgoing); + 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); + self->tick++; } - uint8_t const a0 = addr >> 24; - uint8_t const a1 = addr >> 16; - uint8_t const a2 = addr >> 8; - uint8_t const a3 = addr; - printf("Received %u from %u.%u.%u.%u:%u\n", udp_buffer[0], a0, a1, a2, a3, port); - return 0; } -static int SendFrame(int const fd, struct frame const *const frame) +static void ConnectionHandleSynExpiration(struct connection *self, int fd) { - udp_buffer[0] = frame->type; - struct sockaddr_in const addr = { - .sin_family = AF_INET, - .sin_port = htons(frame->port), - .sin_addr.s_addr = htonl(frame->addr), + struct frame const outgoing = { + .addr = self->addr, + .port = self->port, + .connection_id = self->connection_id, + .type = FT_SYN, + .syn = { + .tick = self->tick, + .prev_is_lost = self->prev_is_lost, + .roundtrip_prev = self->server_roundtrip_prev, + }, }; - ssize_t const ret = sendto( - fd, udp_buffer, 1, 0, (struct sockaddr const *)&addr, sizeof(addr)); - if (ret < 0) { - int const err = errno; - perror("sendto failed"); - return -err; + SendFrame(fd, &outgoing); +} + +static void HandleFrame(struct frame const *frame, struct timespec now, int fd) +{ + uint32_t const connection_id = frame->connection_id; + if (connection_id >= CONNECTIONS_MAX) { + return; + } + if (frame->type == FT_HANDSHAKE) { + if (LOG_DEBUG) { + fprintf(stderr, "FT_HANDSHAKE(tick_period=%" PRIu32 ")\n", frame->handshake.tick_period); + } + struct frame outgoing = *frame; + bool const is_tick_period_valid = + frame->handshake.tick_period >= TICK_PERIOD_MIN_MS && + frame->handshake.tick_period <= TICK_PERIOD_MAX_MS; + if (!is_tick_period_valid) { + outgoing.type = FT_RST; + SendFrame(fd, &outgoing); + return; + } + for (size_t i = 0; i < CONNECTIONS_MAX; i++) { + if (!g_connections[i].exist) { + g_connections[i] = (struct connection) { + .exist = true, + .tick_period = timespec_from_ms(frame->handshake.tick_period), + .addr = frame->addr, + .port = frame->port, + .connection_id = i, + .last_syn = now, + }; + outgoing.type = FT_HANDSHAKE_RESP; + outgoing.connection_id = i; + SendFrame(fd, &outgoing); + if (LOG_DEBUG) { + fprintf(stderr, "New connection id=%" PRIu32 "\n", connection_id); + } + return; + } + } + outgoing.type = FT_RST; + SendFrame(fd, &outgoing); + return; + } + if (g_connections[connection_id].exist) { + ConnectionHandleFrame(&g_connections[connection_id], frame, now, fd); + } + if (frame->type == FT_RST) { + g_connections[connection_id].exist = false; + if (LOG_DEBUG) { + fprintf(stderr, "Connection closed id=%" PRIu32 "\n", connection_id); + } } - printf("message sent\n"); - return 0; } int main(void) @@ -93,54 +153,100 @@ int main(void) return 1; } fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK); + uint32_t const addr = INADDR_ANY; + uint16_t const port = 50037u; struct sockaddr_in const serveraddr = { .sin_family = AF_INET, - .sin_port = htons(50037u), - .sin_addr.s_addr = htonl(INADDR_ANY), + .sin_port = htons(port), + .sin_addr.s_addr = htonl(addr), }; + struct sigaction sa = { + .sa_handler = sigintHandler, + }; + sigaction(SIGINT, &sa, NULL); if (bind(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0) { perror("bind failed"); return 1; } + { + uint8_t const a0 = addr >> 24; + 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); + } struct pollfd fds[1] = { { .fd = fd, .events = POLLIN, }, }; - for (;;) { - int const timeout_msecs = 500; - int const pollret = poll(fds, sizeof(fds)/sizeof(*fds), timeout_msecs); + int timeout_ms = 10000u; + while (!g_should_exit) { + int const pollret = poll(fds, sizeof(fds)/sizeof(*fds), timeout_ms); + int const poll_err = errno; + struct timespec now; + if (-1 == clock_gettime(CLOCK_MONOTONIC, &now)) { + int const err = errno; + fprintf(stderr, + "clock_gettime(CLOCK_MONOTONIC, &now) failed (%d): \"%s\"", + err, strerror(err)); + } if (pollret > 0) { if (fds[0].revents & POLLIN) { while (1) { struct frame frame = { .type = FT_NONE, }; - int const ret = ReceiveFrame(fd, &frame); + int const ret = ReceiveFrame(fds[0].fd, &frame); if (ret) { break; } - if (frame.type == FT_HELLO) { - SendFrame(fd, &frame); - } + HandleFrame(&frame, now, fds[0].fd); } } if (fds[0].revents & POLLRDNORM) { - printf("POLLRDNORM, ignoring...\n"); + fprintf(stderr, "POLLRDNORM, ignoring...\n"); } if (fds[0].revents & POLLRDBAND) { - printf("POLLRDBAND, ignoring...\n"); + fprintf(stderr, "POLLRDBAND, ignoring...\n"); } if (fds[0].revents & POLLPRI) { - printf("POLLPRI, ignoring...\n"); + fprintf(stderr, "POLLPRI, ignoring...\n"); } if (fds[0].revents & POLLPRI) { - printf("POLLPRI, ignoring...\n"); + fprintf(stderr, "POLLPRI, ignoring...\n"); } if (fds[0].revents & POLLHUP) { - printf("POLLHUP, ignoring...\n"); + fprintf(stderr, "POLLHUP, ignoring...\n"); } if (fds[0].revents & POLLERR) { - printf("POLLERR, ignoring...\n"); + fprintf(stderr, "POLLERR, ignoring...\n"); } if (fds[0].revents & POLLNVAL) { - printf("POLLNVAL, exiting...\n"); + fprintf(stderr, "POLLNVAL, exiting...\n"); return 1; } + } else if (pollret < 0) { + if (poll_err == EINTR) { + fprintf(stderr, "SIGINT received\n"); + break; + } + fprintf(stderr, + "poll() failed (%d): \"%s\"", + poll_err, strerror(poll_err)); + } + timeout_ms = 10000u; + for (size_t i = 0; i < CONNECTIONS_MAX; i++) { + struct connection *const conn = g_connections + i; + if (!conn->exist) { + continue; + } + struct timespec const expiration = timespec_add( + conn->last_syn, conn->tick_period); + if (timespec_ge(now, expiration)) { + ConnectionHandleSynExpiration(conn, fd); + // Using `expiration` instead of `now` time here to keep SYN + // rate consistent, although it might have a little jitter. + conn->last_syn = expiration; + } + timeout_ms = timespec_to_ms( + timespec_min( + timespec_from_ms(timeout_ms), + timespec_sub(expiration, now))); } } close(fd); |