aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorXavier Del Campo Romero <xavi92@disroot.org>2025-09-09 03:14:50 +0200
committerXavier Del Campo Romero <xavi92@disroot.org>2025-09-18 01:08:14 +0200
commitc11cb04929f28853142b14339b66f561ca028f36 (patch)
treea77a3ddcc1d01028e4077cb295b9b41f594c5d52 /src
First commitHEADmaster
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt16
-rw-r--r--src/init.c20
-rw-r--r--src/io/CMakeLists.txt15
-rw-r--r--src/io/be16.c16
-rw-r--r--src/io/be32.c17
-rw-r--r--src/io/read.c32
-rw-r--r--src/io/read_c.c28
-rw-r--r--src/io/tobe16.c17
-rw-r--r--src/io/tobe32.c19
-rw-r--r--src/log/CMakeLists.txt10
-rw-r--r--src/log/log.c23
-rw-r--r--src/protocol/CMakeLists.txt10
-rw-r--r--src/protocol/icmp/CMakeLists.txt14
-rw-r--r--src/protocol/icmp/alloc.c29
-rw-r--r--src/protocol/icmp/free.c25
-rw-r--r--src/protocol/icmp/rx.c200
-rw-r--r--src/protocol/icmp/start.c205
-rw-r--r--src/protocol/icmp/tx.c21
-rw-r--r--src/protocol/tcp/CMakeLists.txt13
-rw-r--r--src/protocol/tcp/alloc.c20
-rw-r--r--src/protocol/tcp/free.c19
-rw-r--r--src/protocol/tcp/rx.c21
-rw-r--r--src/protocol/tcp/tx.c21
-rw-r--r--src/protocol/udp/CMakeLists.txt13
-rw-r--r--src/protocol/udp/alloc.c20
-rw-r--r--src/protocol/udp/free.c19
-rw-r--r--src/protocol/udp/rx.c21
-rw-r--r--src/protocol/udp/tx.c21
-rw-r--r--src/routines/CMakeLists.txt10
-rw-r--r--src/routines/rx.c447
-rw-r--r--src/run.c49
31 files changed, 1411 insertions, 0 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
new file mode 100644
index 0000000..8ca9376
--- /dev/null
+++ b/src/CMakeLists.txt
@@ -0,0 +1,16 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ init.c
+ run.c
+)
+
+add_subdirectory(io)
+add_subdirectory(log)
+add_subdirectory(protocol)
+add_subdirectory(routines)
diff --git a/src/init.c b/src/init.c
new file mode 100644
index 0000000..570f78b
--- /dev/null
+++ b/src/init.c
@@ -0,0 +1,20 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/routines.h>
+
+void wip_init(struct wip *const w, const struct wip_cfg *const cfg)
+{
+ const struct wip ww = {0};
+
+ *w = ww;
+ w->cfg = *cfg;
+ wip_rx(w);
+}
diff --git a/src/io/CMakeLists.txt b/src/io/CMakeLists.txt
new file mode 100644
index 0000000..b989afd
--- /dev/null
+++ b/src/io/CMakeLists.txt
@@ -0,0 +1,15 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ be16.c
+ be32.c
+ read.c
+ read_c.c
+ tobe16.c
+ tobe32.c
+)
diff --git a/src/io/be16.c b/src/io/be16.c
new file mode 100644
index 0000000..d8b995a
--- /dev/null
+++ b/src/io/be16.c
@@ -0,0 +1,16 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+
+unsigned short wip_be16(const struct wip_be16 *const v)
+{
+ return ((unsigned long)v->v[0] << 8) | v->v[1];
+}
diff --git a/src/io/be32.c b/src/io/be32.c
new file mode 100644
index 0000000..9defd06
--- /dev/null
+++ b/src/io/be32.c
@@ -0,0 +1,17 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+
+unsigned long wip_be32(const struct wip_be32 *const v)
+{
+ return ((unsigned long)v->v[0] << 24) | ((unsigned long)v->v[1] << 16)
+ | (v->v[2] << 8) | v->v[3];
+}
diff --git a/src/io/read.c b/src/io/read.c
new file mode 100644
index 0000000..f8ee1b2
--- /dev/null
+++ b/src/io/read.c
@@ -0,0 +1,32 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+
+enum wip_state wip_io_read(struct wip *const w, struct wip_sm_io *const io)
+{
+ void *const buf = (unsigned char *)io->buf + io->read;
+ const struct wip_cfg *const cfg = &w->cfg;
+ const size_t rem = io->n - io->read;
+ const int n = cfg->read(buf, rem, cfg->user);
+
+ if (n < 0)
+ {
+ w->exception = "read error";
+ return WIP_FATAL;
+ }
+ else if ((io->read += n) >= io->n)
+ {
+ io->read = 0;
+ return WIP_OK;
+ }
+
+ return WIP_AGAIN;
+}
diff --git a/src/io/read_c.c b/src/io/read_c.c
new file mode 100644
index 0000000..4433a9f
--- /dev/null
+++ b/src/io/read_c.c
@@ -0,0 +1,28 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+
+enum wip_state wip_io_read_c(struct wip_cfg *const cfg, struct wip_sm_io *const io)
+{
+ void *const buf = (unsigned char *)io->buf + io->read;
+ const size_t rem = io->n - io->read;
+ const int n = cfg->read(buf, rem, cfg->user);
+
+ if (n < 0)
+ return WIP_FATAL;
+ else if ((io->read += n) >= io->n)
+ {
+ io->read = 0;
+ return WIP_OK;
+ }
+
+ return WIP_AGAIN;
+}
diff --git a/src/io/tobe16.c b/src/io/tobe16.c
new file mode 100644
index 0000000..3ac7992
--- /dev/null
+++ b/src/io/tobe16.c
@@ -0,0 +1,17 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+
+void wip_tobe16(const unsigned long v, struct wip_be16 *const r)
+{
+ r->v[0] = v >> 8;
+ r->v[1] = v;
+}
diff --git a/src/io/tobe32.c b/src/io/tobe32.c
new file mode 100644
index 0000000..251a0f9
--- /dev/null
+++ b/src/io/tobe32.c
@@ -0,0 +1,19 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+
+void wip_tobe32(const unsigned long v, struct wip_be32 *const r)
+{
+ r->v[0] = v >> 24;
+ r->v[1] = v >> 16;
+ r->v[2] = v >> 8;
+ r->v[3] = v;
+}
diff --git a/src/log/CMakeLists.txt b/src/log/CMakeLists.txt
new file mode 100644
index 0000000..e4a0289
--- /dev/null
+++ b/src/log/CMakeLists.txt
@@ -0,0 +1,10 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+if(WIP_LOG AND NOT WIP_LOG_CUSTOM)
+ target_sources(${PROJECT_NAME} PRIVATE log.c)
+endif()
diff --git a/src/log/log.c b/src/log/log.c
new file mode 100644
index 0000000..b9e20af
--- /dev/null
+++ b/src/log/log.c
@@ -0,0 +1,23 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/prv/log.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+int nwp_log(const char *const fmt, ...)
+{
+ va_list ap;
+ int ret;
+
+ va_start(ap, fmt);
+ ret = vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ return ret;
+}
diff --git a/src/protocol/CMakeLists.txt b/src/protocol/CMakeLists.txt
new file mode 100644
index 0000000..194274d
--- /dev/null
+++ b/src/protocol/CMakeLists.txt
@@ -0,0 +1,10 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+add_subdirectory(icmp)
+add_subdirectory(tcp)
+add_subdirectory(udp)
diff --git a/src/protocol/icmp/CMakeLists.txt b/src/protocol/icmp/CMakeLists.txt
new file mode 100644
index 0000000..4fd374d
--- /dev/null
+++ b/src/protocol/icmp/CMakeLists.txt
@@ -0,0 +1,14 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ alloc.c
+ free.c
+ rx.c
+ start.c
+ tx.c
+)
diff --git a/src/protocol/icmp/alloc.c b/src/protocol/icmp/alloc.c
new file mode 100644
index 0000000..23d7329
--- /dev/null
+++ b/src/protocol/icmp/alloc.c
@@ -0,0 +1,29 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/icmp.h>
+#include <wip/prv/icmp/routines.h>
+#include <wip/prv/icmp/types.h>
+#include <stddef.h>
+
+void *wip_icmp_alloc(struct wip *const w)
+{
+ const struct wip_cfg *const cfg = &w->cfg;
+ const struct wip_icmp z = {0};
+ struct wip_icmp *const ret = cfg->alloc(sizeof *ret, cfg->user);
+
+ if (!ret)
+ return ret;
+
+ *ret = z;
+ ret->next = wip_icmp_rx_start;
+ return ret;
+}
diff --git a/src/protocol/icmp/free.c b/src/protocol/icmp/free.c
new file mode 100644
index 0000000..452e1d4
--- /dev/null
+++ b/src/protocol/icmp/free.c
@@ -0,0 +1,25 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/icmp.h>
+#include <wip/prv/icmp/types.h>
+#include <stddef.h>
+
+void wip_icmp_free(struct wip *const w, void *const args)
+{
+ const struct wip_cfg *const cfg = &w->cfg;
+ struct wip_icmp *const icmp = args;
+
+ if (icmp)
+ cfg->free(icmp->buf, cfg->user);
+
+ cfg->free(icmp, cfg->user);
+}
diff --git a/src/protocol/icmp/rx.c b/src/protocol/icmp/rx.c
new file mode 100644
index 0000000..8e543f9
--- /dev/null
+++ b/src/protocol/icmp/rx.c
@@ -0,0 +1,200 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+#include <wip/prv/log.h>
+#include <wip/prv/icmp.h>
+#include <wip/prv/icmp/types.h>
+#include <stddef.h>
+#include <string.h>
+
+enum
+{
+ TYPE,
+ CHECKSUM = 2,
+ ID = 4,
+ SEQ_NUM = 6,
+ DATA = 8
+};
+
+#if 0
+
+static int get_type(struct wip_icmp *const icmp,
+ const unsigned char *const p, const size_t n)
+{
+ unsigned char type;
+
+ if (icmp->read)
+ return 0;
+
+ type = p[0];
+
+ if (type != ECHO_REQUEST)
+ return -1;
+
+ wip_log("%s: got echo request\n");
+ icmp->type = type;
+ return 0;
+}
+
+static void get_checksum(struct wip_icmp *const icmp,
+ const unsigned char *const p, const size_t n)
+{
+ size_t off;
+ struct wip_be16 v;
+ enum {END = CHECKSUM + sizeof v};
+
+ if (icmp->read >= END || n < END - icmp->read)
+ return;
+
+ off = CHECKSUM - icmp->read;
+ v.v[0] = p[off];
+ v.v[1] = p[off + 1];
+ icmp->exp_checksum = wip_be16(&v);
+}
+
+static void get_id(struct wip_icmp *const icmp, const unsigned char *const p,
+ const size_t n)
+{
+ size_t off;
+ struct wip_be16 v;
+ enum {END = ID + sizeof v};
+
+ if (icmp->read >= END || n < END - icmp->read)
+ return;
+
+ off = ID - icmp->read;
+ v.v[0] = p[off];
+ v.v[1] = p[off + 1];
+ icmp->id = wip_be16(&v);
+}
+
+static void get_seqnum(struct wip_icmp *const icmp, const unsigned char *const p,
+ const size_t n)
+{
+ size_t off;
+ struct wip_be16 v;
+ enum {END = SEQ_NUM + sizeof v};
+
+ if (icmp->read >= END || n < END - icmp->read)
+ return;
+
+ off = SEQ_NUM - icmp->read;
+ v.v[0] = p[off];
+ v.v[1] = p[off + 1];
+ icmp->seqnum = wip_be16(&v);
+ wip_log("chksum=%#x, id=%#x, seq_num=%#x\n", icmp->exp_checksum,
+ icmp->id, icmp->seqnum);
+}
+
+static enum wip_state get_data(struct wip *const w,
+ struct wip_icmp *const icmp, const unsigned char *const p, const size_t n)
+{
+ const struct wip_cfg *const cfg = &w->cfg;
+ const size_t offset = icmp->read + n;
+ const void *src = p;
+ size_t sz = n;
+
+ if (offset < DATA)
+ return WIP_OK;
+ else if (!icmp->buf)
+ {
+ sz = n - DATA - icmp->read;
+ src = p + DATA - icmp->read;
+
+ if (!(icmp->buf = cfg->alloc(sz, cfg->user)))
+ return WIP_FATAL;
+ }
+ else
+ {
+ const size_t nsz = icmp->n + n;
+ enum {MAXSZ = 40};
+ void *buf;
+
+ if (nsz > MAXSZ)
+ return WIP_INVALID;
+ else if (!(buf = cfg->realloc(icmp->buf, nsz, cfg->user)))
+ return WIP_FATAL;
+
+ icmp->buf = buf;
+ }
+
+ memcpy((char *)icmp->buf + icmp->read, src, sz);
+ icmp->n += sz;
+
+ wip_log("data (%lu bytes): {", (unsigned long)icmp->n);
+
+ {
+ size_t i;
+
+ for (i = 0; i < icmp->n; i++)
+ {
+ wip_log("%#hhx", *((const char *)icmp->buf + i));
+
+ if (i + 1 < icmp->n)
+ wip_log(", ");
+ }
+ }
+
+ wip_log("}\n");
+ return WIP_OK;
+}
+#endif
+
+enum wip_state wip_icmp_rx(struct wip *const w, const void *buf,
+ size_t n, void *const args)
+{
+ struct wip_icmp *const icmp = args;
+ enum wip_state ret;
+ size_t r;
+
+ while ((ret = icmp->next(w, buf, n, icmp, &r)) == WIP_AGAIN)
+ {
+ buf = (const char *)buf + r;
+ n -= r;
+ }
+
+ return ret;
+
+#if 0
+ enum wip_state (*next)(struct wip *, const void *, size_t,
+ struct wip_icmp *)
+
+ const struct wip_rx_sm_h *const h = &w->rx.sm.h;
+ const unsigned char *const p = buf;
+ const unsigned short dlen = wip_be16(&h->len) - h->header_sz;
+
+ enum wip_state ret;
+ size_t i;
+
+ if (get_type(icmp, buf, n))
+ return 0;
+
+ get_checksum(icmp, buf, n);
+ get_id(icmp, buf, n);
+ get_seqnum(icmp, buf, n);
+
+ if ((ret = get_data(w, icmp, buf, n)))
+ return ret;
+
+ for (i = 0; i < n; i++)
+ /* Avoid suming checksum */
+ if (icmp->read >= CHECKSUM || ((i != 2) && (i != 3)))
+ icmp->checksum += p[i];
+
+ if (icmp->read += n >= dlen)
+ {
+ wip_log("expected checksum=%#x, got=%#x\n",
+ icmp->exp_checksum, icmp->checksum);
+ }
+
+ return WIP_OK;
+#endif
+}
diff --git a/src/protocol/icmp/start.c b/src/protocol/icmp/start.c
new file mode 100644
index 0000000..f50052e
--- /dev/null
+++ b/src/protocol/icmp/start.c
@@ -0,0 +1,205 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+#include <wip/prv/log.h>
+#include <wip/prv/icmp.h>
+#include <wip/prv/icmp/routines.h>
+#include <wip/prv/icmp/types.h>
+#include <stddef.h>
+#include <string.h>
+
+struct read
+{
+ const void *buf;
+ size_t n;
+ struct wip_icmp *icmp;
+};
+
+static int io_read(void *const buf, const size_t n, void *const user)
+{
+ const struct read *const r = user;
+ const size_t ret = n > r->n ? r->n : n;
+
+ memcpy(buf, r->buf, ret);
+ return ret;
+}
+
+static enum wip_state readbuf(struct wip_sm_io *const io, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp)
+{
+ struct wip_cfg cfg = {0};
+ struct read r;
+
+ r.buf = buf;
+ r.n = n;
+ r.icmp = icmp;
+
+ cfg.read = io_read;
+ cfg.user = &r;
+ return wip_io_read_c(&cfg, io);
+}
+
+enum wip_state get_payload(struct wip *const w, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp, size_t *const r)
+{
+ const struct wip_cfg *const cfg = &w->cfg;
+ const size_t nsz = icmp->read + n;
+ char *const nbuf = cfg->realloc(icmp->buf, nsz, cfg->user);
+ size_t i;
+
+ if (!nbuf)
+ return WIP_FATAL;
+
+ memcpy(nbuf + icmp->read, buf, n);
+ icmp->buf = nbuf;
+ icmp->read += n;
+ *r = n;
+
+ for (i = icmp->read; i < nsz; i++)
+ icmp->checksum += *(const char *)icmp->buf;
+
+ wip_log("ICMP data={");
+
+ for (i = 0; i < icmp->read; i++)
+ {
+ wip_log("%#hhx", *((const char *)icmp->buf + i));
+
+ if (i + 1 < icmp->read)
+ wip_log(", ");
+ }
+
+ wip_log("}\n");
+
+ wip_log("calculated checksum: %#hx, expected checksum: %#hx\n",
+ icmp->checksum, wip_be16(&icmp->exp_checksum));
+ return WIP_OK;
+}
+
+enum wip_state get_seqnum(struct wip *const w, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp, size_t *const r)
+{
+ const enum wip_state state = readbuf(&icmp->io, buf, n, icmp);
+
+ if (state)
+ return state;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &icmp->seqnum;
+ io.n = sizeof icmp->seqnum;
+ icmp->io = io;
+ }
+
+ wip_log("got seqnum: %#x\n", wip_be16(&icmp->seqnum));
+ icmp->checksum += wip_be16(&icmp->seqnum);
+ icmp->next = get_payload;
+ *r = sizeof icmp->seqnum;
+ return WIP_AGAIN;
+}
+
+enum wip_state get_id(struct wip *const w, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp, size_t *const r)
+{
+ const enum wip_state state = readbuf(&icmp->io, buf, n, icmp);
+
+ if (state)
+ return state;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &icmp->seqnum;
+ io.n = sizeof icmp->seqnum;
+ icmp->io = io;
+ }
+
+ wip_log("got id: %#x\n", wip_be16(&icmp->id));
+ icmp->checksum += wip_be16(&icmp->id);
+ icmp->next = get_seqnum;
+ *r = sizeof icmp->id;
+ return WIP_AGAIN;
+}
+
+enum wip_state get_checksum(struct wip *const w, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp, size_t *const r)
+{
+ const enum wip_state state = readbuf(&icmp->io, buf, n, icmp);
+
+ if (state)
+ return state;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &icmp->id;
+ io.n = sizeof icmp->id;
+ icmp->io = io;
+ }
+
+ wip_log("got expected checksum: %#x\n", wip_be16(&icmp->exp_checksum));
+ icmp->next = get_id;
+ *r = sizeof icmp->exp_checksum;
+ return WIP_AGAIN;
+}
+
+enum wip_state get_code(struct wip *const w, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp, size_t *const r)
+{
+ unsigned char code;
+ struct wip_sm_io io = {0};
+ enum wip_state state;
+
+ io.buf = &code;
+ io.n = sizeof code;
+
+ if ((state = readbuf(&io, buf, n, icmp)))
+ return state;
+ else if (code)
+ return WIP_INVALID;
+ else
+ {
+ struct wip_sm_io nio = {0};
+
+ nio.buf = &icmp->exp_checksum;
+ nio.n = sizeof icmp->exp_checksum;
+ icmp->io = nio;
+ }
+
+ wip_log("got code\n");
+ icmp->checksum += code;
+ icmp->next = get_checksum;
+ *r = sizeof code;
+ return WIP_AGAIN;
+}
+
+enum wip_state wip_icmp_rx_start(struct wip *const w, const void *const buf,
+ const size_t n, struct wip_icmp *const icmp, size_t *const r)
+{
+ unsigned char type;
+ struct wip_sm_io io = {0};
+ enum wip_state state;
+
+ io.buf = &type;
+ io.n = sizeof type;
+
+ if ((state = readbuf(&io, buf, n, icmp)))
+ return state;
+ else if (type != ICMP_ECHO_REQUEST)
+ return WIP_INVALID;
+
+ wip_log("got echo request\n");
+ icmp->checksum += type;
+ icmp->type = type;
+ icmp->next = get_code;
+ *r = sizeof type;
+ return WIP_AGAIN;
+}
diff --git a/src/protocol/icmp/tx.c b/src/protocol/icmp/tx.c
new file mode 100644
index 0000000..27fe876
--- /dev/null
+++ b/src/protocol/icmp/tx.c
@@ -0,0 +1,21 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/icmp.h>
+#include <wip/prv/icmp/types.h>
+#include <stddef.h>
+
+int wip_icmp_tx(struct wip *const wip, void *const buf, const size_t n,
+ void *const args)
+{
+ wip_log("%s: TODO\n");
+ return 0;
+}
diff --git a/src/protocol/tcp/CMakeLists.txt b/src/protocol/tcp/CMakeLists.txt
new file mode 100644
index 0000000..0caec18
--- /dev/null
+++ b/src/protocol/tcp/CMakeLists.txt
@@ -0,0 +1,13 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ alloc.c
+ free.c
+ rx.c
+ tx.c
+)
diff --git a/src/protocol/tcp/alloc.c b/src/protocol/tcp/alloc.c
new file mode 100644
index 0000000..c90f1c9
--- /dev/null
+++ b/src/protocol/tcp/alloc.c
@@ -0,0 +1,20 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/tcp.h>
+#include <wip/prv/tcp/types.h>
+#include <stddef.h>
+
+void *wip_tcp_alloc(struct wip *const w)
+{
+ wip_log("%s: TODO\n", __func__);
+ return NULL;
+}
diff --git a/src/protocol/tcp/free.c b/src/protocol/tcp/free.c
new file mode 100644
index 0000000..ddde038
--- /dev/null
+++ b/src/protocol/tcp/free.c
@@ -0,0 +1,19 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/tcp.h>
+#include <wip/prv/tcp/types.h>
+#include <stddef.h>
+
+void wip_tcp_free(struct wip *const w, void *const args)
+{
+ wip_log("%s: TODO\n", __func__);
+}
diff --git a/src/protocol/tcp/rx.c b/src/protocol/tcp/rx.c
new file mode 100644
index 0000000..6b23546
--- /dev/null
+++ b/src/protocol/tcp/rx.c
@@ -0,0 +1,21 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/tcp.h>
+#include <wip/prv/tcp/types.h>
+#include <stddef.h>
+
+enum wip_state wip_tcp_rx(struct wip *const wip, const void *const buf,
+ const size_t n, void *const args)
+{
+ wip_log("%s: TODO\n");
+ return WIP_OK;
+}
diff --git a/src/protocol/tcp/tx.c b/src/protocol/tcp/tx.c
new file mode 100644
index 0000000..55524e3
--- /dev/null
+++ b/src/protocol/tcp/tx.c
@@ -0,0 +1,21 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/tcp.h>
+#include <wip/prv/tcp/types.h>
+#include <stddef.h>
+
+int wip_tcp_tx(struct wip *const wip, void *const buf, const size_t n,
+ void *const args)
+{
+ wip_log("%s: TODO\n");
+ return 0;
+}
diff --git a/src/protocol/udp/CMakeLists.txt b/src/protocol/udp/CMakeLists.txt
new file mode 100644
index 0000000..0caec18
--- /dev/null
+++ b/src/protocol/udp/CMakeLists.txt
@@ -0,0 +1,13 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ alloc.c
+ free.c
+ rx.c
+ tx.c
+)
diff --git a/src/protocol/udp/alloc.c b/src/protocol/udp/alloc.c
new file mode 100644
index 0000000..d812ea5
--- /dev/null
+++ b/src/protocol/udp/alloc.c
@@ -0,0 +1,20 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/udp.h>
+#include <wip/prv/udp/types.h>
+#include <stddef.h>
+
+void *wip_udp_alloc(struct wip *const w)
+{
+ wip_log("%s: TODO\n", __func__);
+ return NULL;
+}
diff --git a/src/protocol/udp/free.c b/src/protocol/udp/free.c
new file mode 100644
index 0000000..c6354fb
--- /dev/null
+++ b/src/protocol/udp/free.c
@@ -0,0 +1,19 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/udp.h>
+#include <wip/prv/udp/types.h>
+#include <stddef.h>
+
+void wip_udp_free(struct wip *const w, void *const args)
+{
+ wip_log("%s: TODO\n", __func__);
+}
diff --git a/src/protocol/udp/rx.c b/src/protocol/udp/rx.c
new file mode 100644
index 0000000..29aa4cc
--- /dev/null
+++ b/src/protocol/udp/rx.c
@@ -0,0 +1,21 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/udp.h>
+#include <wip/prv/udp/types.h>
+#include <stddef.h>
+
+enum wip_state wip_udp_rx(struct wip *const wip, const void *const buf,
+ const size_t n, void *const args)
+{
+ wip_log("%s: TODO\n");
+ return WIP_OK;
+}
diff --git a/src/protocol/udp/tx.c b/src/protocol/udp/tx.c
new file mode 100644
index 0000000..a29ec0f
--- /dev/null
+++ b/src/protocol/udp/tx.c
@@ -0,0 +1,21 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/log.h>
+#include <wip/prv/udp.h>
+#include <wip/prv/udp/types.h>
+#include <stddef.h>
+
+int wip_udp_tx(struct wip *const wip, void *const buf, const size_t n,
+ void *const args)
+{
+ wip_log("%s: TODO\n");
+ return 0;
+}
diff --git a/src/routines/CMakeLists.txt b/src/routines/CMakeLists.txt
new file mode 100644
index 0000000..aa69a13
--- /dev/null
+++ b/src/routines/CMakeLists.txt
@@ -0,0 +1,10 @@
+# wip, a small TCP/IP stack.
+# Copyright (C) 2025 Xavier Del Campo Romero
+#
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+target_sources(${PROJECT_NAME} PRIVATE
+ rx.c
+)
diff --git a/src/routines/rx.c b/src/routines/rx.c
new file mode 100644
index 0000000..b031d14
--- /dev/null
+++ b/src/routines/rx.c
@@ -0,0 +1,447 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/io.h>
+#include <wip/prv/log.h>
+#include <wip/prv/icmp.h>
+#include <wip/prv/tcp.h>
+#include <wip/prv/udp.h>
+#include <wip/prv/routines.h>
+#include <stddef.h>
+
+#include <time.h>
+
+unsigned valid_packets;
+
+enum {MIN_HEADER_SZ = 20};
+
+static const struct wip_prot_ops protocols[] =
+{
+ {
+ WIP_P_ICMP,
+ wip_icmp_alloc,
+ wip_icmp_free,
+ wip_icmp_rx,
+ wip_icmp_tx
+ },
+
+ {
+ WIP_P_TCP,
+ wip_tcp_alloc,
+ wip_tcp_free,
+ wip_tcp_rx,
+ wip_tcp_tx
+ },
+
+ {
+ WIP_P_UDP,
+ wip_udp_alloc,
+ wip_udp_free,
+ wip_udp_rx,
+ wip_udp_tx
+ }
+};
+
+static enum wip_state get_data(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io *const io = &h->io;
+ const unsigned short dlen = wip_be16(&h->len) - h->header_sz;
+ const struct wip_prot_ops *const p = h->prot;
+ enum wip_state n;
+
+ if ((n = wip_io_read(w, io)))
+ return n;
+ else if (p && (n = p->rx(w, rx->buf, io->n, h->args)))
+ return n;
+
+#if 0
+ wip_log("got new data, h->i=%lu, io->n=%lu, dlen=%u\n",
+ (unsigned long)h->i, (unsigned long)io->n, dlen);
+#endif
+
+ if ((h->i += io->n) >= dlen)
+ {
+#if 1
+ wip_log("finished getting data (%u bytes)\n", (unsigned)dlen);
+#endif
+ if (h->prot)
+ {
+ h->prot->free(w, h->args);
+ h->prot = h->args = NULL;
+ }
+
+ wip_rx(w);
+ valid_packets++;
+ }
+ else
+ {
+ struct wip_sm_io nio = {0};
+ const size_t n = dlen - h->i;
+
+#if 0
+ wip_log("still need more data! (%u bytes)\n", n);
+#endif
+
+ nio.buf = rx->buf;
+ nio.n = n > sizeof rx->buf ? sizeof rx->buf : n;
+ *io = nio;
+ }
+
+ return WIP_OK;
+}
+
+static enum wip_state get_options(struct wip *const w)
+{
+ unsigned char b;
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io io = {0};
+ enum wip_state n;
+
+ io.buf = &b;
+ io.n = sizeof b;
+
+ if ((n = wip_io_read(w, &io)))
+ return n;
+ else if (++h->i >= h->header_sz - MIN_HEADER_SZ)
+ {
+ struct wip_sm_io nio = {0};
+ const unsigned short dlen = wip_be16(&h->len);
+ const size_t n = dlen - h->i;
+
+ nio.buf = rx->buf;
+ nio.n = n > sizeof rx->buf ? sizeof rx->buf : n;
+ h->io = nio;
+ h->i = 0;
+#if 0
+ wip_log("%s: will read %lu bytes of data\n", __func__, (unsigned long)h->io.n);
+#endif
+ rx->next = get_data;
+ }
+
+ h->chksum += b;
+ return WIP_OK;
+}
+
+static enum wip_state get_dst(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ const enum wip_state n = wip_io_read(w, &h->io);
+
+ if (n)
+ return n;
+
+#if 0
+ wip_log("got dst address=%hhu.%hhu.%hhu.%hhu\n",
+ h->src.v[0], h->src.v[1], h->src.v[2], h->src.v[3]);
+#endif
+ h->chksum += wip_be32(&h->dst);
+
+ if (h->header_sz > MIN_HEADER_SZ)
+ {
+#if 0
+ wip_log("will parse options\n");
+#endif
+ rx->next = get_options;
+ }
+ else
+ {
+ struct wip_sm_io io = {0};
+ const size_t n = wip_be16(&h->len) - h->header_sz;
+
+ io.buf = rx->buf;
+ io.n = n >= sizeof rx->buf ? sizeof rx->buf : n;
+#if 0
+ wip_log("%s: expecting %lu bytes of data\n", __func__, (unsigned long)io.n);
+#endif
+ h->i = 0;
+ h->io = io;
+ rx->next = get_data;
+ }
+
+ return WIP_OK;
+}
+
+static enum wip_state get_src(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ const enum wip_state n = wip_io_read(w, &h->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &h->dst;
+ io.n = sizeof h->dst;
+ h->io = io;
+ }
+
+#if 0
+ wip_log("got source address=%hhu.%hhu.%hhu.%hhu\n",
+ h->src.v[0], h->src.v[1], h->src.v[2], h->src.v[3]);
+#endif
+ h->chksum += wip_be32(&h->src);
+ rx->next = get_dst;
+ return WIP_OK;
+}
+
+static enum wip_state get_chksum(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ const enum wip_state n = wip_io_read(w, &h->io);
+
+ if (n)
+ return n;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &h->src;
+ io.n = sizeof h->src;
+ h->io = io;
+ }
+
+#if 0
+ wip_log("got expected checksum=%hu\n", wip_be16(&h->rchksum));
+#endif
+ rx->next = get_src;
+ return WIP_OK;
+}
+
+static const struct wip_prot_ops *get_ops(const unsigned char protocol)
+{
+ size_t i;
+
+ for (i = 0; i < sizeof protocols / sizeof *protocols; i++)
+ {
+ const struct wip_prot_ops *const p = &protocols[i];
+
+ if (protocol == p->id)
+ return p;
+ }
+
+ return NULL;
+}
+
+static enum wip_state get_protocol(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io io = {0};
+ enum wip_state n;
+
+ io.buf = &h->protocol;
+ io.n = sizeof h->protocol;
+
+ if ((n = wip_io_read(w, &io)))
+ return n;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &h->rchksum;
+ io.n = sizeof h->rchksum;
+ h->io = io;
+ }
+
+#if 0
+ wip_log("got protocol: %#hhx\n", h->protocol);
+#endif
+ if ((h->prot = get_ops(h->protocol))
+ && !(h->args = h->prot->alloc(w)))
+ return WIP_FATAL;
+
+ h->chksum += h->protocol;
+ rx->next = get_chksum;
+ return WIP_OK;
+}
+
+static enum wip_state get_ttl(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io io = {0};
+ enum wip_state n;
+
+ io.buf = &h->ttl;
+ io.n = sizeof h->ttl;
+
+ if ((n = wip_io_read(w, &io)))
+ return n;
+
+#if 0
+ wip_log("got TTL\n");
+#endif
+ h->chksum += h->ttl;
+ rx->next = get_protocol;
+ return WIP_OK;
+}
+
+static enum wip_state get_flags_offset(struct wip *const w)
+{
+ unsigned char flags;
+ unsigned short fl_off;
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ const enum wip_state n = wip_io_read(w, &h->io);
+
+ if (n)
+ return n;
+
+ fl_off = wip_be16(&h->fl_off);
+
+ if ((flags = fl_off >> 13) & 1)
+ return WIP_INVALID;
+
+#if 0
+ wip_log("got flags offset: %hu\n", fl_off);
+#endif
+ h->chksum += fl_off;
+ rx->next = get_ttl;
+ return WIP_OK;
+}
+
+static enum wip_state get_id(struct wip *const w)
+{
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ const enum wip_state n = wip_io_read(w, &h->io);
+
+ /* TODO: use ID? */
+ if (n)
+ return n;
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &h->fl_off;
+ io.n = sizeof h->fl_off;
+ h->io = io;
+ }
+
+#if 0
+ wip_log("got id: %hu\n", wip_be16(&h->id));
+#endif
+ h->chksum += wip_be16(&h->id);
+ rx->next = get_flags_offset;
+ return WIP_OK;
+}
+
+static enum wip_state get_len(struct wip *const w)
+{
+ unsigned short len;
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ const enum wip_state n = wip_io_read(w, &h->io);
+
+ if (n)
+ return n;
+ else if ((len = wip_be16(&h->len)) < MIN_HEADER_SZ)
+ return WIP_INVALID;
+ else if (len > sizeof rx->buf)
+ {
+#if 0
+ wip_log("data too big: %hu (max %lu)\n", len,
+ (unsigned long)sizeof rx->buf);
+#endif
+ return WIP_INVALID;
+ }
+ else
+ {
+ struct wip_sm_io io = {0};
+
+ io.buf = &h->id;
+ io.n = sizeof h->id;
+ h->io = io;
+ }
+
+#if 0
+ wip_log("got length: %hu\n", len);
+#endif
+ h->chksum = len;
+ rx->next = get_id;
+ return WIP_OK;
+}
+
+static enum wip_state get_dscp_ecn(struct wip *const w)
+{
+ unsigned char b;
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io io = {0};
+ enum wip_state n;
+
+ io.buf = &b;
+ io.n = sizeof b;
+
+ /* TODO: support DSCP and ECN? */
+ if ((n = wip_io_read(w, &io)))
+ return n;
+ else
+ {
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io io = {0};
+
+ io.buf = &h->len;
+ io.n = sizeof h->len;
+ h->io = io;
+ }
+
+#if 0
+ wip_log("got DSCP and ECN\n");
+#endif
+ h->chksum += b;
+ rx->next = get_len;
+ return WIP_OK;
+}
+
+static enum wip_state get_version_ihl(struct wip *const w)
+{
+ enum {VERSION = 4};
+ unsigned char b, version, ihl;
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const h = &rx->sm.h;
+ struct wip_sm_io io = {0};
+ enum wip_state n;
+
+ io.buf = &b;
+ io.n = sizeof b;
+
+ if ((n = wip_io_read(w, &io)))
+ return n;
+ else if ((version = b >> 4) != 4
+ || (ihl = b & 0xf) < MIN_HEADER_SZ / 4)
+ return WIP_INVALID;
+
+#if 0
+ wip_log("got IPv4 header, length=%u\n", ihl * 4);
+#endif
+ h->chksum += b;
+ h->header_sz = ihl * 4;
+ rx->next = get_dscp_ecn;
+ return WIP_OK;
+}
+
+void wip_rx(struct wip *const w)
+{
+ const struct wip_rx_sm_h h = {0};
+ struct wip_rx *const rx = &w->rx;
+ struct wip_rx_sm_h *const ph = &rx->sm.h;
+
+ *ph = h;
+ rx->next = get_version_ihl;
+}
diff --git a/src/run.c b/src/run.c
new file mode 100644
index 0000000..8ef952f
--- /dev/null
+++ b/src/run.c
@@ -0,0 +1,49 @@
+/*
+ * wip, a small TCP/IP stack.
+ * Copyright (C) 2025 Xavier Del Campo Romero
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+#include <wip/wip.h>
+#include <wip/prv/routines.h>
+
+int wip_run(struct wip *const w)
+{
+ enum wip_state n;
+
+again:
+
+ switch ((n = w->rx.next(w)))
+ {
+ case WIP_OK:
+ goto again;
+
+ case WIP_AGAIN:
+ break;
+
+ case WIP_FATAL:
+ return -1;
+
+ case WIP_INVALID:
+ wip_rx(w);
+ break;
+ }
+
+#if 0
+ switch (w->tx.next(w))
+ {
+ case WIP_OK:
+ case WIP_AGAIN:
+ break;
+
+ case WIP_INVALID:
+ case WIP_FATAL:
+ return -1;
+ }
+#endif
+
+ return 0;
+}