summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOxore <oxore@protonmail.com>2023-12-28 00:04:20 +0300
committerOxore <oxore@protonmail.com>2023-12-28 00:04:20 +0300
commit18893127524d87e47a948b9a92d8d8b2ab869852 (patch)
tree1491660a439221174a776b9c7f38f05234590cdb
parent4faf778b6ec4b9d8ab0c19e22d27c76f4c67ab28 (diff)
WIP
-rw-r--r--CMakeLists.txt37
-rw-r--r--client.c166
-rw-r--r--lib/timespec/README.md123
-rw-r--r--lib/timespec/commit.txt1
-rw-r--r--lib/timespec/timespec.c979
-rw-r--r--lib/timespec/timespec.h71
-rw-r--r--proto.c196
-rw-r--r--proto.h140
-rw-r--r--proto_io.c78
-rw-r--r--proto_io.h8
-rw-r--r--server.c258
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)
diff --git a/client.c b/client.c
index 8f4d5ff..11f6156 100644
--- a/client.c
+++ b/client.c
@@ -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 */
diff --git a/proto.c b/proto.c
new file mode 100644
index 0000000..b1f4e02
--- /dev/null
+++ b/proto.c
@@ -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;
+}
diff --git a/proto.h b/proto.h
new file mode 100644
index 0000000..d07aec1
--- /dev/null
+++ b/proto.h
@@ -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);
diff --git a/server.c b/server.c
index ffb7c42..8d00dc7 100644
--- a/server.c
+++ b/server.c
@@ -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);