diff options
Diffstat (limited to 'lib9p/core.c')
-rw-r--r-- | lib9p/core.c | 213 |
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 |