diff options
| author | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2022-09-27 17:03:06 +0200 |
|---|---|---|
| committer | Xavier Del Campo Romero <xavi.dcr@tutanota.com> | 2022-11-01 16:26:16 +0100 |
| commit | 980858186149651df5543b6fc99a4f7db0cdd089 (patch) | |
| tree | d347200b0a562d84df505097651ad0642f207fdd /src/transport | |
| parent | 39f50e601d395bbd2d78d0147ac530b756da2fff (diff) | |
| download | jancity-980858186149651df5543b6fc99a4f7db0cdd089.tar.gz | |
WIP
Diffstat (limited to 'src/transport')
| -rw-r--r-- | src/transport/CMakeLists.txt | 10 | ||||
| -rw-r--r-- | src/transport/inc/transport.h | 66 | ||||
| -rw-r--r-- | src/transport/privinc/transport_private.h | 66 | ||||
| -rw-r--r-- | src/transport/src/heap.c | 93 | ||||
| -rw-r--r-- | src/transport/src/static.c | 71 | ||||
| -rw-r--r-- | src/transport/src/transport.c | 613 |
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); +} |
