aboutsummaryrefslogtreecommitdiff
path: root/src/transport
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-09-27 17:03:06 +0200
committerXavier Del Campo Romero <xavi.dcr@tutanota.com>2022-11-01 16:26:16 +0100
commit980858186149651df5543b6fc99a4f7db0cdd089 (patch)
treed347200b0a562d84df505097651ad0642f207fdd /src/transport
parent39f50e601d395bbd2d78d0147ac530b756da2fff (diff)
downloadjancity-980858186149651df5543b6fc99a4f7db0cdd089.tar.gz
WIP
Diffstat (limited to 'src/transport')
-rw-r--r--src/transport/CMakeLists.txt10
-rw-r--r--src/transport/inc/transport.h66
-rw-r--r--src/transport/privinc/transport_private.h66
-rw-r--r--src/transport/src/heap.c93
-rw-r--r--src/transport/src/static.c71
-rw-r--r--src/transport/src/transport.c613
6 files changed, 919 insertions, 0 deletions
diff --git a/src/transport/CMakeLists.txt b/src/transport/CMakeLists.txt
new file mode 100644
index 0000000..8f05c31
--- /dev/null
+++ b/src/transport/CMakeLists.txt
@@ -0,0 +1,10 @@
+set(src "src/transport.c")
+
+if(PS1_BUILD)
+ set(src ${src} "src/static.c")
+elseif(SDL1_2_BUILD)
+ set(src ${src} "src/heap.c")
+endif()
+
+add_library(transport ${src})
+target_include_directories(transport PUBLIC "inc" PRIVATE "privinc")
diff --git a/src/transport/inc/transport.h b/src/transport/inc/transport.h
new file mode 100644
index 0000000..503812b
--- /dev/null
+++ b/src/transport/inc/transport.h
@@ -0,0 +1,66 @@
+#ifndef TRANSPORT_H
+#define TRANSPORT_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+enum {TRANSPORT_MAX_DATA_LEN = 32};
+
+struct transport_event
+{
+ struct transport_event_common
+ {
+ enum transport_event_type
+ {
+ TRANSPORT_EVENT_TYPE_CONNECT,
+ TRANSPORT_EVENT_TYPE_DISCONNECT,
+ TRANSPORT_EVENT_TYPE_DATA
+ } type;
+ } common;
+
+ union
+ {
+ struct transport_event_data
+ {
+ const void *buf;
+ size_t n;
+ } data;
+ } u;
+};
+
+union transport_packet;
+
+struct transport_handle
+{
+ struct transport_cfg
+ {
+ void *arg;
+ int (*write)(const void *, size_t, void *);
+ int (*read)(void *, size_t, void *);
+ void (*received)(const struct transport_event *, void *);
+ } cfg;
+
+ union transport_packet **packets;
+ size_t n_packets;
+
+ struct transport_input
+ {
+ union transport_packet *p;
+ struct transport_event ev;
+ } input;
+};
+
+int transport_connect(struct transport_handle *h);
+int transport_disconnect(struct transport_handle *h);
+int transport_send(struct transport_handle *h, const void *buf, size_t n);
+int transport_update(struct transport_handle *h);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TRANSPORT_H */
diff --git a/src/transport/privinc/transport_private.h b/src/transport/privinc/transport_private.h
new file mode 100644
index 0000000..dc7481e
--- /dev/null
+++ b/src/transport/privinc/transport_private.h
@@ -0,0 +1,66 @@
+#ifndef TRANSPORT_PRIVATE_H
+#define TRANSPORT_PRIVATE_H
+
+#include <transport.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+typedef uint8_t transport_checksum;
+
+union transport_packet
+{
+ struct transport_common
+ {
+ enum transport_packet_type
+ {
+ TRANSPORT_PACKET_TYPE_CONNECT,
+ TRANSPORT_PACKET_TYPE_DISCONNECT,
+ TRANSPORT_PACKET_TYPE_ACK,
+ TRANSPORT_PACKET_TYPE_NACK,
+ TRANSPORT_PACKET_TYPE_DATA,
+
+ MAX_TRANSPORT_PACKET_TYPES
+ } type;
+
+ enum transport_state
+ {
+ TRANSPORT_STATE_HEADER,
+ TRANSPORT_STATE_LEN,
+ TRANSPORT_STATE_BODY,
+ TRANSPORT_STATE_CHECKSUM
+ } state;
+
+ bool done;
+ unsigned ttl;
+ } common;
+
+ struct transport_packet_data
+ {
+ struct transport_common common;
+ uint8_t buf[TRANSPORT_MAX_DATA_LEN];
+ size_t n, written;
+ } data;
+
+ struct transport_packet_ack
+ {
+ struct transport_common common;
+ transport_checksum checksum;
+ } ack;
+};
+
+union transport_packet *transport_packet_alloc(enum transport_packet_type t);
+void transport_packet_free(union transport_packet *p);
+int transport_append(struct transport_handle *h, union transport_packet *p);
+int transport_pop(struct transport_handle *h);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* TRANSPORT_PRIVATE_H */
diff --git a/src/transport/src/heap.c b/src/transport/src/heap.c
new file mode 100644
index 0000000..cf43426
--- /dev/null
+++ b/src/transport/src/heap.c
@@ -0,0 +1,93 @@
+#include <transport.h>
+#include <transport_private.h>
+#include <stdlib.h>
+
+static void *alloc_common(void)
+{
+ struct transport_common *const ret = malloc(sizeof *ret);
+
+ if (ret)
+ *ret = (const struct transport_common){0};
+
+ return ret;
+}
+
+static void *alloc_ack(void)
+{
+ struct transport_packet_ack *const ret = malloc(sizeof *ret);
+
+ if (ret)
+ *ret = (const struct transport_packet_ack){0};
+
+ return ret;
+}
+
+static void *alloc_data(void)
+{
+ struct transport_packet_data *const ret = malloc(sizeof *ret);
+
+ if (ret)
+ *ret = (const struct transport_packet_data){0};
+
+ return ret;
+}
+
+union transport_packet *transport_packet_alloc(const enum transport_packet_type t)
+{
+ static void *(*const alloc[])(void) =
+ {
+ [TRANSPORT_PACKET_TYPE_CONNECT] = alloc_common,
+ [TRANSPORT_PACKET_TYPE_DISCONNECT] = alloc_common,
+ [TRANSPORT_PACKET_TYPE_NACK] = alloc_common,
+ [TRANSPORT_PACKET_TYPE_ACK] = alloc_ack,
+ [TRANSPORT_PACKET_TYPE_DATA] = alloc_data
+ };
+
+ union transport_packet *const ret = alloc[t]();
+
+ if (!ret)
+ return NULL;
+
+ ret->common.type = t;
+ return ret;
+}
+
+int transport_append(struct transport_handle *const h,
+ union transport_packet *const p)
+{
+ h->packets = realloc(h->packets, (h->n_packets + 1) * sizeof *h->packets);
+
+ if (!h->packets)
+ return -1;
+
+ h->packets[h->n_packets++] = p;
+ return 0;
+}
+
+void transport_packet_free(union transport_packet *const p)
+{
+ free(p);
+}
+
+int transport_pop(struct transport_handle *const h)
+{
+ if (!h->n_packets)
+ return -1;
+
+ union transport_packet *const p = h->packets[h->n_packets - 1];
+
+ if (--h->n_packets)
+ {
+ if (!(h->packets = realloc(h->packets,
+ (h->n_packets - 1) * sizeof *h->packets)))
+ return -1;
+ }
+ else
+ {
+ free(h->packets);
+ h->packets = NULL;
+ }
+
+ transport_packet_free(p);
+ return 0;
+}
diff --git a/src/transport/src/static.c b/src/transport/src/static.c
new file mode 100644
index 0000000..e5dda54
--- /dev/null
+++ b/src/transport/src/static.c
@@ -0,0 +1,71 @@
+#include <transport.h>
+#include <transport_private.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <string.h>
+
+static struct list
+{
+ union transport_packet p;
+ bool used;
+} list[16];
+
+static union transport_packet *packets[sizeof list / sizeof *list];
+
+union transport_packet *transport_packet_alloc(const enum transport_packet_type t)
+{
+ for (size_t i = 0; i < sizeof list / sizeof *list; i++)
+ {
+ struct list *const l = &list[i];
+
+ if (!l->used)
+ {
+ union transport_packet *const p = &l->p;
+
+ l->used = true;
+ *p = (const union transport_packet)
+ {
+ .common =
+ {
+ .type = t
+ }
+ };
+
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
+int transport_append(struct transport_handle *const h,
+ union transport_packet *const p)
+{
+ if (h->n_packets >= sizeof packets / sizeof *packets)
+ return -1;
+
+ packets[h->n_packets++] = p;
+ h->packets = packets;
+ return 0;
+}
+
+void transport_packet_free(union transport_packet *const p)
+{
+ for (size_t i = 0; i < sizeof list / sizeof *list; i++)
+ {
+ struct list *const l = &list[i];
+
+ if (p == &l->p)
+ {
+ l->used = false;
+ return;
+ }
+ }
+}
+
+int transport_pop(struct transport_handle *const h)
+{
+ transport_packet_free(h->packets[h->n_packets - 1]);
+ return 0;
+}
diff --git a/src/transport/src/transport.c b/src/transport/src/transport.c
new file mode 100644
index 0000000..fe2d54f
--- /dev/null
+++ b/src/transport/src/transport.c
@@ -0,0 +1,613 @@
+#include <transport.h>
+#include <transport_private.h>
+#include <inttypes.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+
+typedef uint8_t packet_header, packet_len;
+
+int transport_connect(struct transport_handle *const h)
+{
+ union transport_packet *const p = transport_packet_alloc(
+ TRANSPORT_PACKET_TYPE_CONNECT);
+
+ if (!p)
+ return -1;
+
+ return transport_append(h, p);
+}
+
+int transport_disconnect(struct transport_handle *const h)
+{
+ union transport_packet *const p = transport_packet_alloc(
+ TRANSPORT_PACKET_TYPE_DISCONNECT);
+
+ if (!p)
+ return -1;
+
+ return transport_append(h, p);
+}
+
+int transport_send(struct transport_handle *const h,
+ const void *const buf, size_t n)
+{
+ const char *b = buf;
+
+ while (n)
+ {
+ union transport_packet *const p = transport_packet_alloc(
+ TRANSPORT_PACKET_TYPE_DATA);
+
+ if (!p)
+ return -1;
+
+ struct transport_packet_data *const d = &p->data;
+ const size_t rem = n > sizeof d->buf ? sizeof d->buf : n;
+
+ memcpy(d->buf, b, rem);
+ d->n = rem;
+
+ if (transport_append(h, p))
+ return -1;
+
+ n -= rem;
+ b += rem;
+ }
+
+ return 0;
+}
+
+static enum transport_state header_next_state(const enum transport_packet_type t)
+{
+ switch (t)
+ {
+ case TRANSPORT_PACKET_TYPE_CONNECT:
+ /* Fall through. */
+ case TRANSPORT_PACKET_TYPE_DISCONNECT:
+ /* Fall through. */
+ case TRANSPORT_PACKET_TYPE_NACK:
+ /* Fall through. */
+ return TRANSPORT_STATE_CHECKSUM;
+
+ case TRANSPORT_PACKET_TYPE_ACK:
+ return TRANSPORT_STATE_BODY;
+
+ case TRANSPORT_PACKET_TYPE_DATA:
+ return TRANSPORT_STATE_LEN;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int read_header(const struct transport_cfg *const cfg,
+ struct transport_input *const in, bool *const done)
+{
+ packet_header header;
+ const int res = cfg->read(&header, sizeof header, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof header)
+ return 1;
+
+ if (header >= MAX_TRANSPORT_PACKET_TYPES)
+ {
+ fprintf(stderr, "%s: invalid header %" PRIu8 "\n",
+ __func__, header);
+ return -1;
+ }
+
+ if (!(in->p = transport_packet_alloc(header)))
+ {
+ fprintf(stderr, "%s: transport_packet_alloc failed\n", __func__);
+ return -1;
+ }
+
+ struct transport_common *const c = &in->p->common;
+
+ c->state = header_next_state(c->type);
+ return 0;
+}
+
+static int read_len(const struct transport_cfg *const cfg,
+ struct transport_input *const in, bool *const done)
+{
+ packet_len len;
+ const int res = cfg->read(&len, sizeof len, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof len)
+ return 1;
+
+ struct transport_packet_data *const d = &in->p->data;
+
+ if (len >= sizeof d->buf)
+ {
+ fprintf(stderr, "%s: invalid length %" PRIu8 "\n", __func__, len);
+ return -1;
+ }
+
+ d->n = len;
+ d->common.state = TRANSPORT_STATE_BODY;
+ return 0;
+}
+
+static int read_data_body(const struct transport_cfg *const cfg,
+ struct transport_input *const in)
+{
+ struct transport_packet_data *const d = &in->p->data;
+ const size_t rem = d->n - d->written;
+ const int res = cfg->read(&d->buf[d->written], rem, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != rem)
+ {
+ d->written += res;
+ return 1;
+ }
+
+ in->p->common.state = TRANSPORT_STATE_CHECKSUM;
+ return 0;
+}
+
+static int read_ack_body(const struct transport_cfg *const cfg,
+ struct transport_input *const in)
+{
+ struct transport_packet_ack *const a = &in->p->ack;
+ const int res = cfg->read(&a->checksum, sizeof a->checksum, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof a->checksum)
+ return 1;
+
+ in->p->common.state = TRANSPORT_STATE_CHECKSUM;
+ return 0;
+}
+
+static int read_body(const struct transport_cfg *const cfg,
+ struct transport_input *const in, bool *const done)
+{
+ const struct transport_common *const c = &in->p->common;
+
+ switch (c->type)
+ {
+ case TRANSPORT_PACKET_TYPE_ACK:
+ return read_ack_body(cfg, in);
+
+ case TRANSPORT_PACKET_TYPE_DATA:
+ return read_data_body(cfg, in);
+
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+static transport_checksum calc_checksum(const union transport_packet *const p)
+{
+ const struct transport_common *const c = &p->common;
+ transport_checksum ret = c->type;
+
+ switch (c->type)
+ {
+ case TRANSPORT_PACKET_TYPE_DATA:
+ {
+ const struct transport_packet_data *const d = &p->data;
+
+ ret += d->n;
+
+ for (size_t i = 0; i < d->n; i++)
+ ret += d->buf[i];
+ }
+ break;
+
+ case TRANSPORT_PACKET_TYPE_ACK:
+ ret += p->ack.checksum;
+ break;
+
+ default:
+ break;
+ }
+
+ return ~ret;
+}
+
+static int read_checksum(const struct transport_cfg *const cfg,
+ struct transport_input *const in, bool *const done)
+{
+ transport_checksum checksum;
+ const int res = cfg->read(&checksum, sizeof checksum, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof checksum)
+ return 1;
+
+ const transport_checksum expected = calc_checksum(in->p);
+
+ *done = true;
+
+ if (checksum != expected)
+ {
+ fprintf(stderr, "%s: invalid checksum %#" PRIx8 ", expected %#" PRIx8,
+ __func__, checksum, expected);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int send_nack(struct transport_handle *const h)
+{
+ union transport_packet *const p = transport_packet_alloc(
+ TRANSPORT_PACKET_TYPE_NACK);
+
+ if (!p)
+ return -1;
+
+ return transport_append(h, p);
+}
+
+static bool requires_ack(const enum transport_packet_type t)
+{
+ static const bool r[] =
+ {
+ [TRANSPORT_PACKET_TYPE_CONNECT] = true,
+ [TRANSPORT_PACKET_TYPE_DISCONNECT] = true,
+ [TRANSPORT_PACKET_TYPE_ACK] = false,
+ [TRANSPORT_PACKET_TYPE_NACK] = false,
+ [TRANSPORT_PACKET_TYPE_DATA] = true
+ };
+
+ return r[t];
+}
+
+static int send_ack(struct transport_handle *const h)
+{
+ union transport_packet *const p = transport_packet_alloc(
+ TRANSPORT_PACKET_TYPE_ACK);
+
+ if (!p)
+ return -1;
+
+ p->ack.checksum = calc_checksum(h->input.p);
+ return transport_append(h, p);
+}
+
+static void delete_input(struct transport_input *const in)
+{
+ transport_packet_free(in->p);
+ in->p = NULL;
+}
+
+static void send_event(const struct transport_cfg *const cfg,
+ const union transport_packet *const p,
+ struct transport_event *const ev)
+{
+ switch (p->common.type)
+ {
+ case TRANSPORT_PACKET_TYPE_CONNECT:
+ ev->common.type = TRANSPORT_EVENT_TYPE_CONNECT;
+ break;
+
+ case TRANSPORT_PACKET_TYPE_DISCONNECT:
+ ev->common.type = TRANSPORT_EVENT_TYPE_DISCONNECT;
+ break;
+
+ case TRANSPORT_PACKET_TYPE_DATA:
+ ev->common.type = TRANSPORT_EVENT_TYPE_DATA;
+ ev->u.data.buf = p->data.buf;
+ ev->u.data.n = p->data.n;
+ break;
+
+ default:
+ return;
+ }
+
+ if (cfg->received)
+ cfg->received(ev, cfg->arg);
+}
+
+static int remove_packet(struct transport_handle *const h,
+ union transport_packet *const p)
+{
+ for (size_t i = 0; i < h->n_packets; i++)
+ {
+ union transport_packet **pp = &h->packets[i];
+
+ if (*pp == p)
+ {
+ if (i + 1 < h->n_packets)
+ for (union transport_packet **pr = &h->packets[i + 1];
+ pr - h->packets < h->n_packets;
+ pp++, pr++)
+ *pp = *pr;
+
+ return transport_pop(h);
+ }
+ }
+
+ return -1;
+}
+
+static void get_event(const struct transport_cfg *const cfg,
+ const union transport_packet *const p,
+ struct transport_event *const ev)
+{
+ switch (p->common.type)
+ {
+ case TRANSPORT_PACKET_TYPE_CONNECT:
+ ev->common.type = TRANSPORT_EVENT_TYPE_CONNECT;
+ break;
+
+ case TRANSPORT_PACKET_TYPE_DISCONNECT:
+ ev->common.type = TRANSPORT_EVENT_TYPE_DISCONNECT;
+ break;
+
+ default:
+ return;
+ }
+
+ if (cfg->received)
+ cfg->received(ev, cfg->arg);
+}
+
+static int process_ack(struct transport_handle *const h,
+ const struct transport_packet_ack *const a)
+{
+ for (size_t i = 0; i < h->n_packets; i++)
+ {
+ union transport_packet *const p = h->packets[i];
+
+ if (p->common.ttl && a->checksum == calc_checksum(p))
+ {
+ get_event(&h->cfg, p, &h->input.ev);
+
+ if (remove_packet(h, p))
+ return -1;
+
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int process_nack(struct transport_handle *const h)
+{
+ if (!h->n_packets)
+ {
+ fprintf(stderr, "%s: received nack without previous sent packet\n",
+ __func__);
+ return -1;
+ }
+
+ h->packets[0]->common.ttl = 0;
+ return 0;
+}
+
+static int process_input(struct transport_handle *const h)
+{
+ const union transport_packet *const p = h->input.p;
+
+ switch (p->common.type)
+ {
+ case TRANSPORT_PACKET_TYPE_ACK:
+ return process_ack(h, &p->ack);
+
+ case TRANSPORT_PACKET_TYPE_NACK:
+ return process_nack(h);
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int update_input(struct transport_handle *const h)
+{
+ int ret = -1;
+ bool done = false;
+ struct transport_input *const in = &h->input;
+
+ static int (*const f[])(const struct transport_cfg *,
+ struct transport_input *, bool *) =
+ {
+ [TRANSPORT_STATE_HEADER] = read_header,
+ [TRANSPORT_STATE_LEN] = read_len,
+ [TRANSPORT_STATE_BODY] = read_body,
+ [TRANSPORT_STATE_CHECKSUM] = read_checksum
+ };
+
+ const struct transport_cfg *const cfg = &h->cfg;
+
+ while (!done)
+ {
+ const enum transport_state s = in->p ? in->p->common.state : 0;
+ const int res = f[s](cfg, in, &done);
+
+ if (res < 0)
+ {
+ ret = send_nack(h);
+ goto end;
+ }
+ else if (res)
+ return 0;
+ }
+
+ const union transport_packet *const p = in->p;
+ const enum transport_packet_type t = p->common.type;
+
+ if (process_input(h))
+ goto end;
+ else if (requires_ack(t))
+ {
+ if (send_ack(h))
+ {
+ fprintf(stderr, "%s: send_ack failed\n", __func__);
+ goto end;
+ }
+
+ send_event(&h->cfg, p, &h->input.ev);
+ }
+
+ ret = 0;
+
+end:
+ delete_input(in);
+ return ret;
+}
+
+static int write_header(const struct transport_cfg *const cfg,
+ union transport_packet *const p)
+{
+ const packet_header header = p->common.type;
+ struct transport_common *const c = &p->common;
+ const int res = cfg->write(&header, sizeof header, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof header)
+ return 1;
+
+ c->state = header_next_state(c->type);
+ return 0;
+}
+
+static int write_len(const struct transport_cfg *const cfg,
+ union transport_packet *const p)
+{
+ const packet_len len = p->data.n;
+ const int res = cfg->write(&len, sizeof len, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof len)
+ return 1;
+
+ p->common.state = TRANSPORT_STATE_BODY;
+ return 0;
+}
+
+static int write_ack_body(const struct transport_cfg *const cfg,
+ struct transport_packet_ack *const a)
+{
+ const int res = cfg->write(&a->checksum, sizeof a->checksum, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof a->checksum)
+ return 1;
+
+ a->common.state = TRANSPORT_STATE_CHECKSUM;
+ return 0;
+}
+
+static int write_data_body(const struct transport_cfg *const cfg,
+ struct transport_packet_data *const d)
+{
+ const size_t rem = d->n - d->written;
+ const int res = cfg->write(&d->buf[d->written], rem, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != rem)
+ {
+ d->written += res;
+ return 1;
+ }
+
+ d->common.state = TRANSPORT_STATE_CHECKSUM;
+ return 0;
+}
+
+static int write_body(const struct transport_cfg *const cfg,
+ union transport_packet *const p)
+{
+ struct transport_common *const c = &p->common;
+
+ switch (c->type)
+ {
+ case TRANSPORT_PACKET_TYPE_ACK:
+ return write_ack_body(cfg, &p->ack);
+
+ case TRANSPORT_PACKET_TYPE_DATA:
+ return write_data_body(cfg, &p->data);
+
+ default:
+ break;
+ }
+
+ return -1;
+}
+
+static int write_checksum(const struct transport_cfg *const cfg,
+ union transport_packet *const p)
+{
+ const transport_checksum checksum = calc_checksum(p);
+ const int res = cfg->write(&checksum, sizeof checksum, cfg->arg);
+
+ if (res < 0)
+ return res;
+ else if (res != sizeof checksum)
+ return 1;
+
+ p->common.done = true;
+ return 0;
+}
+
+static int update_output(struct transport_handle *const h)
+{
+ for (size_t i = 0; i < h->n_packets; i++)
+ {
+ union transport_packet *const p = h->packets[i];
+ struct transport_common *const c = &p->common;
+
+ if (!c->ttl)
+ {
+ static int (*const f[])(const struct transport_cfg *,
+ union transport_packet *) =
+ {
+ [TRANSPORT_STATE_HEADER] = write_header,
+ [TRANSPORT_STATE_LEN] = write_len,
+ [TRANSPORT_STATE_BODY] = write_body,
+ [TRANSPORT_STATE_CHECKSUM] = write_checksum
+ };
+
+ while (!c->done)
+ {
+ const int res = f[c->state](&h->cfg, p);
+
+ if (res < 0)
+ return res;
+ else if (res)
+ return 0;
+ }
+
+ /* Arbitrary value, also depends on frame rate, yet good enough. */
+ enum {TTL = 200};
+
+ c->ttl = TTL;
+ }
+ else
+ c->ttl--;
+ }
+
+ return 0;
+}
+
+int transport_update(struct transport_handle *const h)
+{
+ return update_input(h) || update_output(h);
+}