summaryrefslogtreecommitdiff
path: root/lib9p/core.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib9p/core.c')
-rw-r--r--lib9p/core.c213
1 files changed, 213 insertions, 0 deletions
diff --git a/lib9p/core.c b/lib9p/core.c
new file mode 100644
index 0000000..58fe538
--- /dev/null
+++ b/lib9p/core.c
@@ -0,0 +1,213 @@
+/* lib9p/core.c - Base 9P protocol utilities for both clients and servers
+ *
+ * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <string.h> /* for strlen(), strnlen(), strncpy(), memcmp(), memset() */
+
+#include <libmisc/assert.h> /* for assert() */
+#include <libmisc/endian.h> /* for uint32le_decode() */
+#include <libmisc/log.h> /* for const_byte_str() */
+
+#include <lib9p/core.h>
+
+#include "core_tables.h"
+
+/* strings ********************************************************************/
+
+struct lib9p_s lib9p_str(char *s) {
+ if (!s)
+ return (struct lib9p_s){};
+ return (struct lib9p_s){
+ .len = strlen(s),
+ .utf8 = s,
+ };
+}
+struct lib9p_s lib9p_strn(char *s, size_t maxlen) {
+ if (maxlen == 0 || !s)
+ return (struct lib9p_s){};
+ return (struct lib9p_s){
+ .len = strnlen(s, maxlen),
+ .utf8 = s,
+ };
+}
+struct lib9p_s lib9p_str_slice(struct lib9p_s s, uint16_t beg, uint16_t end) {
+ assert(s.len == 0 || s.utf8);
+ assert(beg <= end && end <= s.len);
+ return (struct lib9p_s){
+ .len = end - beg,
+ .utf8 = &s.utf8[beg],
+ };
+}
+bool lib9p_str_eq(struct lib9p_s a, struct lib9p_s b) {
+ return a.len == b.len &&
+ (a.len == 0 || memcmp(a.utf8, b.utf8, a.len) == 0);
+}
+
+/* bounds checks **************************************************************/
+
+static inline void assert_ver(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
+}
+
+static inline void assert_typ(enum lib9p_msg_type typ) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wtype-limits"
+ assert(0 <= typ && typ < 0xFF);
+#pragma GCC diagnostic pop
+}
+
+/* simple lookups *************************************************************/
+
+const char *lib9p_version_str(enum lib9p_version ver) {
+ assert_ver(ver);
+ return _lib9p_table_ver[ver].name;
+}
+
+uint32_t lib9p_version_min_Rerror_size(enum lib9p_version ver) {
+ assert_ver(ver);
+ return _lib9p_table_ver[ver].min_Rerror_size;
+}
+
+uint32_t lib9p_version_min_Rread_size(enum lib9p_version ver) {
+ assert_ver(ver);
+ return _lib9p_table_ver[ver].min_Rread_size;
+}
+
+const char *lib9p_msgtype_str(enum lib9p_version ver, enum lib9p_msg_type typ) {
+ assert_ver(ver);
+ assert_typ(typ);
+ return _lib9p_table_msg[ver][typ].name ?: const_byte_str(typ);
+}
+
+/* main message functions *****************************************************/
+
+void fmt_print_lib9p_msg(lo_interface fmt_dest w, struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body) {
+ assert(ctx);
+ assert_ver(ctx->version);
+ assert_typ(typ);
+ assert(_lib9p_table_msg[ctx->version][typ].print);
+ _lib9p_table_msg[ctx->version][typ].print(w, ctx, body);
+}
+
+#define _lib9p_validate(LOW_TYP_BIT, ERRMSG, TABLE) do { \
+ assert_ver(ctx->version); \
+ /* Inspect the first 5 bytes ourselves. */ \
+ uint32_t net_size = uint32le_decode(net_bytes); \
+ if (net_size < 5) \
+ return ERROR_NEW_ERR(size_t, error_new(E_POSIX_EBADMSG, "message is impossibly short")); \
+ uint8_t typ = net_bytes[4]; \
+ if (typ % 2 != LOW_TYP_BIT) \
+ return ERROR_NEW_ERR(size_t, error_new(E_POSIX_EOPNOTSUPP, ERRMSG ": message_type=", \
+ lib9p_msgtype_str(ctx->version, typ))); \
+ struct _lib9p_recv_tentry tentry = TABLE[ctx->version][typ/2]; \
+ if (!tentry.validate) \
+ return ERROR_NEW_ERR(size_t, error_new(E_POSIX_EOPNOTSUPP, "unknown message type: ", \
+ lib9p_msgtype_str(ctx->version, typ), \
+ " (protocol_version=", 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); \
+} while (0)
+
+size_t_or_error lib9p_Tmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) {
+ _lib9p_validate(0, "expected a T-message but got an R-message", _lib9p_table_Tmsg_recv);
+}
+
+size_t_or_error lib9p_Rmsg_validate(struct lib9p_ctx *ctx, uint8_t *net_bytes) {
+ _lib9p_validate(1, "expected an R-message but got a T-message", _lib9p_table_Rmsg_recv);
+}
+
+#define _lib9p_unmarshal(TABLE) do { \
+ assert_ver(ctx->version); \
+ enum lib9p_msg_type typ = net_bytes[4]; \
+ *ret_typ = typ; \
+ struct _lib9p_recv_tentry tentry = TABLE[ctx->version][typ/2]; \
+ assert(tentry.unmarshal); \
+ \
+ tentry.unmarshal(ctx, net_bytes, ret_body); \
+} while (0)
+
+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);
+}
+
+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);
+}
+
+#define _lib9p_marshal(LOW_TYP_BIT, TABLE) do { \
+ assert_ver(ctx->version); \
+ assert(typ % 2 == LOW_TYP_BIT); \
+ assert_typ(typ); \
+ \
+ memset(ret, 0, sizeof(*ret)); \
+ \
+ 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 = TABLE[ctx->version][typ/2]; \
+ assert(tentry.marshal); \
+ \
+ error ret_err = tentry.marshal(ctx, body, &_ret); \
+ if (_ret.net_iov[_ret.net_iov_cnt-1].iov_len == 0) \
+ _ret.net_iov_cnt--; \
+ \
+ ret->iov_cnt = _ret.net_iov_cnt; \
+ return ret_err; \
+} while (0)
+
+error lib9p_Tmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
+ struct lib9p_Tmsg_send_buf *ret) {
+ _lib9p_marshal(0, _lib9p_table_Tmsg_send);
+}
+
+error lib9p_Rmsg_marshal(struct lib9p_ctx *ctx, enum lib9p_msg_type typ, void *body,
+ struct lib9p_Rmsg_send_buf *ret) {
+ _lib9p_marshal(1, _lib9p_table_Rmsg_send);
+}
+
+/* `struct lib9p_stat` helpers ************************************************/
+
+#if _LIB9P_ENABLE_stat
+error lib9p_stat_validate(struct lib9p_ctx *ctx, uint32_t net_size, uint8_t *net_bytes,
+ uint32_t *ret_net_size, size_t *ret_host_size) {
+ size_t_or_error host_size = _lib9p_stat_validate(ctx, net_size, net_bytes, ret_net_size);
+ if (host_size.is_err)
+ return host_size.err;
+ if (ret_host_size)
+ *ret_host_size = host_size.size_t;
+ return ERROR_NULL;
+}
+
+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 = {};
+ 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;
+}
+#endif