summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuke T. Shumaker <lukeshu@lukeshu.com>2025-04-02 16:41:26 -0600
committerLuke T. Shumaker <lukeshu@lukeshu.com>2025-04-02 18:42:19 -0600
commit24a331cb177327b3b4a30064150cd99c469a1d11 (patch)
treed2778fe1726d70355be69908f7a28f496230484a
parentc077d93a390be2b00e95b3c91adf3c23bc2d0f27 (diff)
lib9p: Split internal.h up, rename some CONFIG symbols
-rw-r--r--cmd/sbc_harness/config/config.h26
-rw-r--r--lib9p/9p.c150
-rw-r--r--lib9p/9p.generated.c4
-rw-r--r--lib9p/CMakeLists.txt1
-rw-r--r--lib9p/include/lib9p/9p.h5
-rw-r--r--lib9p/internal.h99
-rw-r--r--lib9p/map.h2
-rw-r--r--lib9p/protogen/c.py4
-rw-r--r--lib9p/srv.c30
-rw-r--r--lib9p/tables.c165
-rw-r--r--lib9p/tables.h44
-rw-r--r--lib9p/tests/test_compile_config/config.h28
-rw-r--r--lib9p/tests/test_server/config/config.h24
-rw-r--r--lib9p/utf8.h34
-rw-r--r--libmisc/include/libmisc/endian.h1
15 files changed, 320 insertions, 297 deletions
diff --git a/cmd/sbc_harness/config/config.h b/cmd/sbc_harness/config/config.h
index fd089b1..da3edad 100644
--- a/cmd/sbc_harness/config/config.h
+++ b/cmd/sbc_harness/config/config.h
@@ -32,6 +32,8 @@
/* 9P *************************************************************************/
+#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */
+
/**
* This max-msg-size is sized so that a Twrite message can return
* 8KiB of data.
@@ -49,22 +51,22 @@
* negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to
* (8*1024)+160 in 2e and 3e.
*/
-#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24)
+#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24)
/**
* Maximum host-data-structure size. A message may be larger in
* unmarshaled-host-structures than marshaled-net-bytes due to (1)
- * struct padding, (2) nul-terminator byes for strings.
+ * struct padding, (2) array pointers.
*/
-#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16
-#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */
-#define CONFIG_9P_SRV_MAX_FIDS 16
-#define CONFIG_9P_SRV_MAX_REQS 2
-#define CONFIG_9P_SRV_MAX_DEPTH 3
-#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */
+#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16
+#define CONFIG_9P_SRV_MAX_FIDS 16
+#define CONFIG_9P_SRV_MAX_REQS 2
+#define CONFIG_9P_SRV_MAX_DEPTH 3
+
+#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */
/* DHCP ***********************************************************************/
diff --git a/lib9p/9p.c b/lib9p/9p.c
index c11bcd6..e90ef30 100644
--- a/lib9p/9p.c
+++ b/lib9p/9p.c
@@ -9,32 +9,10 @@
#include <stdio.h> /* for vsnprintf() */
#include <string.h> /* for strncpy() */
-#define LOG_NAME 9P
-#include <libmisc/log.h> /* for const_byte_str() */
-
#include <lib9p/9p.h>
-#include "internal.h"
-
/* strings ********************************************************************/
-const char *lib9p_version_str(enum lib9p_version ver) {
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wtype-limits"
- assert(0 <= ver && ver < LIB9P_VER_NUM);
-#pragma GCC diagnostic pop
- return _lib9p_table_ver_name[ver];
-}
-
-const char *lib9p_msgtype_str(enum lib9p_version ver, enum lib9p_msg_type typ) {
-#pragma GCC diagnostic push
-#pragma GCC diagnostic ignored "-Wtype-limits"
- assert(0 <= ver && ver < LIB9P_VER_NUM);
- assert(0 <= typ && typ <= 0xFF);
-#pragma GCC diagnostic pop
- return _lib9p_table_msg_name[ver][typ] ?: const_byte_str(typ);
-}
-
struct lib9p_s lib9p_str(char *s) {
if (!s)
return (struct lib9p_s){0};
@@ -114,131 +92,3 @@ int lib9p_errorf(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *f
return -1;
}
-
-/* main message functions *****************************************************/
-
-static
-ssize_t _lib9p_validate(uint8_t xxx_low_typ_bit,
- const char *xxx_errmsg,
- const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80],
- struct lib9p_ctx *ctx, uint8_t *net_bytes) {
- /* Inspect the first 5 bytes ourselves. */
- uint32_t net_size = uint32le_decode(net_bytes);
- if (net_size < 5)
- return lib9p_error(ctx, LINUX_EBADMSG, "message is impossibly short");
- uint8_t typ = net_bytes[4];
- if (typ % 2 != xxx_low_typ_bit)
- return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "%s: message_type=%s", xxx_errmsg,
- lib9p_msgtype_str(ctx->version, typ));
- struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2];
- if (!tentry.validate)
- return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "unknown message type: %s (protocol_version=%s)",
- lib9p_msgtype_str(ctx->version, typ), lib9p_version_str(ctx->version));
-
- /* Now use the message-type-specific tentry to process the whole thing. */
- return tentry.validate(ctx, net_size, net_bytes);
-}
-
-ssize_t lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) {
- return _lib9p_validate(0, "expected a T-message but got an R-message", _lib9p_table_Tmsg_recv,
- ctx, net_bytes);
-}
-
-ssize_t lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) {
- return _lib9p_validate(1, "expected an R-message but got a T-message", _lib9p_table_Rmsg_recv,
- ctx, net_bytes);
-}
-
-static
-void _lib9p_unmarshal(const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80],
- struct lib9p_ctx *ctx, uint8_t *net_bytes,
- enum lib9p_msg_type *ret_typ, void *ret_body) {
- enum lib9p_msg_type typ = net_bytes[4];
- *ret_typ = typ;
- struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2];
-
- tentry.unmarshal(ctx, net_bytes, ret_body);
-}
-
-void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
- enum lib9p_msg_type *ret_typ, void *ret_body) {
- _lib9p_unmarshal(_lib9p_table_Tmsg_recv,
- ctx, net_bytes, ret_typ, ret_body);
-}
-
-void lib9p_Rmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
- enum lib9p_msg_type *ret_typ, void *ret_body) {
- _lib9p_unmarshal(_lib9p_table_Rmsg_recv,
- ctx, net_bytes, ret_typ, ret_body);
-}
-
-static
-bool _lib9p_marshal(const struct _lib9p_send_tentry xxx_table[LIB9P_VER_NUM][0x80],
- struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
- size_t *ret_iov_cnt, struct iovec *ret_iov, uint8_t *ret_copied) {
- struct _marshal_ret ret = {
- .net_iov_cnt = 1,
- .net_iov = ret_iov,
- .net_copied_size = 0,
- .net_copied = ret_copied,
- };
-
- struct _lib9p_send_tentry tentry = xxx_table[ctx->version][typ/2];
- bool ret_erred = tentry.marshal(ctx, body, &ret);
- if (ret_iov[ret.net_iov_cnt-1].iov_len == 0)
- ret.net_iov_cnt--;
- *ret_iov_cnt = ret.net_iov_cnt;
- return ret_erred;
-}
-
-bool lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
- struct lib9p_Tmsg_send_buf *ret) {
- assert(typ % 2 == 0);
- memset(ret, 0, sizeof(*ret));
- return _lib9p_marshal(_lib9p_table_Tmsg_send,
- ctx, typ, body,
- &ret->iov_cnt, ret->iov, ret->copied);
-}
-
-bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
- struct lib9p_Rmsg_send_buf *ret) {
- assert(typ % 2 == 1);
- memset(ret, 0, sizeof(*ret));
- return _lib9p_marshal(_lib9p_table_Rmsg_send,
- ctx, typ, body,
- &ret->iov_cnt, ret->iov, ret->copied);
-}
-
-/* `struct lib9p_stat` helpers ************************************************/
-
-bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes,
- uint32_t *ret_net_size, ssize_t *ret_host_size) {
- ssize_t host_size = _lib9p_stat_validate(ctx, net_size, net_bytes, ret_net_size);
- if (host_size < 0)
- return true;
- if (ret_host_size)
- *ret_host_size = host_size;
- return false;
-}
-
-void lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
- struct lib9p_stat *ret) {
- _lib9p_stat_unmarshal(ctx, net_bytes, ret);
-}
-
-uint32_t lib9p_stat_marshal(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj,
- uint8_t *ret_bytes) {
- struct lib9p_ctx _ctx = *ctx;
- _ctx.max_msg_size = max_net_size;
-
- struct iovec iov = {0};
- struct _marshal_ret ret = {
- .net_iov_cnt = 1,
- .net_iov = &iov,
- .net_copied_size = 0,
- .net_copied = ret_bytes,
- };
- if (_lib9p_stat_marshal(&_ctx, obj, &ret))
- return 0;
- return ret.net_iov[0].iov_len;
-}
diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c
index 3031392..2549b87 100644
--- a/lib9p/9p.generated.c
+++ b/lib9p/9p.generated.c
@@ -6,10 +6,12 @@
#include <string.h> /* for memset() */
#include <libmisc/assert.h>
+#include <libmisc/endian.h>
#include <lib9p/9p.h>
-#include "internal.h"
+#include "tables.h"
+#include "utf8.h"
/* utilities ******************************************************************/
#if CONFIG_9P_ENABLE_9P2000
diff --git a/lib9p/CMakeLists.txt b/lib9p/CMakeLists.txt
index 707308e..9240aa3 100644
--- a/lib9p/CMakeLists.txt
+++ b/lib9p/CMakeLists.txt
@@ -8,6 +8,7 @@ target_include_directories(lib9p SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/in
target_sources(lib9p INTERFACE
9p.generated.c
9p.c
+ tables.c
srv.c
)
target_link_libraries(lib9p INTERFACE
diff --git a/lib9p/include/lib9p/9p.h b/lib9p/include/lib9p/9p.h
index ffac453..4cdb997 100644
--- a/lib9p/include/lib9p/9p.h
+++ b/lib9p/include/lib9p/9p.h
@@ -18,6 +18,7 @@
#ifndef CONFIG_9P_MAX_ERR_SIZE
#error config.h must define CONFIG_9P_MAX_ERR_SIZE
#endif
+static_assert(CONFIG_9P_MAX_ERR_SIZE <= UINT16_MAX);
/* constants ******************************************************************/
@@ -60,6 +61,10 @@ int lib9p_error(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *ms
/** Write a printf-style error into ctx, return -1. */
int lib9p_errorf(struct lib9p_ctx *ctx, lib9p_errno_t linux_errno, char const *fmt, ...) [[gnu::format(printf, 3, 4)]];
+/* misc utilities *************************************************************/
+
+uint32_t lib9p_version_min_msg_size(enum lib9p_version);
+
/* main T-message functions ***************************************************/
/**
diff --git a/lib9p/internal.h b/lib9p/internal.h
deleted file mode 100644
index 51bf792..0000000
--- a/lib9p/internal.h
+++ /dev/null
@@ -1,99 +0,0 @@
-/* lib9p/internal.h - Internal machinery shared between parts of lib9p
- *
- * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- */
-
-#ifndef _LIB9P_INTERNAL_H_
-#define _LIB9P_INTERNAL_H_
-
-#include <stddef.h> /* for size_t */
-#include <limits.h> /* for SSIZE_MAX, not set by newlib */
-#ifndef SSIZE_MAX
-#define SSIZE_MAX (SIZE_MAX >> 1)
-#endif
-
-#include <libmisc/endian.h>
-#include <libmisc/macro.h>
-
-#include <lib9p/9p.h>
-
-/* configuration **************************************************************/
-
-#include "config.h"
-
-#ifndef CONFIG_9P_MAX_MSG_SIZE
- #error config.h must define CONFIG_9P_MAX_MSG_SIZE
-#endif
-#ifndef CONFIG_9P_MAX_HOSTMSG_SIZE
- #error config.h must define CONFIG_9P_MAX_HOSTMSG_SIZE
-#endif
-#ifndef CONFIG_9P_MAX_ERR_SIZE
- #error config.h must define CONFIG_9P_MAX_ERR_SIZE
-#endif
-
-static_assert(CONFIG_9P_MAX_ERR_SIZE <= UINT16_MAX);
-static_assert(CONFIG_9P_MAX_MSG_SIZE <= CONFIG_9P_MAX_HOSTMSG_SIZE);
-static_assert(CONFIG_9P_MAX_HOSTMSG_SIZE <= SSIZE_MAX);
-
-/* tables / exports ***********************************************************/
-
-typedef ssize_t (*_validate_fn_t)(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes);
-typedef void (*_unmarshal_fn_t)(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out);
-
-struct _marshal_ret {
- size_t net_iov_cnt;
- struct iovec *net_iov;
- size_t net_copied_size;
- uint8_t *net_copied;
-};
-typedef bool (*_marshal_fn_t)(struct lib9p_ctx *ctx, void *host_val, struct _marshal_ret *ret);
-
-struct _lib9p_recv_tentry {
- _validate_fn_t validate;
- _unmarshal_fn_t unmarshal;
-};
-
-struct _lib9p_send_tentry {
- _marshal_fn_t marshal;
-};
-
-extern const char *const _lib9p_table_ver_name[LIB9P_VER_NUM];
-extern const char *const _lib9p_table_msg_name[LIB9P_VER_NUM][0x100];
-extern const uint32_t _lib9p_table_msg_min_size[LIB9P_VER_NUM];
-extern const struct _lib9p_recv_tentry _lib9p_table_Tmsg_recv[LIB9P_VER_NUM][0x80];
-extern const struct _lib9p_recv_tentry _lib9p_table_Rmsg_recv[LIB9P_VER_NUM][0x80];
-extern const struct _lib9p_send_tentry _lib9p_table_Tmsg_send[LIB9P_VER_NUM][0x80];
-extern const struct _lib9p_send_tentry _lib9p_table_Rmsg_send[LIB9P_VER_NUM][0x80];
-
-ssize_t _lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size);
-void _lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out);
-bool _lib9p_stat_marshal(struct lib9p_ctx *ctx, struct lib9p_stat *val, struct _marshal_ret *ret);
-
-/* unmarshal utilities ********************************************************/
-
-static inline bool _is_valid_utf8(uint8_t *str, size_t len, bool forbid_nul) {
- uint32_t ch;
- uint8_t chlen;
- assert(str);
- for (size_t pos = 0; pos < len;) {
- if ((str[pos] & 0b10000000) == 0b00000000) { ch = str[pos] & 0b01111111; chlen = 1; }
- else if ((str[pos] & 0b11100000) == 0b11000000) { ch = str[pos] & 0b00011111; chlen = 2; }
- else if ((str[pos] & 0b11110000) == 0b11100000) { ch = str[pos] & 0b00001111; chlen = 3; }
- else if ((str[pos] & 0b11111000) == 0b11110000) { ch = str[pos] & 0b00000111; chlen = 4; }
- else return false;
- if ((ch == 0 && (chlen != 1 || forbid_nul)) || pos + chlen > len) return false;
- for (uint8_t i = 1; i < chlen; i++) {
- if ((str[pos+i] & 0b11000000) != 0b10000000) return false;
- ch = (ch << 6) | (str[pos+i] & 0b00111111);
- }
- if (ch > 0x10FFFF) return false;
- pos += chlen;
- }
- return true;
-}
-
-#define is_valid_utf8(str, len) _is_valid_utf8(str, len, false)
-#define is_valid_utf8_without_nul(str, len) _is_valid_utf8(str, len, true)
-
-#endif /* _LIB9P_INTERNAL_H_ */
diff --git a/lib9p/map.h b/lib9p/map.h
index ab9564f..c5eab0f 100644
--- a/lib9p/map.h
+++ b/lib9p/map.h
@@ -4,8 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include "internal.h"
-
/**
* `#define` `NAME`, `KEY_T`, `VAL_T`, and `CAP`; then `#include
* "map.h".
diff --git a/lib9p/protogen/c.py b/lib9p/protogen/c.py
index 0c9b21f..c487c03 100644
--- a/lib9p/protogen/c.py
+++ b/lib9p/protogen/c.py
@@ -29,10 +29,12 @@ def gen_c(versions: set[str], typs: list[idl.UserType]) -> str:
#include <string.h> /* for memset() */
#include <libmisc/assert.h>
+#include <libmisc/endian.h>
#include <lib9p/9p.h>
-#include "internal.h"
+#include "tables.h"
+#include "utf8.h"
"""
# utilities ################################################################
diff --git a/lib9p/srv.c b/lib9p/srv.c
index a29a4cb..2723207 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -6,12 +6,18 @@
#include <alloca.h>
#include <inttypes.h> /* for PRI* */
+#include <stddef.h> /* for size_t */
+#include <limits.h> /* for SSIZE_MAX, not set by newlib */
+#ifndef SSIZE_MAX
+#define SSIZE_MAX (SIZE_MAX >> 1)
+#endif
#include <libcr/coroutine.h>
#include <libcr_ipc/chan.h>
#include <libcr_ipc/mutex.h>
#include <libcr_ipc/select.h>
#include <libmisc/assert.h>
+#include <libmisc/endian.h>
#include <libhw/generic/net.h>
#define LOG_NAME 9P_SRV
@@ -20,7 +26,9 @@
#define IMPLEMENTATION_FOR_LIB9P_SRV_H YES
#include <lib9p/srv.h>
-#include "internal.h"
+/* config *********************************************************************/
+
+#include "config.h"
#ifndef CONFIG_9P_SRV_MAX_FIDS
#error config.h must define CONFIG_9P_SRV_MAX_FIDS
@@ -32,6 +40,14 @@
/* 1=just the root dir, 2=just files in the root dir, 3=1 subdir, ... */
#error config.h must define CONFIG_9P_SRV_MAX_DEPTH
#endif
+#ifndef CONFIG_9P_SRV_MAX_MSG_SIZE
+ #error config.h must define CONFIG_9P_SRV_MAX_MSG_SIZE
+#endif
+#ifndef CONFIG_9P_SRV_MAX_HOSTMSG_SIZE
+ #error config.h must define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE
+#endif
+static_assert(CONFIG_9P_SRV_MAX_MSG_SIZE <= CONFIG_9P_SRV_MAX_HOSTMSG_SIZE);
+static_assert(CONFIG_9P_SRV_MAX_HOSTMSG_SIZE <= SSIZE_MAX);
/* context ********************************************************************/
@@ -218,7 +234,7 @@ static void handle_message(struct _lib9p_srv_req *ctx);
srv->readers++;
- uint32_t initial_rerror_overhead = _lib9p_table_msg_min_size[LIB9P_VER_unknown];
+ uint32_t initial_rerror_overhead = lib9p_version_min_msg_size(LIB9P_VER_unknown);
for (;;) {
struct _srv_conn conn = {
@@ -238,7 +254,7 @@ static void handle_message(struct _lib9p_srv_req *ctx);
struct _srv_sess sess = {
.parent_conn = &conn,
.version = LIB9P_VER_unknown,
- .max_msg_size = CONFIG_9P_MAX_MSG_SIZE,
+ .max_msg_size = CONFIG_9P_SRV_MAX_MSG_SIZE,
.rerror_overhead = initial_rerror_overhead,
.initialized = false,
};
@@ -397,7 +413,7 @@ static tmessage_handler tmessage_handlers[0x100] = {
static void handle_message(struct _lib9p_srv_req *ctx) {
uint8_t *host_req = NULL;
- uint8_t host_resp[CONFIG_9P_MAX_HOSTMSG_SIZE];
+ uint8_t host_resp[CONFIG_9P_SRV_MAX_HOSTMSG_SIZE];
/* Unmarshal it. */
ssize_t host_size = lib9p_Tmsg_validate(&ctx->ctx.basectx, ctx->net_bytes);
@@ -573,7 +589,7 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx,
#endif
}
- uint32_t min_msg_size = _lib9p_table_msg_min_size[version];
+ uint32_t min_msg_size = lib9p_version_min_msg_size(version);
if (req->max_msg_size < min_msg_size) {
lib9p_errorf(&ctx->ctx.basectx,
LINUX_EDOM, "requested max_msg_size is less than minimum for %s (%"PRIu32" < %"PRIu32")",
@@ -582,8 +598,8 @@ static void handle_Tversion(struct _lib9p_srv_req *ctx,
}
resp->version = lib9p_str((char *)lib9p_version_str(version)); /* cast to discard "const" qualifier */
- resp->max_msg_size = (CONFIG_9P_MAX_MSG_SIZE < req->max_msg_size)
- ? CONFIG_9P_MAX_MSG_SIZE
+ resp->max_msg_size = (CONFIG_9P_SRV_MAX_MSG_SIZE < req->max_msg_size)
+ ? CONFIG_9P_SRV_MAX_MSG_SIZE
: req->max_msg_size;
/* Close the old session. */
diff --git a/lib9p/tables.c b/lib9p/tables.c
new file mode 100644
index 0000000..34ac1af
--- /dev/null
+++ b/lib9p/tables.c
@@ -0,0 +1,165 @@
+/* lib9p/tables.c - Access tables of version and message information
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h>
+
+#include <libmisc/endian.h>
+#include <libmisc/log.h> /* for const_byte_str() */
+
+#include "tables.h"
+
+const char *lib9p_version_str(enum lib9p_version ver) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+ assert(0 <= ver && ver < LIB9P_VER_NUM);
+#pragma GCC diagnostic pop
+ return _lib9p_table_ver_name[ver];
+}
+
+uint32_t lib9p_version_min_msg_size(enum lib9p_version ver) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+ assert(0 <= ver && ver < LIB9P_VER_NUM);
+#pragma GCC diagnostic pop
+ return _lib9p_table_msg_min_size[ver];
+}
+
+const char *lib9p_msgtype_str(enum lib9p_version ver, enum lib9p_msg_type typ) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+ assert(0 <= ver && ver < LIB9P_VER_NUM);
+ assert(0 <= typ && typ <= 0xFF);
+#pragma GCC diagnostic pop
+ return _lib9p_table_msg_name[ver][typ] ?: const_byte_str(typ);
+}
+
+/* main message functions *****************************************************/
+
+static
+ssize_t _lib9p_validate(uint8_t xxx_low_typ_bit,
+ const char *xxx_errmsg,
+ const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80],
+ struct lib9p_ctx *ctx, uint8_t *net_bytes) {
+ /* Inspect the first 5 bytes ourselves. */
+ uint32_t net_size = uint32le_decode(net_bytes);
+ if (net_size < 5)
+ return lib9p_error(ctx, LINUX_EBADMSG, "message is impossibly short");
+ uint8_t typ = net_bytes[4];
+ if (typ % 2 != xxx_low_typ_bit)
+ return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "%s: message_type=%s", xxx_errmsg,
+ lib9p_msgtype_str(ctx->version, typ));
+ struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2];
+ if (!tentry.validate)
+ return lib9p_errorf(ctx, LINUX_EOPNOTSUPP, "unknown message type: %s (protocol_version=%s)",
+ lib9p_msgtype_str(ctx->version, typ), lib9p_version_str(ctx->version));
+
+ /* Now use the message-type-specific tentry to process the whole thing. */
+ return tentry.validate(ctx, net_size, net_bytes);
+}
+
+ssize_t lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) {
+ return _lib9p_validate(0, "expected a T-message but got an R-message", _lib9p_table_Tmsg_recv,
+ ctx, net_bytes);
+}
+
+ssize_t lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) {
+ return _lib9p_validate(1, "expected an R-message but got a T-message", _lib9p_table_Rmsg_recv,
+ ctx, net_bytes);
+}
+
+static
+void _lib9p_unmarshal(const struct _lib9p_recv_tentry xxx_table[LIB9P_VER_NUM][0x80],
+ struct lib9p_ctx *ctx, uint8_t *net_bytes,
+ enum lib9p_msg_type *ret_typ, void *ret_body) {
+ enum lib9p_msg_type typ = net_bytes[4];
+ *ret_typ = typ;
+ struct _lib9p_recv_tentry tentry = xxx_table[ctx->version][typ/2];
+
+ tentry.unmarshal(ctx, net_bytes, ret_body);
+}
+
+void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
+ enum lib9p_msg_type *ret_typ, void *ret_body) {
+ _lib9p_unmarshal(_lib9p_table_Tmsg_recv,
+ ctx, net_bytes, ret_typ, ret_body);
+}
+
+void lib9p_Rmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
+ enum lib9p_msg_type *ret_typ, void *ret_body) {
+ _lib9p_unmarshal(_lib9p_table_Rmsg_recv,
+ ctx, net_bytes, ret_typ, ret_body);
+}
+
+static
+bool _lib9p_marshal(const struct _lib9p_send_tentry xxx_table[LIB9P_VER_NUM][0x80],
+ struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
+ size_t *ret_iov_cnt, struct iovec *ret_iov, uint8_t *ret_copied) {
+ struct _marshal_ret ret = {
+ .net_iov_cnt = 1,
+ .net_iov = ret_iov,
+ .net_copied_size = 0,
+ .net_copied = ret_copied,
+ };
+
+ struct _lib9p_send_tentry tentry = xxx_table[ctx->version][typ/2];
+ bool ret_erred = tentry.marshal(ctx, body, &ret);
+ if (ret_iov[ret.net_iov_cnt-1].iov_len == 0)
+ ret.net_iov_cnt--;
+ *ret_iov_cnt = ret.net_iov_cnt;
+ return ret_erred;
+}
+
+bool lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
+ struct lib9p_Tmsg_send_buf *ret) {
+ assert(typ % 2 == 0);
+ memset(ret, 0, sizeof(*ret));
+ return _lib9p_marshal(_lib9p_table_Tmsg_send,
+ ctx, typ, body,
+ &ret->iov_cnt, ret->iov, ret->copied);
+}
+
+bool lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
+ struct lib9p_Rmsg_send_buf *ret) {
+ assert(typ % 2 == 1);
+ memset(ret, 0, sizeof(*ret));
+ return _lib9p_marshal(_lib9p_table_Rmsg_send,
+ ctx, typ, body,
+ &ret->iov_cnt, ret->iov, ret->copied);
+}
+
+/* `struct lib9p_stat` helpers ************************************************/
+
+bool lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes,
+ uint32_t *ret_net_size, ssize_t *ret_host_size) {
+ ssize_t host_size = _lib9p_stat_validate(ctx, net_size, net_bytes, ret_net_size);
+ if (host_size < 0)
+ return true;
+ if (ret_host_size)
+ *ret_host_size = host_size;
+ return false;
+}
+
+void lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes,
+ struct lib9p_stat *ret) {
+ _lib9p_stat_unmarshal(ctx, net_bytes, ret);
+}
+
+uint32_t lib9p_stat_marshal(struct lib9p_ctx *ctx, uint32_t max_net_size, struct lib9p_stat *obj,
+ uint8_t *ret_bytes) {
+ struct lib9p_ctx _ctx = *ctx;
+ _ctx.max_msg_size = max_net_size;
+
+ struct iovec iov = {0};
+ struct _marshal_ret ret = {
+ .net_iov_cnt = 1,
+ .net_iov = &iov,
+ .net_copied_size = 0,
+ .net_copied = ret_bytes,
+ };
+ if (_lib9p_stat_marshal(&_ctx, obj, &ret))
+ return 0;
+ return ret.net_iov[0].iov_len;
+}
diff --git a/lib9p/tables.h b/lib9p/tables.h
new file mode 100644
index 0000000..5591e4b
--- /dev/null
+++ b/lib9p/tables.h
@@ -0,0 +1,44 @@
+/* lib9p/tables.h - Declare tables of version and message information
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIB9P_TABLES_H_
+#define _LIB9P_TABLES_H_
+
+#include <lib9p/9p.h>
+
+typedef ssize_t (*_validate_fn_t)(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes);
+typedef void (*_unmarshal_fn_t)(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out);
+
+struct _marshal_ret {
+ size_t net_iov_cnt;
+ struct iovec *net_iov;
+ size_t net_copied_size;
+ uint8_t *net_copied;
+};
+typedef bool (*_marshal_fn_t)(struct lib9p_ctx *ctx, void *host_val, struct _marshal_ret *ret);
+
+struct _lib9p_recv_tentry {
+ _validate_fn_t validate;
+ _unmarshal_fn_t unmarshal;
+};
+
+struct _lib9p_send_tentry {
+ _marshal_fn_t marshal;
+};
+
+extern const char *const _lib9p_table_ver_name[LIB9P_VER_NUM];
+extern const char *const _lib9p_table_msg_name[LIB9P_VER_NUM][0x100];
+extern const uint32_t _lib9p_table_msg_min_size[LIB9P_VER_NUM];
+extern const struct _lib9p_recv_tentry _lib9p_table_Tmsg_recv[LIB9P_VER_NUM][0x80];
+extern const struct _lib9p_recv_tentry _lib9p_table_Rmsg_recv[LIB9P_VER_NUM][0x80];
+extern const struct _lib9p_send_tentry _lib9p_table_Tmsg_send[LIB9P_VER_NUM][0x80];
+extern const struct _lib9p_send_tentry _lib9p_table_Rmsg_send[LIB9P_VER_NUM][0x80];
+
+ssize_t _lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes, uint32_t *ret_net_size);
+void _lib9p_stat_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, void *out);
+bool _lib9p_stat_marshal(struct lib9p_ctx *ctx, struct lib9p_stat *val, struct _marshal_ret *ret);
+
+#endif /* _LIB9P_TABLES_H_ */
diff --git a/lib9p/tests/test_compile_config/config.h b/lib9p/tests/test_compile_config/config.h
index 38ab0c0..cc8eec1 100644
--- a/lib9p/tests/test_compile_config/config.h
+++ b/lib9p/tests/test_compile_config/config.h
@@ -9,20 +9,20 @@
/* 9P *************************************************************************/
-#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24)
-#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16
-#define CONFIG_9P_MAX_ERR_SIZE 128
-#define CONFIG_9P_MAX_9P2000_e_WELEM 16
-
-#define CONFIG_9P_SRV_MAX_FIDS 16
-#define CONFIG_9P_SRV_MAX_REQS 2
-#define CONFIG_9P_SRV_MAX_DEPTH 3
-
-#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_e 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_L 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_p9p 1 /* bool */
+#define CONFIG_9P_MAX_ERR_SIZE 128
+#define CONFIG_9P_MAX_9P2000_e_WELEM 16
+
+#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24)
+#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16
+#define CONFIG_9P_SRV_MAX_FIDS 16
+#define CONFIG_9P_SRV_MAX_REQS 2
+#define CONFIG_9P_SRV_MAX_DEPTH 3
+
+#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_e 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_L 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_p9p 1 /* bool */
/* COROUTINE ******************************************************************/
diff --git a/lib9p/tests/test_server/config/config.h b/lib9p/tests/test_server/config/config.h
index afe6dc0..03143e1 100644
--- a/lib9p/tests/test_server/config/config.h
+++ b/lib9p/tests/test_server/config/config.h
@@ -12,6 +12,8 @@
/* 9P *************************************************************************/
+#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */
+
/**
* This max-msg-size is sized so that a Twrite message can return
* 8KiB of data.
@@ -29,22 +31,22 @@
* negotiated. In Plan 9 1e it was (8*1024)+128, and was bumped to
* (8*1024)+160 in 2e and 3e.
*/
-#define CONFIG_9P_MAX_MSG_SIZE ((4*1024)+24)
+#define CONFIG_9P_SRV_MAX_MSG_SIZE ((4*1024)+24)
/**
* Maximum host-data-structure size. A message may be larger in
* unmarshaled-host-structures than marshaled-net-bytes due to (1)
* struct padding, (2) array pointers.
*/
-#define CONFIG_9P_MAX_HOSTMSG_SIZE CONFIG_9P_MAX_MSG_SIZE+16
-#define CONFIG_9P_MAX_ERR_SIZE 128 /* 128 is what Plan 9 4e uses */
-#define CONFIG_9P_SRV_MAX_FIDS 16
-#define CONFIG_9P_SRV_MAX_REQS 2
-#define CONFIG_9P_SRV_MAX_DEPTH 3
-#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */
-#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */
+#define CONFIG_9P_SRV_MAX_HOSTMSG_SIZE CONFIG_9P_SRV_MAX_MSG_SIZE+16
+#define CONFIG_9P_SRV_MAX_FIDS 16
+#define CONFIG_9P_SRV_MAX_REQS 2
+#define CONFIG_9P_SRV_MAX_DEPTH 3
+
+#define CONFIG_9P_ENABLE_9P2000 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_u 1 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_e 0 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_L 0 /* bool */
+#define CONFIG_9P_ENABLE_9P2000_p9p 0 /* bool */
/* COROUTINE ******************************************************************/
diff --git a/lib9p/utf8.h b/lib9p/utf8.h
new file mode 100644
index 0000000..5ffd674
--- /dev/null
+++ b/lib9p/utf8.h
@@ -0,0 +1,34 @@
+/* lib9p/utf8.h - Internal UTF-8 validation
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIB9P_UTF8_H_
+#define _LIB9P_UTF8_H_
+
+static inline bool _is_valid_utf8(uint8_t *str, size_t len, bool forbid_nul) {
+ uint32_t ch;
+ uint8_t chlen;
+ assert(str);
+ for (size_t pos = 0; pos < len;) {
+ if ((str[pos] & 0b10000000) == 0b00000000) { ch = str[pos] & 0b01111111; chlen = 1; }
+ else if ((str[pos] & 0b11100000) == 0b11000000) { ch = str[pos] & 0b00011111; chlen = 2; }
+ else if ((str[pos] & 0b11110000) == 0b11100000) { ch = str[pos] & 0b00001111; chlen = 3; }
+ else if ((str[pos] & 0b11111000) == 0b11110000) { ch = str[pos] & 0b00000111; chlen = 4; }
+ else return false;
+ if ((ch == 0 && (chlen != 1 || forbid_nul)) || pos + chlen > len) return false;
+ for (uint8_t i = 1; i < chlen; i++) {
+ if ((str[pos+i] & 0b11000000) != 0b10000000) return false;
+ ch = (ch << 6) | (str[pos+i] & 0b00111111);
+ }
+ if (ch > 0x10FFFF) return false;
+ pos += chlen;
+ }
+ return true;
+}
+
+#define is_valid_utf8(str, len) _is_valid_utf8(str, len, false)
+#define is_valid_utf8_without_nul(str, len) _is_valid_utf8(str, len, true)
+
+#endif /* _LIB9P_UTF8_H_ */
diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h
index 665a8c8..75240fe 100644
--- a/libmisc/include/libmisc/endian.h
+++ b/libmisc/include/libmisc/endian.h
@@ -7,6 +7,7 @@
#ifndef _LIBMISC_ENDIAN_H_
#define _LIBMISC_ENDIAN_H_
+#include <stddef.h> /* for size_t */
#include <stdint.h> /* for uint{n}_t */
#include <libmisc/assert.h>