diff options
47 files changed, 1089 insertions, 635 deletions
diff --git a/.editorconfig b/.editorconfig index d2b92ed..9540302 100644 --- a/.editorconfig +++ b/.editorconfig @@ -45,10 +45,10 @@ _mode = gitignore # By specific filename (non-lib9p) ############################################# -[{build-aux/lint-{generic,unknown},build-aux/embed-sources.h.gen}] +[{build-aux/lint-unknown,build-aux/embed-sources.h.gen}] _mode = sh -[{build-aux/lint-{bin,h},build-aux/get-dscname,build-aux/valgrind,libusb/include/libusb/tusb_helpers.h.gen}] +[{build-aux/lint-{bin,h,generic},build-aux/get-dscname,build-aux/valgrind,build-aux/gcov-prune,libusb/include/libusb/tusb_helpers.h.gen}] _mode = bash [build-aux/stack.c.gen] @@ -1,11 +1,13 @@ # .gitignore - Which files to ignore # -# Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> +# Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> # SPDX-License-Identifier: AGPL-3.0-or-later *.o *.log *.tmp +*.gcov.json.gz + .mypy_cache/ __pycache__/ .gdb_history diff --git a/CMakeLists.txt b/CMakeLists.txt index 5ce49ed..2022bd1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,11 @@ function(_suppress_tinyusb_warnings) COMPILE_OPTIONS "-Wno-switch-enum") endfunction() +if (PICO_PLATFORM STREQUAL "host") + add_compile_options(--coverage) + add_link_options(--coverage) +endif() + function(target_embed_sources arg_compile_target arg_link_target arg_hdrname) set(embed_objs) foreach(embed_src IN LISTS ARGN) diff --git a/GNUmakefile b/GNUmakefile index 9836b33..758c5aa 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -101,8 +101,17 @@ check: $(MAKE) -k INNER=t $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) .PHONY: check +# `gcc` writes .gcno +# Running the program writes .gcda (updates existing files, concurrent-safe) +# GCC `gcov` post-processes .gcno+.gcda to .gcov +# `gcovr` is a Python script that calls `gcov` and merges and post-processes the .gcov files to other formats +gcovr_flags = --txt=$(@D)/coverage.txt +gcovr_flags += --html=$(@D)/coverage.html --html-details --html-single-page=js-enabled +gcovr_flags += --sort uncovered-number --sort-reverse $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)): build/%/check: build/%/build + ./build-aux/gcov-prune $(@D) +cd $(@D) && ctest --output-on-failure $(if $(filter --jobserver-auth=%,$(MAKEFLAGS)),--parallel) + gcovr $(gcovr_flags) -- $(@D) .PHONY: $(foreach t,$(build_types),$(foreach p,$(platforms),build/$p-$t/check)) # `lint` and `format` ########################################################## diff --git a/build-aux/gcov-prune b/build-aux/gcov-prune new file mode 100755 index 0000000..dc190a9 --- /dev/null +++ b/build-aux/gcov-prune @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# build-aux/gcov-prune - Prune old GCC coverage files +# +# Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> +# SPDX-License-Identifier: AGPL-3.0-or-later + +set -e + +[[ $# == 1 ]] + +sourcedir="$(realpath -- .)" +builddir="$(realpath -- "$1")" + +# `gcc` writes .gcno +# Running the program writes .gcda (updates existing files, concurrent-safe) +# GCC `gcov` post-processes .gcno+.gcda to .gcov +# `gcovr` is a Python script that calls `gcov` and merges and post-processes the .gcov files to other formats + +# Prune orphaned .gcno files. +find "$builddir" -name '*.gcno' -printf '%P\0' | while read -d '' -r gcno_file; do + rel_base="${gcno_file%/CMakeFiles/*}" + src_file="$gcno_file" + src_file="${src_file#*/CMakeFiles/*.dir/}" + src_file="${src_file%.gcno}" + src_file="${src_file//__/..}" + src_file="$rel_base/$src_file" + if [[ ! -e "$sourcedir/$src_file" || "$sourcedir/$src_file" -nt "$builddir/$gcno_file" ]]; then + rm -fv -- "$builddir/$gcno_file" + fi +done + +# Prune all .gcda files. +find "$builddir" -name '*.gcda' -delete diff --git a/build-aux/lint-generic b/build-aux/lint-generic index d982527..9bf88dd 100755 --- a/build-aux/lint-generic +++ b/build-aux/lint-generic @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash # build-aux/lint-generic - Non-language-specific lint checks # # Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> @@ -16,12 +16,19 @@ r=0 for filename in "$@"; do # File header ########################################################## - shebang="$(sed -n '1{/^#!/{/^#!\/hint\//q; p;};}' "$filename")" - if [ -x "$filename" ] && [ -z "$shebang" ]; then + shebang="$(sed -n '1{/^#!/p;}' "$filename")" + if [[ -x $filename && (-z $shebang || $shebang == '#!/hint/'*) ]]; then err "$filename" 'is executable but does not have a shebang' - elif [ -n "$shebang" ] && ! [ -x "$filename" ]; then + elif [[ (-n $shebang && $shebang != '#!/hint/'*) && ! -x $filename ]]; then err "$filename" 'has a shebang but is not executable' fi + case "$shebang" in + '') : ;; + '#!/bin/sh') : ;; + '#!/usr/bin/env bash') : ;; + '#!/usr/bin/env python3') : ;; + *) err "$filename" 'has an unrecognized shebang' ;; + esac if ! grep -E -q 'Copyright \(C\) 202[4-9]((-|, )202[5-9])* Luke T. Shumaker' "$filename"; then err "$filename" 'is missing a copyright statement' diff --git a/build-aux/valgrind b/build-aux/valgrind index 7ad2712..8fe7c6e 100755 --- a/build-aux/valgrind +++ b/build-aux/valgrind @@ -1,4 +1,4 @@ -#!/bin/env bash +#!/usr/bin/env bash # build-aux/valgrind - Wrapper around valgrind to keep flags consistent # # Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> diff --git a/lib9p/core.gen b/lib9p/core.gen index b30ec31..24f66de 100755 --- a/lib9p/core.gen +++ b/lib9p/core.gen @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # lib9p/core.gen - Generate C marshalers/unmarshalers for .9p files # defining 9P protocol variants. # diff --git a/lib9p/core_gen/c_validate.py b/lib9p/core_gen/c_validate.py index e7a4017..8997237 100644 --- a/lib9p/core_gen/c_validate.py +++ b/lib9p/core_gen/c_validate.py @@ -67,7 +67,7 @@ def gen_c_validate(versions: set[str], typs: list[idl.UserType]) -> str: "\t\tsize_t len = n;\n" "\t\tVALIDATE_NET_BYTES(len);\n" "\t\tif (!utf8_is_valid_without_nul(&net_bytes[net_offset-len], len))\n" - f'\t\t\treturn lib9p_error(ctx, {c9util.IDENT("ERRNO_L_EBADMSG")}, "message contains invalid UTF-8");\n' + f'\t\t\treturn lib9p_error(ctx, {c9util.IDENT("ERRNO_L_EILSEQ")}, "message contains invalid UTF-8");\n' "\t}\n" ) ret += cutil.macro( diff --git a/lib9p/core_generated.c b/lib9p/core_generated.c index 6e3633f..ad7b210 100644 --- a/lib9p/core_generated.c +++ b/lib9p/core_generated.c @@ -229,12 +229,12 @@ static const lib9p_lock_flags_t lock_flags_masks[LIB9P_VER_NUM] = { return lib9p_error(ctx, LIB9P_ERRNO_L_EBADMSG, "message is too short for content"); \ if (net_offset > net_size) \ return lib9p_error(ctx, LIB9P_ERRNO_L_EBADMSG, "message is too short for content (", net_offset, " > ", net_size, ")"); -#define VALIDATE_NET_UTF8(n) \ - { \ - size_t len = n; \ - VALIDATE_NET_BYTES(len); \ - if (!utf8_is_valid_without_nul(&net_bytes[net_offset-len], len)) \ - return lib9p_error(ctx, LIB9P_ERRNO_L_EBADMSG, "message contains invalid UTF-8"); \ +#define VALIDATE_NET_UTF8(n) \ + { \ + size_t len = n; \ + VALIDATE_NET_BYTES(len); \ + if (!utf8_is_valid_without_nul(&net_bytes[net_offset-len], len)) \ + return lib9p_error(ctx, LIB9P_ERRNO_L_EILSEQ, "message contains invalid UTF-8"); \ } #define RESERVE_HOST_BYTES(n) \ if (__builtin_add_overflow(host_size, n, &host_size)) \ diff --git a/lib9p/core_include/lib9p/core.h b/lib9p/core_include/lib9p/core.h index 4941220..afefa2b 100644 --- a/lib9p/core_include/lib9p/core.h +++ b/lib9p/core_include/lib9p/core.h @@ -98,11 +98,7 @@ void fmt_print_lib9p_msg(lo_interface fmt_dest w, struct lib9p_ctx *ctx, enum li * number may be larger than net_bytes due to (1) struct padding, (2) * array pointers. * - * Emits an error (return -1, set ctx->err_num and ctx->err_msg) if - * either the message type is unknown, or if net_bytes is too short - * for that message type, or if an invalid string (invalid UTF-8, - * contains a nul-byte) is encountered. - * + * @param ctx : negotiated protocol parameters, where to record errors * @param net_bytes : the complete request, starting with the "size[4]" * * @return required size, or -1 on error @@ -110,7 +106,7 @@ void fmt_print_lib9p_msg(lo_interface fmt_dest w, struct lib9p_ctx *ctx, enum li * @errno L_EOPNOTSUPP: message is an R-message * @errno L_EOPNOTSUPP: message has unknown type * @errno L_EBADMSG: message is wrong size for content - * @errno L_EBADMSG: message contains invalid UTF-8 + * @errno L_EILSEQ: message contains invalid UTF-8, or the UTF-8 contains a nul-byte * @errno L_EBADMSG: message contains a bitfield with unknown bits * @errno L_EMSGSIZE: would-be return value overflows SSIZE_MAX */ @@ -142,9 +138,9 @@ void lib9p_Tmsg_unmarshal(struct lib9p_ctx *ctx, uint8_t *net_bytes, * * @param ctx : negotiated protocol parameters, where to record errors * @param typ : the message type - * @param msg : the message to encode + * @param msg : the message to encode (`struct lib9p_msg_XXXX` according to `typ`) * - * @return ret_bytes : the buffer to encode to, must be at be at least ctx->max_msg_size bytes + * @return ret : the buffer to encode to * @return whether there was an error (false=success, true=error) * * @errno L_ERANGE: reply does not fit in ctx->max_msg_size diff --git a/lib9p/idl/2010-9P2000.L.9p.gen b/lib9p/idl/2010-9P2000.L.9p.gen index cb32585..f0bdb6b 100755 --- a/lib9p/idl/2010-9P2000.L.9p.gen +++ b/lib9p/idl/2010-9P2000.L.9p.gen @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # lib9p/idl/2010-9P2000.L.9p.gen - Generate definitions of 9P2000.L messages import sys diff --git a/lib9p/srv.c b/lib9p/srv.c index 56fc3ec..32e9a9a 100644 --- a/lib9p/srv.c +++ b/lib9p/srv.c @@ -61,6 +61,15 @@ void lib9p_srv_acknowledge_flush(struct lib9p_srv_ctx *ctx) { /* structs ********************************************************************/ +void lib9p_srv_stat_assert(struct lib9p_srv_stat stat) { + assert( ((bool)(stat.mode & LIB9P_DM_DIR )) == ((bool)(stat.qid.type & LIB9P_QT_DIR )) ); + assert( ((bool)(stat.mode & LIB9P_DM_APPEND)) == ((bool)(stat.qid.type & LIB9P_QT_APPEND)) ); + assert( ((bool)(stat.mode & LIB9P_DM_EXCL )) == ((bool)(stat.qid.type & LIB9P_QT_EXCL )) ); + assert( ((bool)(stat.mode & LIB9P_DM_AUTH )) == ((bool)(stat.qid.type & LIB9P_QT_AUTH )) ); + assert( ((bool)(stat.mode & LIB9P_DM_TMP )) == ((bool)(stat.qid.type & LIB9P_QT_TMP )) ); + assert( (stat.size == 0) || !(stat.mode & LIB9P_DM_DIR) ); +} + enum srv_filetype { SRV_FILETYPE_FILE, SRV_FILETYPE_DIR, diff --git a/lib9p/srv_include/lib9p/srv.h b/lib9p/srv_include/lib9p/srv.h index c40c85a..89dc986 100644 --- a/lib9p/srv_include/lib9p/srv.h +++ b/lib9p/srv_include/lib9p/srv.h @@ -96,14 +96,7 @@ struct lib9p_srv_stat { #endif }; -static inline void lib9p_srv_stat_assert(struct lib9p_srv_stat stat) { - assert( ((bool)(stat.mode & LIB9P_DM_DIR )) == ((bool)(stat.qid.type & LIB9P_QT_DIR )) ); - assert( ((bool)(stat.mode & LIB9P_DM_APPEND)) == ((bool)(stat.qid.type & LIB9P_QT_APPEND)) ); - assert( ((bool)(stat.mode & LIB9P_DM_EXCL )) == ((bool)(stat.qid.type & LIB9P_QT_EXCL )) ); - assert( ((bool)(stat.mode & LIB9P_DM_AUTH )) == ((bool)(stat.qid.type & LIB9P_QT_AUTH )) ); - assert( ((bool)(stat.mode & LIB9P_DM_TMP )) == ((bool)(stat.qid.type & LIB9P_QT_TMP )) ); - assert( (stat.size == 0) || !(stat.mode & LIB9P_DM_DIR) ); -} +void lib9p_srv_stat_assert(struct lib9p_srv_stat stat); /* interface definitions ******************************************************/ @@ -192,7 +185,7 @@ LO_INTERFACE(lib9p_srv_fio); /*>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>*/ LO_FUNC(void , iofree ) \ /** \ * Return the idx-th dirent. idx will always be either 0 or \ - * prev_idx+1. A dirrent with an empty name signals EOF. The string \ + * prev_idx+1. A dirent with an empty name signals EOF. The string \ * must remain valid until the next dread() call or iofree(). \ */ \ LO_FUNC(struct lib9p_srv_dirent , dread , struct lib9p_srv_ctx *, \ diff --git a/libcr/coroutine.c b/libcr/coroutine.c index baa559b..121cc3b 100644 --- a/libcr/coroutine.c +++ b/libcr/coroutine.c @@ -583,7 +583,10 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, stack_size, "+2*", (base10, CR_STACK_GUARD_SIZE), "=", coroutine_table[child-1].stack_size); coroutine_table[child-1].stack = aligned_alloc(CR_PLAT_STACK_ALIGNMENT, coroutine_table[child-1].stack_size); - log_infoln("... done, stack is [", + log_infoln("...done, stack is [", + (ptr, coroutine_table[child-1].stack), ",", + (ptr, coroutine_table[child-1].stack + coroutine_table[child-1].stack_size), ")"); + log_infoln(" usable stack is [", (ptr, coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE), ",", (ptr, coroutine_table[child-1].stack + CR_STACK_GUARD_SIZE + stack_size), ")"); #if CONFIG_COROUTINE_MEASURE_STACK || CONFIG_COROUTINE_PROTECT_STACK @@ -607,7 +610,6 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, + stack_size #endif ; - log_debugln("...stack =", (ptr, coroutine_table[child-1].stack)); log_debugln("...stack_base=", (ptr, stack_base)); /* run until cr_begin() */ cr_plat_call_with_stack(stack_base, fn, args); diff --git a/libdhcp/CMakeLists.txt b/libdhcp/CMakeLists.txt index dee7cb6..f14e46d 100644 --- a/libdhcp/CMakeLists.txt +++ b/libdhcp/CMakeLists.txt @@ -7,7 +7,14 @@ add_library(libdhcp INTERFACE) target_include_directories(libdhcp PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libdhcp INTERFACE dhcp_client.c + dhcp_common.c ) target_link_libraries(libdhcp INTERFACE libmisc + libhw_generic ) + +if (ENABLE_TESTS) + add_lib_test(libdhcp test_client) + target_link_libraries(test_client libcr libhw_cr) +endif() diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c index bf88961..57a2f60 100644 --- a/libdhcp/dhcp_client.c +++ b/libdhcp/dhcp_client.c @@ -179,9 +179,10 @@ static const char *state_strs[] = { }; /** - * For convenience in switch blocks, a list of the states that, when - * msgtyp==DHCP_MSGTYP_REQUEST, dhcp_client_send() has assert()ed the state is - * not. + * IMPOSSIBLE_REQUEST_STATES is a convenience macro for use in switch + * blocks; it is a list of the states that (when + * msgtyp==DHCP_MSGTYP_REQUEST) dhcp_client_send() has assert()ed the + * state is not. */ #define IMPOSSIBLE_REQUEST_STATES \ STATE_INIT: \ @@ -700,14 +701,6 @@ static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg return 0; } -/** @return true if there's a conflict, false if the addr appears to be unused */ -static bool dhcp_check_conflict(lo_interface net_packet_conn sock, struct net_ip4_addr addr) { - assert(!LO_IS_NULL(sock)); - ssize_t v = LO_CALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000); - log_debugln("check_ip_conflict => ", v); - return v != -NET_EARP_TIMEOUT; -} - static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_msg *msg, bool ifup) { assert(client); assert(msg); @@ -805,7 +798,7 @@ static void dhcp_client_setstate(struct dhcp_client *client, dhcp_client_setstate(client, STATE_INIT, 0, NULL, scratch_msg); break; case DHCP_MSGTYP_ACK: - if (dhcp_check_conflict(client->sock, client->lease_client_addr)) { + if (LO_CALL(client->iface, arp_ping, client->lease_client_addr)) { log_debugln("IP ", (net_ip4_addr, client->lease_client_addr), " is already in use"); dhcp_client_setstate(client, STATE_INIT, DHCP_MSGTYP_DECLINE, "IP is already in use", scratch_msg); } else { diff --git a/libdhcp/dhcp_common.c b/libdhcp/dhcp_common.c new file mode 100644 index 0000000..d691836 --- /dev/null +++ b/libdhcp/dhcp_common.c @@ -0,0 +1,104 @@ +/* libdhcp/dhcp_common.c - Base definitions for the DHCP protocol + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include "dhcp_common.h" + +/** + * DHCP Options + * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options + */ +bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len) { + switch (opt) { + /* RFC 2132 */ + case DHCP_OPT_PAD: return len == 0; + case DHCP_OPT_SUBNET_MASK: return len == 4; + case DHCP_OPT_TIME_OFFSET: return len == 4; + case DHCP_OPT_ROUTER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_TIME_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_NAME_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_DOMAIN_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_LOG_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_QUOTES_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_LPR_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_IMPRESS_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_RLP_SERVER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_HOSTNAME: return len >= 1; + case DHCP_OPT_BOOT_FILE_SIZE: return len == 2; + case DHCP_OPT_MERIT_DUMP_FILE: return len >= 1; + case DHCP_OPT_DOMAIN_NAME: return len >= 1; + case DHCP_OPT_SWAP_SERVER: return len == 4; /* IANA says length is "N", but RFC 2132 says "length is 4"; likely releated to errata ID 487 */ + case DHCP_OPT_ROOT_PATH: return len >= 1; + case DHCP_OPT_EXTENSION_FILE: return len >= 1; + case DHCP_OPT_FORWARD_ONOFF: return len == 1; + case DHCP_OPT_SRCRTE_ONOFF: return len == 1; + case DHCP_OPT_POLICY_FILTER: return len >= 8 && len % 8 == 0; + case DHCP_OPT_MAX_DG_ASSEMBLY: return len == 2; + case DHCP_OPT_DEFAULT_IP_TTL: return len == 1; + case DHCP_OPT_MTU_TIMEOUT: return len == 4; + case DHCP_OPT_MTU_PLATEAU: return len >= 2 && len % 2 == 0; + case DHCP_OPT_MTU_INTERFACE: return len == 2; + case DHCP_OPT_MTU_SUBNET: return len == 1; + case DHCP_OPT_BROADCAST_ADDRESS: return len == 4; + case DHCP_OPT_MASK_DISCOVERY: return len == 1; + case DHCP_OPT_MASK_SUPPLIER: return len == 1; + case DHCP_OPT_ROUTER_DISCOVERY: return len == 1; + case DHCP_OPT_ROUTER_REQUEST: return len == 4; + case DHCP_OPT_STATIC_ROUTE: return len >= 8 && len % 8 == 0; + case DHCP_OPT_TRAILERS: return len == 1; + case DHCP_OPT_ARP_TIMEOUT: return len == 4; + case DHCP_OPT_ETHERNET: return len == 1; + case DHCP_OPT_DEFAULT_TCP_TTL: return len == 1; + case DHCP_OPT_KEEPALIVE_TIME: return len == 4; + case DHCP_OPT_KEEPALIVE_DATA: return len == 1; + case DHCP_OPT_NIS_DOMAIN: return len >= 1; + case DHCP_OPT_NIS_SERVERS: return len >= 4 && len % 4 == 0; + case DHCP_OPT_NTP_SERVERS: return len >= 4 && len % 4 == 0; + case DHCP_OPT_VENDOR_SPECIFIC: return len >= 1; + case DHCP_OPT_NETBIOS_NAME_SRV: return len >= 4 && len % 4 == 0; + case DHCP_OPT_NETBIOS_DIST_SRV: return len >= 4 && len % 4 == 0; + case DHCP_OPT_NETBIOS_NODE_TYPE: return len == 1; + case DHCP_OPT_NETBIOS_SCOPE: return len >= 1; + case DHCP_OPT_X_WINDOW_FONT: return len >= 4 && len % 4 == 0; + case DHCP_OPT_X_WINDOW_MANAGER: return len >= 4 && len % 4 == 0; + case DHCP_OPT_ADDRESS_REQUEST: return len == 4; + case DHCP_OPT_ADDRESS_TIME: return len == 4; + case DHCP_OPT_OVERLOAD: return len == 1; + case DHCP_OPT_DHCP_MSG_TYPE: return len == 1; + case DHCP_OPT_DHCP_SERVER_ID: return len == 4; + case DHCP_OPT_PARAMETER_LIST: return len >= 1; + case DHCP_OPT_DHCP_MESSAGE: return len >= 1; + case DHCP_OPT_DHCP_MAX_MSG_SIZE: return len == 2; + case DHCP_OPT_RENEWAL_TIME: return len == 4; + case DHCP_OPT_REBINDING_TIME: return len == 4; + case DHCP_OPT_CLASS_ID: return len >= 1; + case DHCP_OPT_CLIENT_ID: return len >= 2; + + /* RFC 2132 */ + case DHCP_OPT_END: return len == 0; + + /* Unrecognized */ + default: + return true; + } +} + +/** + * DHCP Message Type 53 Values + * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53 + */ +const char *dhcp_msgtyp_str(uint8_t typ) { + switch (typ) { + case DHCP_MSGTYP_DISCOVER: return "DHCP_MSGTYP_DISCOVER"; + case DHCP_MSGTYP_OFFER: return "DHCP_MSGTYP_OFFER"; + case DHCP_MSGTYP_REQUEST: return "DHCP_MSGTYP_REQUEST"; + case DHCP_MSGTYP_DECLINE: return "DHCP_MSGTYP_DECLINE"; + case DHCP_MSGTYP_ACK: return "DHCP_MSGTYP_ACK"; + case DHCP_MSGTYP_NAK: return "DHCP_MSGTYP_NAK"; + case DHCP_MSGTYP_RELEASE: return "DHCP_MSGTYP_RELEASE"; + case DHCP_MSGTYP_INFORM: return "DHCP_MSGTYP_INFORM"; + default: return const_byte_str(typ); + } +} diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h index 5b51ce2..a0cdd3c 100644 --- a/libdhcp/dhcp_common.h +++ b/libdhcp/dhcp_common.h @@ -1,6 +1,6 @@ /* libdhcp/dhcp_common.h - Base definitions for the DHCP protocol * - * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later * * ----------------------------------------------------------------------------- @@ -67,8 +67,9 @@ #ifndef _LIBDHCP_DHCP_COMMON_H_ #define _LIBDHCP_DHCP_COMMON_H_ -#include <libmisc/endian.h> -#include <libmisc/log.h> /* for const_byte_str() */ +#include <libhw/generic/net.h> /* for struct net_ip4_addr */ +#include <libmisc/endian.h> /* for uint{n}be_t */ +#include <libmisc/log.h> /* for const_byte_str() */ /* Config *********************************************************************/ @@ -215,80 +216,7 @@ static const uint8_t dhcp_magic_cookie[] = {99, 130, 83, 99}; /* ... */ #define DHCP_OPT_END ((uint8_t)255) /* RFC2132: length: 0; meaning: None */ -static inline bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len) { - switch (opt) { - /* RFC 2132 */ - case DHCP_OPT_PAD: return len == 0; - case DHCP_OPT_SUBNET_MASK: return len == 4; - case DHCP_OPT_TIME_OFFSET: return len == 4; - case DHCP_OPT_ROUTER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_TIME_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_NAME_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_DOMAIN_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_LOG_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_QUOTES_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_LPR_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_IMPRESS_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_RLP_SERVER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_HOSTNAME: return len >= 1; - case DHCP_OPT_BOOT_FILE_SIZE: return len == 2; - case DHCP_OPT_MERIT_DUMP_FILE: return len >= 1; - case DHCP_OPT_DOMAIN_NAME: return len >= 1; - case DHCP_OPT_SWAP_SERVER: return len == 4; /* IANA says length is "N", but RFC 2132 says "length is 4"; likely releated to errata ID 487 */ - case DHCP_OPT_ROOT_PATH: return len >= 1; - case DHCP_OPT_EXTENSION_FILE: return len >= 1; - case DHCP_OPT_FORWARD_ONOFF: return len == 1; - case DHCP_OPT_SRCRTE_ONOFF: return len == 1; - case DHCP_OPT_POLICY_FILTER: return len >= 8 && len % 8 == 0; - case DHCP_OPT_MAX_DG_ASSEMBLY: return len == 2; - case DHCP_OPT_DEFAULT_IP_TTL: return len == 1; - case DHCP_OPT_MTU_TIMEOUT: return len == 4; - case DHCP_OPT_MTU_PLATEAU: return len >= 2 && len % 2 == 0; - case DHCP_OPT_MTU_INTERFACE: return len == 2; - case DHCP_OPT_MTU_SUBNET: return len == 1; - case DHCP_OPT_BROADCAST_ADDRESS: return len == 4; - case DHCP_OPT_MASK_DISCOVERY: return len == 1; - case DHCP_OPT_MASK_SUPPLIER: return len == 1; - case DHCP_OPT_ROUTER_DISCOVERY: return len == 1; - case DHCP_OPT_ROUTER_REQUEST: return len == 4; - case DHCP_OPT_STATIC_ROUTE: return len >= 8 && len % 8 == 0; - case DHCP_OPT_TRAILERS: return len == 1; - case DHCP_OPT_ARP_TIMEOUT: return len == 4; - case DHCP_OPT_ETHERNET: return len == 1; - case DHCP_OPT_DEFAULT_TCP_TTL: return len == 1; - case DHCP_OPT_KEEPALIVE_TIME: return len == 4; - case DHCP_OPT_KEEPALIVE_DATA: return len == 1; - case DHCP_OPT_NIS_DOMAIN: return len >= 1; - case DHCP_OPT_NIS_SERVERS: return len >= 4 && len % 4 == 0; - case DHCP_OPT_NTP_SERVERS: return len >= 4 && len % 4 == 0; - case DHCP_OPT_VENDOR_SPECIFIC: return len >= 1; - case DHCP_OPT_NETBIOS_NAME_SRV: return len >= 4 && len % 4 == 0; - case DHCP_OPT_NETBIOS_DIST_SRV: return len >= 4 && len % 4 == 0; - case DHCP_OPT_NETBIOS_NODE_TYPE: return len == 1; - case DHCP_OPT_NETBIOS_SCOPE: return len >= 1; - case DHCP_OPT_X_WINDOW_FONT: return len >= 4 && len % 4 == 0; - case DHCP_OPT_X_WINDOW_MANAGER: return len >= 4 && len % 4 == 0; - case DHCP_OPT_ADDRESS_REQUEST: return len == 4; - case DHCP_OPT_ADDRESS_TIME: return len == 4; - case DHCP_OPT_OVERLOAD: return len == 1; - case DHCP_OPT_DHCP_MSG_TYPE: return len == 1; - case DHCP_OPT_DHCP_SERVER_ID: return len == 4; - case DHCP_OPT_PARAMETER_LIST: return len >= 1; - case DHCP_OPT_DHCP_MESSAGE: return len >= 1; - case DHCP_OPT_DHCP_MAX_MSG_SIZE: return len == 2; - case DHCP_OPT_RENEWAL_TIME: return len == 4; - case DHCP_OPT_REBINDING_TIME: return len == 4; - case DHCP_OPT_CLASS_ID: return len >= 1; - case DHCP_OPT_CLIENT_ID: return len >= 2; - - /* RFC 2132 */ - case DHCP_OPT_END: return len == 0; - - /* Unrecognized */ - default: - return true; - } -}; +bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len); /** * DHCP Message Type 53 Values @@ -303,18 +231,6 @@ static inline bool dhcp_opt_length_is_valid(uint8_t opt, uint16_t len) { #define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132, client->server */ #define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132, client->server */ -static const char *dhcp_msgtyp_str(uint8_t typ) { - switch (typ) { - case DHCP_MSGTYP_DISCOVER: return "DHCP_MSGTYP_DISCOVER"; - case DHCP_MSGTYP_OFFER: return "DHCP_MSGTYP_OFFER"; - case DHCP_MSGTYP_REQUEST: return "DHCP_MSGTYP_REQUEST"; - case DHCP_MSGTYP_DECLINE: return "DHCP_MSGTYP_DECLINE"; - case DHCP_MSGTYP_ACK: return "DHCP_MSGTYP_ACK"; - case DHCP_MSGTYP_NAK: return "DHCP_MSGTYP_NAK"; - case DHCP_MSGTYP_RELEASE: return "DHCP_MSGTYP_RELEASE"; - case DHCP_MSGTYP_INFORM: return "DHCP_MSGTYP_INFORM"; - default: return const_byte_str(typ); - } -} +const char *dhcp_msgtyp_str(uint8_t typ); #endif /* _LIBDHCP_DHCP_COMMON_H_ */ diff --git a/libdhcp/tests/config.h b/libdhcp/tests/config.h new file mode 100644 index 0000000..53079fe --- /dev/null +++ b/libdhcp/tests/config.h @@ -0,0 +1,25 @@ +/* config.h - Compile-time configuration for the libdhcp tests + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#define CONFIG_COROUTINE_STACK_SIZE_DEFAULT (16*1024) +#define CONFIG_COROUTINE_NAME_LEN 16 +#define CONFIG_COROUTINE_NUM 16 + +#define CONFIG_COROUTINE_MEASURE_STACK 1 +#define CONFIG_COROUTINE_PROTECT_STACK 1 +#define CONFIG_COROUTINE_DEBUG 1 +#define CONFIG_COROUTINE_VALGRIND 1 +#define CONFIG_COROUTINE_GDB 1 + +#define CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP 0 /* bool */ +#define CONFIG_DHCP_DEBUG 1 /* bool */ +#define CONFIG_DHCP_OPT_SIZE 312 /* minimum of 312 */ +#define CONFIG_DHCP_SELECTING_NS (5*NS_PER_S) + +#endif /* _CONFIG_H_ */ diff --git a/libdhcp/tests/test.h b/libdhcp/tests/test.h new file mode 120000 index 0000000..2fb1bd5 --- /dev/null +++ b/libdhcp/tests/test.h @@ -0,0 +1 @@ +../../libmisc/tests/test.h
\ No newline at end of file diff --git a/libdhcp/tests/test_client.c b/libdhcp/tests/test_client.c new file mode 100644 index 0000000..24b3af6 --- /dev/null +++ b/libdhcp/tests/test_client.c @@ -0,0 +1,123 @@ +/* libdhcp/tests/test_client.c - Tests for libdhcp client + * + * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcpy() */ + +#include <libcr/coroutine.h> +#include <libhw/generic/net.h> +#include <libhw/host_alarmclock.h> + +#include <libdhcp/client.h> + +#include "test.h" + +/******************************************************************************/ + +struct test_udp { +}; +LO_IMPLEMENTATION_H(io_closer, struct test_udp, test_udp); +LO_IMPLEMENTATION_C(io_closer, struct test_udp, test_udp, static); +LO_IMPLEMENTATION_H(net_packet_conn, struct test_udp, test_udp); +LO_IMPLEMENTATION_C(net_packet_conn, struct test_udp, test_udp, static); + +static ssize_t test_udp_sendto(struct test_udp *LM_UNUSED(self), void *LM_UNUSED(buf), size_t len, struct net_ip4_addr LM_UNUSED(node), uint16_t LM_UNUSED(port)) { + static unsigned cnt = 0; + if (cnt++ % 2 == 0) + return -NET_EOTHER; + return len; +} + +static ssize_t test_udp_recvfrom(struct test_udp *LM_UNUSED(self), void *buf, size_t len, struct net_ip4_addr *ret_node, uint16_t *ret_port) { + static const uint8_t resp_offer[] = {0x02,0x01,0x06,0x00,0xE8,0x40,0xC6,0x79,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0xC0,0xA8,0x0A,0x78,0xC0,0xA8,0x0A,0x01,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x82,0x53,0x63,0x35,0x01,0x02,0x36,0x04,0xC0,0xA8,0x0A,0x01,0x33,0x04,0x00,0x00,0xA8,0xC0,0x3A,0x04,0x00,0x00,0x54,0x60,0x3B,0x04,0x00,0x00,0x93,0xA8,0x01,0x04,0xFF,0xFF,0xFF,0x00,0x1C,0x04,0xC0,0xA8,0x0A,0xFF,0x03,0x04,0xC0,0xA8,0x0A,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + static const uint8_t resp_ack[] = {0x02,0x01,0x06,0x00,0xE8,0x40,0xC6,0x79,0x00,0x01,0x80,0x00,0x00,0x00,0x00,0x00,0xC0,0xA8,0x0A,0x78,0xC0,0xA8,0x0A,0x01,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x63,0x82,0x53,0x63,0x35,0x01,0x05,0x36,0x04,0xC0,0xA8,0x0A,0x01,0x33,0x04,0x00,0x00,0xA8,0xC0,0x3A,0x04,0x00,0x00,0x54,0x60,0x3B,0x04,0x00,0x00,0x93,0xA8,0x01,0x04,0xFF,0xFF,0xFF,0x00,0x1C,0x04,0xC0,0xA8,0x0A,0xFF,0x03,0x04,0xC0,0xA8,0x0A,0x01,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}; + + static unsigned cnt = 0; + const void *resp; + size_t resp_len; + switch (cnt++) { + case 0: return -NET_EOTHER; + case 1: resp = resp_offer; resp_len = sizeof(resp_offer); break; + case 2: return -NET_EOTHER; + case 3: resp = resp_ack; resp_len = sizeof(resp_ack); break; + default: return -NET_ERECV_TIMEOUT; + } + test_assert(len >= resp_len); + memcpy(buf, resp, resp_len); + *ret_node = ((struct net_ip4_addr){{192,168,10,1}}); + *ret_port = 67; + return resp_len; +} + +static void test_udp_set_recv_deadline(struct test_udp *LM_UNUSED(self), uint64_t LM_UNUSED(ns_since_boot)) { + /* Do nothing? */ +} + +static int test_udp_close(struct test_udp *LM_UNUSED(self)) { + assert_notreached("not implemented"); +} + +/******************************************************************************/ + +struct test_iface { + struct net_iface_config cfg; + + struct test_udp conn; +}; +LO_IMPLEMENTATION_H(net_iface, struct test_iface, test); +LO_IMPLEMENTATION_C(net_iface, struct test_iface, test, static); + +static struct net_eth_addr test_hwaddr(struct test_iface *LM_UNUSED(self)) { + struct net_eth_addr ret = {{1, 2, 3, 4, 5, 6}}; + return ret; +} + +static void test_ifup(struct test_iface *LM_UNUSED(self), struct net_iface_config LM_UNUSED(cfg)) { + cr_exit(); +} + +static void test_ifdown(struct test_iface *LM_UNUSED(self)) { + assert_notreached("not implemented"); +} + +static bool test_arp_ping(struct test_iface *LM_UNUSED(self), struct net_ip4_addr LM_UNUSED(addr)) { + return false; +} + +static lo_interface net_stream_listener test_tcp_listen(struct test_iface *LM_UNUSED(self), uint16_t LM_UNUSED(local_port)) { + assert_notreached("not implemented"); +} + +static lo_interface net_stream_conn test_tcp_dial(struct test_iface *LM_UNUSED(self), struct net_ip4_addr LM_UNUSED(remote_node), uint16_t LM_UNUSED(remote_port)) { + assert_notreached("not implemented"); +} + +static lo_interface net_packet_conn test_udp_conn(struct test_iface *self, uint16_t local_port) { + bool once = false; + test_assert(local_port == 68); + test_assert(!once); + once = true; + return lo_box_test_udp_as_net_packet_conn(&self->conn); +} + +/******************************************************************************/ + +COROUTINE dhcp_cr(void *) { + cr_begin(); + struct test_iface iface = {}; + dhcp_client_main(lo_box_test_as_net_iface(&iface), "test-client"); + cr_end(); +} + +int main() { + struct hostclock clock_monotonic = { + .clock_id = CLOCK_MONOTONIC, + }; + bootclock = lo_box_hostclock_as_alarmclock(&clock_monotonic); + + coroutine_add("dhcp", dhcp_cr, NULL); + coroutine_main(); + return 0; +} diff --git a/libhw_cr/CMakeLists.txt b/libhw_cr/CMakeLists.txt index ba20b26..9dd6a27 100644 --- a/libhw_cr/CMakeLists.txt +++ b/libhw_cr/CMakeLists.txt @@ -24,6 +24,7 @@ if (PICO_PLATFORM STREQUAL "rp2040") rp2040_hwspi.c rp2040_hwtimer.c w5500.c + w5500_ll.c ) target_link_libraries(libhw_cr INTERFACE hardware_gpio diff --git a/libhw_cr/host_net.c b/libhw_cr/host_net.c index 4a2e65f..8016787 100644 --- a/libhw_cr/host_net.c +++ b/libhw_cr/host_net.c @@ -376,6 +376,8 @@ void hostnet_udp_conn_init(struct hostnet_udp_conn *self, uint16_t port) { fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) error(1, errno, "socket"); + if (setsockopt(fd, SOL_SOCKET, SO_BROADCAST, &(int){1}, sizeof(int)) < 0) + error(1, errno, "setsockopt(fd=%d, SO_BROADCAST=1)", fd); if (bind(fd, &addr.gen, sizeof addr) < 0) error(1, errno, "bind"); @@ -408,10 +410,10 @@ static void *hostnet_pthread_sendto(void *_args) { addr.in.sin_family = AF_INET; addr.in.sin_addr.s_addr = - (((uint32_t)args->node.octets[0])<<24) | - (((uint32_t)args->node.octets[1])<<16) | - (((uint32_t)args->node.octets[2])<< 8) | - (((uint32_t)args->node.octets[3])<< 0) ; + (((uint32_t)args->node.octets[3])<<24) | + (((uint32_t)args->node.octets[2])<<16) | + (((uint32_t)args->node.octets[1])<< 8) | + (((uint32_t)args->node.octets[0])<< 0) ; addr.in.sin_port = htons(args->port); *(args->ret) = sendto(args->connfd, args->buf, args->count, 0, &addr.gen, sizeof(addr)); if (*(args->ret) < 0) @@ -473,7 +475,7 @@ static void *hostnet_pthread_recvfrom(void *_args) { struct sockaddr gen; struct sockaddr_storage stor; } addr = {}; - socklen_t addr_size; + socklen_t addr_size = sizeof(addr); *(args->ret_size) = setsockopt(args->connfd, SOL_SOCKET, SO_RCVTIMEO, &args->timeout, sizeof(args->timeout)); @@ -487,10 +489,10 @@ static void *hostnet_pthread_recvfrom(void *_args) { assert(addr.in.sin_family == AF_INET); if (args->ret_node) { - args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; - args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; - args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; - args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; + args->ret_node->octets[3] = (addr.in.sin_addr.s_addr >> 24) & 0xFF; + args->ret_node->octets[2] = (addr.in.sin_addr.s_addr >> 16) & 0xFF; + args->ret_node->octets[1] = (addr.in.sin_addr.s_addr >> 8) & 0xFF; + args->ret_node->octets[0] = (addr.in.sin_addr.s_addr >> 0) & 0xFF; } if (args->ret_port) { (*args->ret_port) = ntohs(addr.in.sin_port); diff --git a/libhw_cr/host_util.c b/libhw_cr/host_util.c index 7b3200c..8cacd57 100644 --- a/libhw_cr/host_util.c +++ b/libhw_cr/host_util.c @@ -7,6 +7,8 @@ #include <error.h> /* for error(3gnu) */ #include <signal.h> /* for SIGRTMIN, SIGRTMAX */ +#include <libhw/generic/alarmclock.h> /* for {X}S_PER_S */ + #include "host_util.h" int host_sigrt_alloc(void) { @@ -19,3 +21,30 @@ int host_sigrt_alloc(void) { error(1, 0, "SIGRTMAX exceeded"); return ret; } + +host_us_time_t ns_to_host_us_time(uint64_t time_ns) { + host_us_time_t ret; + ret.tv_sec = time_ns + /NS_PER_S; + ret.tv_usec = (time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S) + /(NS_PER_S/US_PER_S); + return ret; +} + +host_ns_time_t ns_to_host_ns_time(uint64_t time_ns) { + host_ns_time_t ret; + ret.tv_sec = time_ns + /NS_PER_S; + ret.tv_nsec = time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S; + return ret; +} + +uint64_t ns_from_host_us_time(host_us_time_t host_time) { + return (((uint64_t)host_time.tv_sec) * NS_PER_S) + + ((uint64_t)host_time.tv_usec * (NS_PER_S/US_PER_S)); +} + +uint64_t ns_from_host_ns_time(host_ns_time_t host_time) { + return (((uint64_t)host_time.tv_sec) * NS_PER_S) + + ((uint64_t)host_time.tv_nsec); +} diff --git a/libhw_cr/host_util.h b/libhw_cr/host_util.h index 3f0a671..4adb94e 100644 --- a/libhw_cr/host_util.h +++ b/libhw_cr/host_util.h @@ -7,41 +7,18 @@ #ifndef _LIBHW_CR_HOST_UTIL_H_ #define _LIBHW_CR_HOST_UTIL_H_ +#include <stdint.h> /* for uint{n}_t */ #include <sys/time.h> /* for struct timeval */ #include <time.h> /* for struct timespec */ -#include <libhw/generic/alarmclock.h> /* for {X}S_PER_S */ - int host_sigrt_alloc(void); typedef struct timeval host_us_time_t; typedef struct timespec host_ns_time_t; -static inline host_us_time_t ns_to_host_us_time(uint64_t time_ns) { - host_us_time_t ret; - ret.tv_sec = time_ns - /NS_PER_S; - ret.tv_usec = (time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S) - /(NS_PER_S/US_PER_S); - return ret; -} - -static inline host_ns_time_t ns_to_host_ns_time(uint64_t time_ns) { - host_ns_time_t ret; - ret.tv_sec = time_ns - /NS_PER_S; - ret.tv_nsec = time_ns - ((uint64_t)ret.tv_sec)*NS_PER_S; - return ret; -} - -static inline uint64_t ns_from_host_us_time(host_us_time_t host_time) { - return (((uint64_t)host_time.tv_sec) * NS_PER_S) + - ((uint64_t)host_time.tv_usec * (NS_PER_S/US_PER_S)); -} - -static inline uint64_t ns_from_host_ns_time(host_ns_time_t host_time) { - return (((uint64_t)host_time.tv_sec) * NS_PER_S) + - ((uint64_t)host_time.tv_nsec); -} +host_us_time_t ns_to_host_us_time(uint64_t time_ns); +host_ns_time_t ns_to_host_ns_time(uint64_t time_ns); +uint64_t ns_from_host_us_time(host_us_time_t host_time); +uint64_t ns_from_host_ns_time(host_ns_time_t host_time); #endif /* _LIBHW_CR_HOST_UTIL_H_ */ diff --git a/libhw_cr/rp2040_dma.c b/libhw_cr/rp2040_dma.c index e117c19..8901f06 100644 --- a/libhw_cr/rp2040_dma.c +++ b/libhw_cr/rp2040_dma.c @@ -1,5 +1,8 @@ /* libhw_cr/rp2040_dma.c - Utilities for sharing the DMA IRQs * + * Copyright (c) 2020 Raspberry Pi (Trading) Ltd. + * SPDX-License-Identifier: BSD-3-Clause + * * Copyright (C) 2025 Luke T. Shumaker <lukeshu@lukeshu.com> * SPDX-License-Identifier: AGPL-3.0-or-later */ @@ -8,6 +11,21 @@ #include "rp2040_dma.h" +/* Borrowed from <hardware/dma.h> *********************************************/ + +dma_channel_hw_t *dma_channel_hw_addr(uint channel) { + assert(channel < NUM_DMA_CHANNELS); + return &dma_hw->ch[channel]; +} + +enum dma_channel_transfer_size { + DMA_SIZE_8 = 0, ///< Byte transfer (8 bits) + DMA_SIZE_16 = 1, ///< Half word transfer (16 bits) + DMA_SIZE_32 = 2 ///< Word transfer (32 bits) +}; + +/* Our own code ***************************************************************/ + struct dmairq_handler_entry { dmairq_handler_t fn; void *arg; diff --git a/libhw_cr/rp2040_dma.h b/libhw_cr/rp2040_dma.h index e8bceec..5c1c7bb 100644 --- a/libhw_cr/rp2040_dma.h +++ b/libhw_cr/rp2040_dma.h @@ -21,10 +21,7 @@ /* Borrowed from <hardware/dma.h> *********************************************/ -static inline dma_channel_hw_t *dma_channel_hw_addr(uint channel) { - assert(channel < NUM_DMA_CHANNELS); - return &dma_hw->ch[channel]; -} +dma_channel_hw_t *dma_channel_hw_addr(uint channel); enum dma_channel_transfer_size { DMA_SIZE_8 = 0, ///< Byte transfer (8 bits) diff --git a/libhw_cr/w5500.c b/libhw_cr/w5500.c index b7c2ad1..c318819 100644 --- a/libhw_cr/w5500.c +++ b/libhw_cr/w5500.c @@ -8,6 +8,7 @@ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.h * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/W5500/w5500.c * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Ethernet/socket.c + * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c * * Copyright (c) 2013, WIZnet Co., LTD. * All rights reserved. @@ -67,6 +68,8 @@ * SPDX-License-Identifier: MIT */ +#include <string.h> /* for memcmp() */ + /* TODO: Write a <libhw/generic/gpio.h> to avoid w5500.c being * pico-sdk-specific. */ #include "rp2040_gpioirq.h" @@ -545,6 +548,22 @@ static lo_interface net_packet_conn w5500_if_udp_conn(struct w5500 *chip, uint16 return lo_box_w5500_udp_as_net_packet_conn(socket); } +static bool w5500_if_arp_ping(struct w5500 *chip, struct net_ip4_addr addr) { + /* FIXME: This arp_ping implementation is really bad (and + * assumes that a UDP socket is open, which is "safe" because + * I only use it from inside of a DHCP client). */ + assert(chip); + struct _w5500_socket *sock = NULL; + for (size_t i = 0; i < LM_ARRAY_LEN(chip->sockets) && !sock; i++) { + if (chip->sockets[i].mode == W5500_MODE_UDP) + sock = &chip->sockets[i]; + } + assert(sock); + ssize_t v = w5500_udp_sendto(sock, "BOGUS_PACKET_TO_TRIGGER_AN_ARP", 17, addr, 5000); + log_debugln("arp_ping => ", v); + return v != -NET_EARP_TIMEOUT; +} + /* tcp_listener methods *******************************************************/ static lo_interface net_stream_conn w5500_tcplist_accept(struct _w5500_socket *socket) { diff --git a/libhw_cr/w5500_ll.c b/libhw_cr/w5500_ll.c new file mode 100644 index 0000000..c60e045 --- /dev/null +++ b/libhw_cr/w5500_ll.c @@ -0,0 +1,115 @@ +/* libhw_cr/w5500_ll.c - Low-level library for the WIZnet W5500 chip + * + * Based entirely on the W5500 datasheet (v1.1.0), not on reference code. + * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <string.h> /* for memcmp() and mempy() */ + +#include <libmisc/alloc.h> /* for stack_alloc() */ +#include <libmisc/fmt.h> + +#include "w5500_ll.h" + +#if CONFIG_W5500_LL_DEBUG +static void fmt_print_ctl_block(lo_interface fmt_dest, uint8_t b) { + static char *strs[] = { + "RES", + "REG", + "TX", + "RX", + }; + fmt_print("CTL_BLOCK_SOCK(", (base10, (((b)>>5) & 0b111)), ", ", strs[((b)>>3)&0b11], ")"); +} +#endif + +void _w5500ll_n_writev(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct iovec *iov, int iovcnt, + size_t skip, size_t max) +{ + assert(!LO_IS_NULL(spidev)); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(iov); + assert(iovcnt > 0); +#if CONFIG_W5500_LL_DEBUG + log_n_debugln(W5500_LL, + func, "(): w5500ll_write(spidev", + ", addr=", (base16_u16_, addr), + ", block=", (ctl_block, block), + ", iov", + ", iovcnt=", iovcnt, + ")"); +#endif + + uint8_t header[] = { + (uint8_t)((addr >> 8) & 0xFF), + (uint8_t)(addr & 0xFF), + (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, + }; + int inner_cnt = 1+io_slice_cnt(iov, iovcnt, skip, max); + struct duplex_iovec *inner = stack_alloc(inner_cnt, struct duplex_iovec); + inner[0] = (struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), + }; + io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); +} + + +void _w5500ll_n_readv(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct iovec *iov, int iovcnt, + size_t max) +{ + assert(!LO_IS_NULL(spidev)); + assert((block & ~CTL_MASK_BLOCK) == 0); + assert(iov); + assert(iovcnt > 0); +#if CONFIG_W5500_LL_DEBUG + log_n_debugln(W5500_LL, + func, "(): w5500ll_read(spidev", + ", addr=", (base16_u16_, addr), + ", block=", (ctl_block, block), + ", iov", + ", iovcnt=", iovcnt, + ")"); +#endif + + uint8_t header[] = { + (uint8_t)((addr >> 8) & 0xFF), + (uint8_t)(addr & 0xFF), + (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, + }; + int inner_cnt = 1+io_slice_cnt(iov, iovcnt, 0, max); + struct duplex_iovec *inner = stack_alloc(inner_cnt, struct duplex_iovec); + inner[0] = (struct duplex_iovec){ + .iov_read_to = IOVEC_DISCARD, + .iov_write_from = header, + .iov_len = sizeof(header), + }; + io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max); + LO_CALL(spidev, readwritev, inner, inner_cnt); +} + +void _w5500ll_n_read_repeated(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + void *buf, void *buf_scratch, size_t len) +{ + _w5500ll_n_readv(func, spidev, addr, block, + &((struct iovec){.iov_base=buf, .iov_len=len}), 1, + 0); + for (;;) { + _w5500ll_n_readv(func, spidev, addr, block, + &((struct iovec){.iov_base=buf_scratch, .iov_len=len}), 1, + 0); + if (memcmp(buf, buf_scratch, len) == 0) + break; + memcpy(buf, buf_scratch, len); + } +} diff --git a/libhw_cr/w5500_ll.h b/libhw_cr/w5500_ll.h index aa41e7a..28116a4 100644 --- a/libhw_cr/w5500_ll.h +++ b/libhw_cr/w5500_ll.h @@ -1,6 +1,6 @@ -/* libhw_cr/w5500_ll.h - Low-level header library for the WIZnet W5500 chip +/* libhw_cr/w5500_ll.h - Low-level library for the WIZnet W5500 chip * - * Based entirely on the W5500 datasheet, v1.1.0. + * Based entirely on the W5500 datasheet (v1.1.0), not on reference code. * https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf * * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> @@ -11,16 +11,14 @@ #define _LIBHW_CR_W5500_LL_H_ #include <stdint.h> /* for uint{n}_t */ -#include <string.h> /* for memcmp() */ -#include <libmisc/alloc.h> /* for stack_alloc() */ -#include <libmisc/assert.h> /* for assert(), static_assert() */ +#include <libmisc/assert.h> /* for static_assert() */ #include <libmisc/endian.h> /* for uint16be_t */ #include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */ #include <libhw/generic/spi.h> /* for lo_interface spi */ -/* Config *********************************************************************/ +/* Config. ***************************************************************************************/ #include "config.h" @@ -28,7 +26,7 @@ #error config.h must define CONFIG_W5500_LL_DEBUG #endif -/* Low-level protocol built on SPI frames. ***********************************/ +/* Low-level protocol built on SPI frames. *******************************************************/ /* A u8 control byte has 3 parts: block-ID, R/W, and operating-mode. */ @@ -53,102 +51,81 @@ #define CTL_OM_FDM2 0b10 /* fixed-length data mode: 2 byte data length */ #define CTL_OM_FDM4 0b11 /* fixed-length data mode: 4 byte data length */ -#if CONFIG_W5500_LL_DEBUG -static void fmt_print_ctl_block(lo_interface fmt_dest, uint8_t b) { - static char *strs[] = { - "RES", - "REG", - "TX", - "RX", - }; - fmt_print("CTL_BLOCK_SOCK(", (base10, (((b)>>5) & 0b111)), ", ", strs[((b)>>3)&0b11], ")"); -} -#endif +/* Even though SPI is a full-duplex protocol, the W5500's spiframe on + * top of it is only half-duplex. Lame. */ -/* Even though SPI is a full-duplex protocol, the W5500's spiframe on top of it is only half-duplex. - * Lame. */ - -static inline void #if CONFIG_W5500_LL_DEBUG -#define w5500ll_writev(...) _w5500ll_writev(__func__, __VA_ARGS__) -_w5500ll_writev(const char *func, + #define w5500ll_writev(...) _w5500ll_n_writev (__func__, __VA_ARGS__) + #define w5500ll_readv(...) _w5500ll_n_readv (__func__, __VA_ARGS__) #else -w5500ll_writev( -#endif - lo_interface spi spidev, uint16_t addr, uint8_t block, - const struct iovec *iov, int iovcnt, - size_t skip, size_t max) -{ - assert(!LO_IS_NULL(spidev)); - assert((block & ~CTL_MASK_BLOCK) == 0); - assert(iov); - assert(iovcnt > 0); -#if CONFIG_W5500_LL_DEBUG - log_n_debugln(W5500_LL, - func, "(): w5500ll_write(spidev", - ", addr=", (base16_u16_, addr), - ", block=", (ctl_block, block), - ", iov", - ", iovcnt=", iovcnt, - ")"); + #define _w5500ll_n_writev(func, ...) w5500ll_writev (__VA_ARGS__) + #define _w5500ll_n_readv(func, ...) w5500ll_readv (__VA_ARGS__) #endif - uint8_t header[] = { - (uint8_t)((addr >> 8) & 0xFF), - (uint8_t)(addr & 0xFF), - (block & CTL_MASK_BLOCK) | CTL_W | CTL_OM_VDM, - }; - int inner_cnt = 1+io_slice_cnt(iov, iovcnt, skip, max); - struct duplex_iovec *inner = stack_alloc(inner_cnt, struct duplex_iovec); - inner[0] = (struct duplex_iovec){ - .iov_read_to = IOVEC_DISCARD, - .iov_write_from = header, - .iov_len = sizeof(header), - }; - io_slice_wr_to_duplex(&inner[1], iov, iovcnt, skip, max); - LO_CALL(spidev, readwritev, inner, inner_cnt); -} - -static inline void +/* `skip` and `max` correspond to the <libhw/generic/io.h> `slice` + * functions' `byte_start` and `max_byte_cnt` arguments for working on + * just a subset of the iovec list. */ + +void _w5500ll_n_writev(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct iovec *iov, int iovcnt, + size_t skip, size_t max); +void _w5500ll_n_readv(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + const struct iovec *iov, int iovcnt, + size_t max); + +/* Operate on registers. *************************************************************************/ + +/* + * Given a `blocktyp` that is a struct describing the layout of + * registers in a block (e.g. `blocktyp = struct + * w5500_ll_block_common_reg`), read or write the `field` register + * (where `field` must be a field in that struct). + */ + +#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ + typeof((blocktyp){}.field) lval = val; \ + w5500ll_writev(spidev, \ + offsetof(blocktyp, field), blockid, \ + &((struct iovec){ \ + .iov_base = &lval, \ + .iov_len = sizeof(lval), \ + }), 1, \ + 0, 0); \ + } while (0) + +/* The datasheet tells us that multi-byte reads are non-atomic and + * that "it is recommended that you read all 16-bits twice or more + * until getting the same value". */ #if CONFIG_W5500_LL_DEBUG -#define w5500ll_readv(...) _w5500ll_read(__func__, __VA_ARGS__) -_w5500ll_readv(const char *func, + #define _w5500ll_read_repeated(...) _w5500ll_n_read_repeated (__func__, __VA_ARGS__) #else -w5500ll_readv( -#endif - lo_interface spi spidev, uint16_t addr, uint8_t block, - const struct iovec *iov, int iovcnt, - size_t max) -{ - assert(!LO_IS_NULL(spidev)); - assert((block & ~CTL_MASK_BLOCK) == 0); - assert(iov); - assert(iovcnt > 0); -#if CONFIG_W5500_LL_DEBUG - log_n_debugln(W5500_LL, - func, "(): w5500ll_read(spidev", - ", addr=", (base16_u16_, addr), - ", block=", (ctl_block, block), - ", iov", - ", iovcnt=", iovcnt, - ")"); + #define _w5500ll_n_read_repeated(func, ...) _w5500ll_read_repeated (__VA_ARGS__) #endif +void _w5500ll_n_read_repeated(const char *func, + lo_interface spi spidev, uint16_t addr, uint8_t block, + void *buf, void *buf_scratch, size_t len); +#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ + typeof((blocktyp){}.field) val; \ + if (sizeof(val) == 1) { \ + w5500ll_readv(spidev, \ + offsetof(blocktyp, field), blockid, \ + &((struct iovec){ \ + .iov_base = &val, \ + .iov_len = sizeof(val), \ + }), 1, \ + 0); \ + } else { \ + typeof(val) val2; \ + _w5500ll_read_repeated(spidev, \ + offsetof(blocktyp, field), blockid, \ + &val, &val2, sizeof(val)); \ + } \ + val; \ + }) - uint8_t header[] = { - (uint8_t)((addr >> 8) & 0xFF), - (uint8_t)(addr & 0xFF), - (block & CTL_MASK_BLOCK) | CTL_R | CTL_OM_VDM, - }; - int inner_cnt = 1+io_slice_cnt(iov, iovcnt, 0, max); - struct duplex_iovec *inner = stack_alloc(inner_cnt, struct duplex_iovec); - inner[0] = (struct duplex_iovec){ - .iov_read_to = IOVEC_DISCARD, - .iov_write_from = header, - .iov_len = sizeof(header), - }; - io_slice_rd_to_duplex(&inner[1], iov, iovcnt, 0, max); - LO_CALL(spidev, readwritev, inner, inner_cnt); -} +/* Register blocks. ******************************************************************************/ /* Common chip-wide registers. ***********************************************/ @@ -387,49 +364,4 @@ static_assert(sizeof(struct w5500ll_block_sock_reg) == 0x30); struct w5500ll_block_sock_reg, \ field) -/******************************************************************************/ - -#define w5500ll_write_reg(spidev, blockid, blocktyp, field, val) do { \ - typeof((blocktyp){}.field) lval = val; \ - w5500ll_writev(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &((struct iovec){ \ - &lval, \ - sizeof(lval), \ - }), \ - 1, 0, 0); \ - } while (0) - -/* The datasheet tells us that multi-byte reads are non-atomic and - * that "it is recommended that you read all 16-bits twice or more - * until getting the same value". */ -#define w5500ll_read_reg(spidev, blockid, blocktyp, field) ({ \ - typeof((blocktyp){}.field) val; \ - w5500ll_readv(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &((struct iovec){ \ - .iov_base = &val, \ - .iov_len = sizeof(val), \ - }), \ - 1, 0); \ - if (sizeof(val) > 1) \ - for (;;) { \ - typeof(val) val2; \ - w5500ll_readv(spidev, \ - offsetof(blocktyp, field), \ - blockid, \ - &((struct iovec){ \ - .iov_base = &val2, \ - .iov_len = sizeof(val), \ - }), \ - 1, 0); \ - if (memcmp(&val2, &val, sizeof(val)) == 0) \ - break; \ - val = val2; \ - } \ - val; \ - }) - #endif /* _LIBHW_CR_W5500_LL_H_ */ diff --git a/libhw_generic/alarmclock.c b/libhw_generic/alarmclock.c index e3aaea3..3579829 100644 --- a/libhw_generic/alarmclock.c +++ b/libhw_generic/alarmclock.c @@ -7,3 +7,19 @@ #include <libhw/generic/alarmclock.h> lo_interface alarmclock bootclock = {}; + +void alarmclock_sleep_for_ns(lo_interface alarmclock clock, uint64_t delta_ns) { + alarmclock_sleep_until_ns(clock, LO_CALL(clock, get_time_ns) + delta_ns); +} + +void alarmclock_sleep_for_us(lo_interface alarmclock clock, uint64_t delta_us) { + alarmclock_sleep_for_ns(clock, delta_us * (NS_PER_S/US_PER_S)); +} + +void alarmclock_sleep_for_ms(lo_interface alarmclock clock, uint64_t delta_ms) { + alarmclock_sleep_for_ns(clock, delta_ms * (NS_PER_S/MS_PER_S)); +} + +void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s) { + alarmclock_sleep_for_ns(clock, delta_s * NS_PER_S); +} diff --git a/libhw_generic/include/libhw/generic/alarmclock.h b/libhw_generic/include/libhw/generic/alarmclock.h index 7f603cb..697d4be 100644 --- a/libhw_generic/include/libhw/generic/alarmclock.h +++ b/libhw_generic/include/libhw/generic/alarmclock.h @@ -62,21 +62,10 @@ LO_INTERFACE(alarmclock); void alarmclock_sleep_until_ns(lo_interface alarmclock clock, uint64_t abstime_ns); -static inline void alarmclock_sleep_for_ns(lo_interface alarmclock clock, uint64_t delta_ns) { - alarmclock_sleep_until_ns(clock, LO_CALL(clock, get_time_ns) + delta_ns); -} - -static inline void alarmclock_sleep_for_us(lo_interface alarmclock clock, uint64_t delta_us) { - alarmclock_sleep_for_ns(clock, delta_us * (NS_PER_S/US_PER_S)); -} - -static inline void alarmclock_sleep_for_ms(lo_interface alarmclock clock, uint64_t delta_ms) { - alarmclock_sleep_for_ns(clock, delta_ms * (NS_PER_S/MS_PER_S)); -} - -static inline void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s) { - alarmclock_sleep_for_ns(clock, delta_s * NS_PER_S); -} +void alarmclock_sleep_for_ns(lo_interface alarmclock clock, uint64_t delta_ns); +void alarmclock_sleep_for_us(lo_interface alarmclock clock, uint64_t delta_us); +void alarmclock_sleep_for_ms(lo_interface alarmclock clock, uint64_t delta_ms); +void alarmclock_sleep_for_s(lo_interface alarmclock clock, uint64_t delta_s); /* Globals ********************************************************************/ diff --git a/libhw_generic/include/libhw/generic/io.h b/libhw_generic/include/libhw/generic/io.h index 96680bb..8b3fb9c 100644 --- a/libhw_generic/include/libhw/generic/io.h +++ b/libhw_generic/include/libhw/generic/io.h @@ -39,6 +39,8 @@ struct duplex_iovec { /* utilities ******************************************************************/ +/* If byte_max_cnt == 0, then there is no maximum. */ + /* slice iovec lists */ int io_slice_cnt ( const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); void io_slice (struct iovec *dst, const struct iovec *src, int src_cnt, size_t byte_start, size_t byte_max_cnt); diff --git a/libhw_generic/include/libhw/generic/net.h b/libhw_generic/include/libhw/generic/net.h index 04d1082..55e4a6f 100644 --- a/libhw_generic/include/libhw/generic/net.h +++ b/libhw_generic/include/libhw/generic/net.h @@ -126,7 +126,10 @@ struct net_iface_config { \ LO_FUNC(lo_interface net_stream_listener, tcp_listen, uint16_t local_port) \ LO_FUNC(lo_interface net_stream_conn , tcp_dial , struct net_ip4_addr remote_node, uint16_t remote_port) \ - LO_FUNC(lo_interface net_packet_conn , udp_conn , uint16_t local_port) + LO_FUNC(lo_interface net_packet_conn , udp_conn , uint16_t local_port) \ + \ + /** FIXME: arp_ping should probably have an explicit timeout or something. */ \ + LO_FUNC(bool , arp_ping , struct net_ip4_addr) LO_INTERFACE(net_iface); #endif /* _LIBHW_GENERIC_NET_H_ */ diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt index 48407bd..c6405ad 100644 --- a/libmisc/CMakeLists.txt +++ b/libmisc/CMakeLists.txt @@ -7,11 +7,15 @@ add_library(libmisc INTERFACE) target_include_directories(libmisc PUBLIC INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) target_sources(libmisc INTERFACE assert.c + endian.c fmt.c + hash.c intercept.c linkedlist.c log.c map.c + rand.c + utf8.c ) add_lib_test(libmisc test_assert) diff --git a/libmisc/endian.c b/libmisc/endian.c new file mode 100644 index 0000000..2528f48 --- /dev/null +++ b/libmisc/endian.c @@ -0,0 +1,136 @@ +/* libmisc/endian.c - Endian-conversion helpers + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/macro.h> /* for LM_FORCE_SEMICOLON */ + +#include <libmisc/endian.h> + +#define endian_declare_wrappers(NBIT, ENDIAN) \ + uint##NBIT##ENDIAN##_t uint##NBIT##ENDIAN##_marshal(uint##NBIT##_t in) { \ + uint##NBIT##ENDIAN##_t out; \ + uint##NBIT##ENDIAN##_encode(out.octets, in); \ + return out; \ + } \ + uint##NBIT##_t uint##NBIT##ENDIAN##_unmarshal(uint##NBIT##ENDIAN##_t in) { \ + return uint##NBIT##ENDIAN##_decode(in.octets); \ + } \ + LM_FORCE_SEMICOLON + +/* Big endian *****************************************************************/ + +size_t uint16be_encode(uint8_t *out, uint16_t in) { + out[0] = (uint8_t)((in >> 8) & 0xFF); + out[1] = (uint8_t)((in >> 0) & 0xFF); + return 2; +} + +uint16_t uint16be_decode(uint8_t *in) { + return (((uint16_t)(in[0])) << 8) + | (((uint16_t)(in[1])) << 0) + ; +} + +size_t uint32be_encode(uint8_t *out, uint32_t in) { + out[0] = (uint8_t)((in >> 24) & 0xFF); + out[1] = (uint8_t)((in >> 16) & 0xFF); + out[2] = (uint8_t)((in >> 8) & 0xFF); + out[3] = (uint8_t)((in >> 0) & 0xFF); + return 4; +} + +uint32_t uint32be_decode(uint8_t *in) { + return (((uint32_t)(in[0])) << 24) + | (((uint32_t)(in[1])) << 16) + | (((uint32_t)(in[2])) << 8) + | (((uint32_t)(in[3])) << 0) + ; +} + +size_t uint64be_encode(uint8_t *out, uint64_t in) { + out[0] = (uint8_t)((in >> 56) & 0xFF); + out[1] = (uint8_t)((in >> 48) & 0xFF); + out[2] = (uint8_t)((in >> 40) & 0xFF); + out[3] = (uint8_t)((in >> 32) & 0xFF); + out[4] = (uint8_t)((in >> 24) & 0xFF); + out[5] = (uint8_t)((in >> 16) & 0xFF); + out[6] = (uint8_t)((in >> 8) & 0xFF); + out[7] = (uint8_t)((in >> 0) & 0xFF); + return 8; +} + +uint64_t uint64be_decode(uint8_t *in) { + return (((uint64_t)(in[0])) << 56) + | (((uint64_t)(in[1])) << 48) + | (((uint64_t)(in[2])) << 40) + | (((uint64_t)(in[3])) << 32) + | (((uint64_t)(in[4])) << 24) + | (((uint64_t)(in[5])) << 16) + | (((uint64_t)(in[6])) << 8) + | (((uint64_t)(in[7])) << 0) + ; +} + +endian_declare_wrappers(16, be); +endian_declare_wrappers(32, be); +endian_declare_wrappers(64, be); + +/* Little endian **************************************************************/ + +size_t uint16le_encode(uint8_t *out, uint16_t in) { + out[0] = (uint8_t)((in >> 0) & 0xFF); + out[1] = (uint8_t)((in >> 8) & 0xFF); + return 2; +} + +uint16_t uint16le_decode(uint8_t *in) { + return (((uint16_t)(in[0])) << 0) + | (((uint16_t)(in[1])) << 8) + ; +} + +size_t uint32le_encode(uint8_t *out, uint32_t in) { + out[0] = (uint8_t)((in >> 0) & 0xFF); + out[1] = (uint8_t)((in >> 8) & 0xFF); + out[2] = (uint8_t)((in >> 16) & 0xFF); + out[3] = (uint8_t)((in >> 24) & 0xFF); + return 4; +} + +uint32_t uint32le_decode(uint8_t *in) { + return (((uint32_t)(in[0])) << 0) + | (((uint32_t)(in[1])) << 8) + | (((uint32_t)(in[2])) << 16) + | (((uint32_t)(in[3])) << 24) + ; +} + +size_t uint64le_encode(uint8_t *out, uint64_t in) { + out[0] = (uint8_t)((in >> 0) & 0xFF); + out[1] = (uint8_t)((in >> 8) & 0xFF); + out[2] = (uint8_t)((in >> 16) & 0xFF); + out[3] = (uint8_t)((in >> 24) & 0xFF); + out[4] = (uint8_t)((in >> 32) & 0xFF); + out[5] = (uint8_t)((in >> 40) & 0xFF); + out[6] = (uint8_t)((in >> 48) & 0xFF); + out[7] = (uint8_t)((in >> 56) & 0xFF); + return 8; +} + +uint64_t uint64le_decode(uint8_t *in) { + return (((uint64_t)(in[0])) << 0) + | (((uint64_t)(in[1])) << 8) + | (((uint64_t)(in[2])) << 16) + | (((uint64_t)(in[3])) << 24) + | (((uint64_t)(in[4])) << 32) + | (((uint64_t)(in[5])) << 40) + | (((uint64_t)(in[6])) << 48) + | (((uint64_t)(in[7])) << 56) + ; +} + +endian_declare_wrappers(16, le); +endian_declare_wrappers(32, le); +endian_declare_wrappers(64, le); diff --git a/libmisc/fmt.c b/libmisc/fmt.c index 6cf1d8d..7c18ef5 100644 --- a/libmisc/fmt.c +++ b/libmisc/fmt.c @@ -14,6 +14,31 @@ static const char *const hexdig = "0123456789ABCDEF"; /* small/trivial formatters ***************************************************/ +void fmt_print_mem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + while (size--) + fmt_print_byte(w, *(str++)); +} +void fmt_print_str(lo_interface fmt_dest w, const char *str) { + while (*str) + fmt_print_byte(w, *(str++)); +} +void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size) { + while (size-- && *str) + fmt_print_byte(w, *(str++)); +} + +void fmt_print_hmem(lo_interface fmt_dest w, const void *_str, size_t size) { + const uint8_t *str = _str; + fmt_print_byte(w, '{'); + for (size_t i = 0; i < size; i++) { + if (i) + fmt_print_byte(w, ','); + fmt_print_hbyte(w, str[i]); + } + fmt_print_byte(w, '}'); +} + void fmt_print_byte(lo_interface fmt_dest w, uint8_t b) { LO_CALL(w, putb, b); } @@ -46,9 +71,33 @@ void fmt_print_ptr(lo_interface fmt_dest w, void *ptr) { */ void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b) { fmt_print_byte(w, '\''); - if (' ' <= b && b <= '~') { - if (b == '\'' || b == '\\') - fmt_print_byte(w, '\\'); + if (b == '\0' || + b == '\b' || + b == '\f' || + b == '\n' || + b == '\r' || + b == '\t' || + b == '\v' || + b == '\\' || + b == '\'' || + b == '"' || + b == '?') { + fmt_print_byte(w, '\\'); + switch (b) { + case '\0': fmt_print_byte(w, '0'); break; + case '\a': fmt_print_byte(w, 'a'); break; + case '\b': fmt_print_byte(w, 'b'); break; + case '\f': fmt_print_byte(w, 'f'); break; + case '\n': fmt_print_byte(w, 'n'); break; + case '\r': fmt_print_byte(w, 'r'); break; + case '\t': fmt_print_byte(w, 't'); break; + case '\v': fmt_print_byte(w, 'v'); break; + case '\\': fmt_print_byte(w, '\\'); break; + case '\'': fmt_print_byte(w, '\''); break; + case '"': fmt_print_byte(w, '"'); break; + case '?': fmt_print_byte(w, '?'); break; + } + } else if (' ' <= b && b <= '~') { fmt_print_byte(w, b); } else { fmt_print_byte(w, '\\'); diff --git a/libmisc/hash.c b/libmisc/hash.c new file mode 100644 index 0000000..3814cec --- /dev/null +++ b/libmisc/hash.c @@ -0,0 +1,24 @@ +/* libmisc/hash.c - General-purpose hash utilities + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/hash.h> + +/* djb2 hash */ +void hash_init(hash_t *hash) { + *hash = 5381; +} +void hash_write(hash_t *hash, void *dat, size_t len) { + for (size_t i = 0; i < len; i++) + *hash = (*hash * 33) + (hash_t)(((unsigned char *)dat)[i]); +} + +/* utilities */ +hash_t hash(void *dat, size_t len) { + hash_t h; + hash_init(&h); + hash_write(&h, dat, len); + return h; +} diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h index 75240fe..966c3bc 100644 --- a/libmisc/include/libmisc/endian.h +++ b/libmisc/include/libmisc/endian.h @@ -10,204 +10,25 @@ #include <stddef.h> /* for size_t */ #include <stdint.h> /* for uint{n}_t */ -#include <libmisc/assert.h> - -/* Big endian *****************************************************************/ - -typedef struct { - uint8_t octets[2]; -} uint16be_t; -static_assert(sizeof(uint16be_t) == 2); - -static inline size_t uint16be_encode(uint8_t *out, uint16_t in) { - out[0] = (uint8_t)((in >> 8) & 0xFF); - out[1] = (uint8_t)((in >> 0) & 0xFF); - return 2; -} - -static inline uint16_t uint16be_decode(uint8_t *in) { - return (((uint16_t)(in[0])) << 8) - | (((uint16_t)(in[1])) << 0) - ; -} - -static inline uint16be_t uint16be_marshal(uint16_t in) { - uint16be_t out; - uint16be_encode(out.octets, in); - return out; -} - -static inline uint16_t uint16be_unmarshal(uint16be_t in) { - return uint16be_decode(in.octets); -} - -typedef struct { - uint8_t octets[4]; -} uint32be_t; -static_assert(sizeof(uint32be_t) == 4); - -static inline size_t uint32be_encode(uint8_t *out, uint32_t in) { - out[0] = (uint8_t)((in >> 24) & 0xFF); - out[1] = (uint8_t)((in >> 16) & 0xFF); - out[2] = (uint8_t)((in >> 8) & 0xFF); - out[3] = (uint8_t)((in >> 0) & 0xFF); - return 4; -} - -static inline uint32_t uint32be_decode(uint8_t *in) { - return (((uint32_t)(in[0])) << 24) - | (((uint32_t)(in[1])) << 16) - | (((uint32_t)(in[2])) << 8) - | (((uint32_t)(in[3])) << 0) - ; -} - -static inline uint32be_t uint32be_marshal(uint32_t in) { - uint32be_t out; - uint32be_encode(out.octets, in); - return out; -} - -static inline uint32_t uint32be_unmarshal(uint32be_t in) { - return uint32be_decode(in.octets); -} - -typedef struct { - uint8_t octets[8]; -} uint64be_t; -static_assert(sizeof(uint64be_t) == 8); - -static inline size_t uint64be_encode(uint8_t *out, uint64_t in) { - out[0] = (uint8_t)((in >> 56) & 0xFF); - out[1] = (uint8_t)((in >> 48) & 0xFF); - out[2] = (uint8_t)((in >> 40) & 0xFF); - out[3] = (uint8_t)((in >> 32) & 0xFF); - out[4] = (uint8_t)((in >> 24) & 0xFF); - out[5] = (uint8_t)((in >> 16) & 0xFF); - out[6] = (uint8_t)((in >> 8) & 0xFF); - out[7] = (uint8_t)((in >> 0) & 0xFF); - return 8; -} - -static inline uint64_t uint64be_decode(uint8_t *in) { - return (((uint64_t)(in[0])) << 56) - | (((uint64_t)(in[1])) << 48) - | (((uint64_t)(in[2])) << 40) - | (((uint64_t)(in[3])) << 32) - | (((uint64_t)(in[4])) << 24) - | (((uint64_t)(in[5])) << 16) - | (((uint64_t)(in[6])) << 8) - | (((uint64_t)(in[7])) << 0) - ; -} - -static inline uint64be_t uint64be_marshal(uint64_t in) { - uint64be_t out; - uint64be_encode(out.octets, in); - return out; -} - -static inline uint64_t uint64be_unmarshal(uint64be_t in) { - return uint64be_decode(in.octets); -} - -/* Little endian **************************************************************/ - -typedef struct { - uint8_t octets[2]; -} uint16le_t; -static_assert(sizeof(uint16le_t) == 2); - -static inline size_t uint16le_encode(uint8_t *out, uint16_t in) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); - return 2; -} - -static inline uint16_t uint16le_decode(uint8_t *in) { - return (((uint16_t)(in[0])) << 0) - | (((uint16_t)(in[1])) << 8) - ; -} - -static inline uint16le_t uint16le_marshal(uint16_t in) { - uint16le_t out; - uint16le_encode(out.octets, in); - return out; -} - -static inline uint16_t uint16le_unmarshal(uint16le_t in) { - return uint16le_decode(in.octets); -} - -typedef struct { - uint8_t octets[4]; -} uint32le_t; -static_assert(sizeof(uint32le_t) == 4); - -static inline size_t uint32le_encode(uint8_t *out, uint32_t in) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); - out[2] = (uint8_t)((in >> 16) & 0xFF); - out[3] = (uint8_t)((in >> 24) & 0xFF); - return 4; -} - -static inline uint32_t uint32le_decode(uint8_t *in) { - return (((uint32_t)(in[0])) << 0) - | (((uint32_t)(in[1])) << 8) - | (((uint32_t)(in[2])) << 16) - | (((uint32_t)(in[3])) << 24) - ; -} - -static inline uint32le_t uint32le_marshal(uint32_t in) { - uint32le_t out; - uint32le_encode(out.octets, in); - return out; -} - -static inline uint32_t uint32le_unmarshal(uint32le_t in) { - return uint32le_decode(in.octets); -} - -typedef struct { - uint8_t octets[8]; -} uint64le_t; -static_assert(sizeof(uint64le_t) == 8); - -static inline size_t uint64le_encode(uint8_t *out, uint64_t in) { - out[0] = (uint8_t)((in >> 0) & 0xFF); - out[1] = (uint8_t)((in >> 8) & 0xFF); - out[2] = (uint8_t)((in >> 16) & 0xFF); - out[3] = (uint8_t)((in >> 24) & 0xFF); - out[4] = (uint8_t)((in >> 32) & 0xFF); - out[5] = (uint8_t)((in >> 40) & 0xFF); - out[6] = (uint8_t)((in >> 48) & 0xFF); - out[7] = (uint8_t)((in >> 56) & 0xFF); - return 8; -} - -static inline uint64_t uint64le_decode(uint8_t *in) { - return (((uint64_t)(in[0])) << 0) - | (((uint64_t)(in[1])) << 8) - | (((uint64_t)(in[2])) << 16) - | (((uint64_t)(in[3])) << 24) - | (((uint64_t)(in[4])) << 32) - | (((uint64_t)(in[5])) << 40) - | (((uint64_t)(in[6])) << 48) - | (((uint64_t)(in[7])) << 56) - ; -} - -static inline uint64le_t uint64le_marshal(uint64_t in) { - uint64le_t out; - uint64le_encode(out.octets, in); - return out; -} - -static inline uint64_t uint64le_unmarshal(uint64le_t in) { - return uint64le_decode(in.octets); -} +#define _endian_declare_conv(NBIT, ENDIAN) \ + /* byte array encode/decode */ \ + size_t uint##NBIT##ENDIAN##_encode(uint8_t *out, uint##NBIT##_t in); \ + uint##NBIT##_t uint##NBIT##ENDIAN##_decode(uint8_t *in); \ + /* struct marshal/unmarshal */ \ + typedef struct { \ + uint8_t octets[NBIT/8]; \ + } uint##NBIT##ENDIAN##_t; \ + uint##NBIT##ENDIAN##_t uint##NBIT##ENDIAN##_marshal(uint##NBIT##_t in); \ + uint##NBIT##_t uint##NBIT##ENDIAN##_unmarshal(uint##NBIT##ENDIAN##_t in) + +_endian_declare_conv(16, be); +_endian_declare_conv(32, be); +_endian_declare_conv(64, be); + +_endian_declare_conv(16, le); +_endian_declare_conv(32, le); +_endian_declare_conv(64, le); + +#undef _endian_declare_conv #endif /* _LIBMISC_ENDIAN_H_ */ diff --git a/libmisc/include/libmisc/fmt.h b/libmisc/include/libmisc/fmt.h index c29c085..6c04d99 100644 --- a/libmisc/include/libmisc/fmt.h +++ b/libmisc/include/libmisc/fmt.h @@ -9,6 +9,7 @@ #include <stddef.h> /* for size_t */ #include <stdint.h> /* for (u)int{n}_t */ +#include <stdlib.h> /* for realloc() */ #include <libmisc/macro.h> #include <libmisc/obj.h> @@ -24,21 +25,9 @@ LO_INTERFACE(fmt_dest); /* Simple bytes. */ void fmt_print_byte(lo_interface fmt_dest w, uint8_t b); - -/* These are `static inline` so that the compiler can unroll the loops. */ -static inline void fmt_print_mem(lo_interface fmt_dest w, const void *_str, size_t size) { - const uint8_t *str = _str; - while (size--) - fmt_print_byte(w, *(str++)); -} -static inline void fmt_print_str(lo_interface fmt_dest w, const char *str) { - while (*str) - fmt_print_byte(w, *(str++)); -} -static inline void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size) { - while (size-- && *str) - fmt_print_byte(w, *(str++)); -} +void fmt_print_mem(lo_interface fmt_dest w, const void *str, size_t size); +void fmt_print_str(lo_interface fmt_dest w, const char *str); +void fmt_print_strn(lo_interface fmt_dest w, const char *str, size_t size); /* Quoted bytes. */ void fmt_print_qbyte(lo_interface fmt_dest w, uint8_t b); @@ -46,6 +35,10 @@ void fmt_print_qmem(lo_interface fmt_dest w, const void *str, size_t size); void fmt_print_qstr(lo_interface fmt_dest w, const char *str); void fmt_print_qstrn(lo_interface fmt_dest w, const char *str, size_t size); +/* Hex bytes. */ +#define fmt_print_hbyte fmt_print_base16_u8_ +void fmt_print_hmem(lo_interface fmt_dest w, const void *str, size_t size); + /* Integers. */ #define _fmt_declare_base(base) \ void _fmt_print_base##base##_u8(lo_interface fmt_dest w, uint8_t val); \ @@ -107,6 +100,11 @@ void fmt_print_bool(lo_interface fmt_dest w, bool b); const char * : fmt_print_str , \ bool : fmt_print_bool )(w, val) +/** Same as fmt_print(), but usable from inside of fmt_print(). */ +#define fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0) +#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__) +#define _fmt_param_indirect() _fmt_param + /* print-to-memory ************************************************************/ struct fmt_buf { @@ -124,16 +122,25 @@ LO_IMPLEMENTATION_H(fmt_dest, struct fmt_buf, fmt_buf); _w.len; \ }) -/* justify ********************************************************************/ +#define fmt_asprint(...) ({ \ + struct fmt_buf _w = {}; \ + lo_interface fmt_dest w = lo_box_fmt_buf_as_fmt_dest(&_w); \ + fmt_print(w, __VA_ARGS__); \ + while (_w.cap <= _w.len) { \ + _w.cap = _w.len + 1; \ + _w.len = 0; \ + _w.dat = realloc(_w.dat, _w.cap); \ + fmt_print(w, __VA_ARGS__); \ + } \ + ((char *)_w.dat)[_w.len] = '\0'; \ + _w.dat; \ +}) -/* *grubles about not being allowed to nest things* */ -#define _fmt_param_indirect() _fmt_param -#define _fmt_print2(w, ...) do { LM_FOREACH_PARAM2_(_fmt_param2, (w), __VA_ARGS__) } while (0) -#define _fmt_param2(...) _LM_DEFER2(_fmt_param_indirect)()(__VA_ARGS__) +/* justify ********************************************************************/ #define fmt_print_ljust(w, width, fillchar, ...) do { \ size_t beg = LO_CALL(w, tell); \ - _fmt_print2(w, __VA_ARGS__); \ + fmt_print2(w, __VA_ARGS__); \ while ((LO_CALL(w, tell) - beg) < width) \ fmt_print_byte(w, fillchar); \ } while (0) @@ -141,10 +148,10 @@ LO_IMPLEMENTATION_H(fmt_dest, struct fmt_buf, fmt_buf); #define fmt_print_rjust(w, width, fillchar, ...) do { \ struct fmt_buf _discard = {}; \ lo_interface fmt_dest discard = lo_box_fmt_buf_as_fmt_dest(&_discard); \ - _fmt_print2(discard, __VA_ARGS__); \ + fmt_print2(discard, __VA_ARGS__); \ while (_discard.len++ < width) \ fmt_print_byte(w, fillchar); \ - _fmt_print2(w, __VA_ARGS__); \ + fmt_print2(w, __VA_ARGS__); \ } while (0) void fmt_print_base16_u8_(lo_interface fmt_dest w, uint8_t x); diff --git a/libmisc/include/libmisc/hash.h b/libmisc/include/libmisc/hash.h index 58a895f..029bd3b 100644 --- a/libmisc/include/libmisc/hash.h +++ b/libmisc/include/libmisc/hash.h @@ -10,22 +10,12 @@ #include <stddef.h> /* for size_t */ #include <stdint.h> /* for uint{n}_t */ -/* djb2 hash */ -typedef uint32_t hash_t; -static inline void hash_init(hash_t *hash) { - *hash = 5381; -} -static inline void hash_write(hash_t *hash, void *dat, size_t len) { - for (size_t i = 0; i < len; i++) - *hash = (*hash * 33) + (hash_t)(((unsigned char *)dat)[i]); -} +/* base */ +typedef uint32_t hash_t; /* size subject to change */ +void hash_init(hash_t *hash); +void hash_write(hash_t *hash, void *dat, size_t len); /* utilities */ -static inline hash_t hash(void *dat, size_t len) { - hash_t h; - hash_init(&h); - hash_write(&h, dat, len); - return h; -} +hash_t hash(void *dat, size_t len); #endif /* _LIBMISC_HASH_H_ */ diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h index 7ef238b..ca16f42 100644 --- a/libmisc/include/libmisc/rand.h +++ b/libmisc/include/libmisc/rand.h @@ -7,40 +7,12 @@ #ifndef _LIBMISC_RAND_H_ #define _LIBMISC_RAND_H_ -#include <stdint.h> /* for uint{n}_t, UINT{n}_C() */ -#include <stdlib.h> /* for random() */ - -#include <libmisc/assert.h> +#include <stdint.h> /* for uint{n}_t */ /** * Return a psuedo-random number in the half-open interval [0,cnt). * `cnt` must not be greater than 1<<63. */ -static inline uint64_t rand_uint63n(uint64_t cnt) { - assert(cnt != 0 && ((cnt-1) & 0x8000000000000000) == 0); - if (cnt <= UINT64_C(1)<<31) { - uint32_t fair_cnt = ((UINT32_C(1)<<31) / cnt) * cnt; - uint32_t rnd; - do { - rnd = random(); - } while (rnd >= fair_cnt); - return rnd % cnt; - } else if (cnt <= UINT64_C(1)<<62) { - uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt; - uint64_t rnd; - do { - rnd = (((uint64_t)random()) << 31) | random(); - } while (rnd >= fair_cnt); - return rnd % cnt; - } else if (cnt <= UINT64_C(1)<<63) { - uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt; - uint64_t rnd; - do { - rnd = (((uint64_t)random()) << 62) | (((uint64_t)random()) << 31) | random(); - } while (rnd >= fair_cnt); - return rnd % cnt; - } - assert_notreached("cnt is out of bounds"); -} +uint64_t rand_uint63n(uint64_t cnt); #endif /* _LIBMISC_RAND_H_ */ diff --git a/libmisc/include/libmisc/utf8.h b/libmisc/include/libmisc/utf8.h index b5e1b0b..54fcc92 100644 --- a/libmisc/include/libmisc/utf8.h +++ b/libmisc/include/libmisc/utf8.h @@ -15,38 +15,9 @@ * bytes. Invalid UTF-8 is indicated with chlen=0. For valid UTF-8, * chlen is always in the range [1, 4]. */ -static inline void utf8_decode_codepoint(const uint8_t *str, size_t len, uint32_t *ret_ch, uint8_t *ret_chlen) { - uint32_t ch; - uint8_t chlen; - if ((str[0] & 0b10000000) == 0b00000000) { ch = str[0] & 0b01111111; chlen = 1; } - else if ((str[0] & 0b11100000) == 0b11000000) { ch = str[0] & 0b00011111; chlen = 2; } - else if ((str[0] & 0b11110000) == 0b11100000) { ch = str[0] & 0b00001111; chlen = 3; } - else if ((str[0] & 0b11111000) == 0b11110000) { ch = str[0] & 0b00000111; chlen = 4; } - else goto invalid; - if ((ch == 0 && chlen != 1) || chlen > len) goto invalid; - for (uint8_t i = 1; i < chlen; i++) { - if ((str[i] & 0b11000000) != 0b10000000) goto invalid; - ch = (ch << 6) | (str[i] & 0b00111111); - } - if (ch > 0x10FFFF) goto invalid; - *ret_ch = ch; - *ret_chlen = chlen; - return; - invalid: - *ret_chlen = 0; -} +void utf8_decode_codepoint(const uint8_t *str, size_t len, uint32_t *ret_ch, uint8_t *ret_chlen); -static inline bool _utf8_is_valid(const uint8_t *str, size_t len, bool forbid_nul) { - for (size_t pos = 0; pos < len;) { - uint32_t ch; - uint8_t chlen; - utf8_decode_codepoint(&str[pos], len-pos, &ch, &chlen); - if (chlen == 0 || (forbid_nul && ch == 0)) - return false; - pos += chlen; - } - return true; -} +bool _utf8_is_valid(const uint8_t *str, size_t len, bool forbid_nul); #define utf8_is_valid(str, len) _utf8_is_valid(str, len, false) #define utf8_is_valid_without_nul(str, len) _utf8_is_valid(str, len, true) diff --git a/libmisc/rand.c b/libmisc/rand.c new file mode 100644 index 0000000..d1643ee --- /dev/null +++ b/libmisc/rand.c @@ -0,0 +1,38 @@ +/* libmisc/rand.c - Non-crytpographic random-number utilities + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <stdlib.h> /* for random() */ + +#include <libmisc/assert.h> + +#include <libmisc/rand.h> + +uint64_t rand_uint63n(uint64_t cnt) { + assert(cnt != 0 && ((cnt-1) & 0x8000000000000000) == 0); + if (cnt <= UINT64_C(1)<<31) { + uint32_t fair_cnt = ((UINT32_C(1)<<31) / cnt) * cnt; + uint32_t rnd; + do { + rnd = random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else if (cnt <= UINT64_C(1)<<62) { + uint64_t fair_cnt = ((UINT64_C(1)<<62) / cnt) * cnt; + uint64_t rnd; + do { + rnd = (((uint64_t)random()) << 31) | random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } else if (cnt <= UINT64_C(1)<<63) { + uint64_t fair_cnt = ((UINT64_C(1)<<63) / cnt) * cnt; + uint64_t rnd; + do { + rnd = (((uint64_t)random()) << 62) | (((uint64_t)random()) << 31) | random(); + } while (rnd >= fair_cnt); + return rnd % cnt; + } + assert_notreached("cnt is out of bounds"); +} diff --git a/libmisc/tests/test_fmt.c b/libmisc/tests/test_fmt.c index 6a6eb7c..64b3b8a 100644 --- a/libmisc/tests/test_fmt.c +++ b/libmisc/tests/test_fmt.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +#include <stdlib.h> /* for free() */ #include <string.h> /* for strcmp(), memcmp(), memset() */ #include <libmisc/fmt.h> @@ -62,6 +63,26 @@ int main() { test_assert(strcmp(str, "\"hell\"") == 0); memset(str, 0, sizeof(str)); + do_print((qstr, "hello\xFFworld🚧")); + test_assert(strcmp(str, "\"hello\\xFFworld\\U0001F6A7\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qstr, "¡hello world!")); + test_assert(strcmp(str, "\"\\u00A1hello world!\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "🚧", 3)); /* truncated UTF-8 */ + test_assert(strcmp(str, "\"\\xF0\\x9F\\x9A\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "\xF7\xBF\xBF\xBF", 4)); /* over unicode_max */ + test_assert(strcmp(str, "\"\\xF7\\xBF\\xBF\\xBF\"") == 0); + memset(str, 0, sizeof(str)); + + do_print((qmem, "\xE0\xA0", 2)); /* non-optimal encoding (of ' ') */ + test_assert(strcmp(str, "\"\\xE0\\xA0\"") == 0); + memset(str, 0, sizeof(str)); + do_print((byte, 'h'), (byte, 'w')); test_assert(strcmp(str, "hw") == 0); memset(str, 0, sizeof(str)); @@ -70,6 +91,26 @@ int main() { test_assert(strcmp(str, "'h''w'") == 0); memset(str, 0, sizeof(str)); + do_print((qbyte, 0)); + test_assert(strcmp(str, "'\\0'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, '\\')); + test_assert(strcmp(str, "'\\\\'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, '\'')); + test_assert(strcmp(str, "'\\''") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, '\n')); + test_assert(strcmp(str, "'\\n'") == 0); + memset(str, 0, sizeof(str)); + + do_print((qbyte, 0xff)); + test_assert(strcmp(str, "'\\xFF'") == 0); + memset(str, 0, sizeof(str)); + do_print("zero ", 0); test_assert(strcmp(str, "zero 0") == 0); memset(str, 0, sizeof(str)); @@ -166,5 +207,37 @@ int main() { test_assert(strcmp(str, " 1x") == 0); memset(str, 0, sizeof(str)); + do_print((base16_u8_, 1)); + test_assert(strcmp(str, "0x01") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u16_, 1)); + test_assert(strcmp(str, "0x0001") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u32_, 1)); + test_assert(strcmp(str, "0x00000001") == 0); + memset(str, 0, sizeof(str)); + + do_print((base16_u64_, 1)); + test_assert(strcmp(str, "0x0000000000000001") == 0); + memset(str, 0, sizeof(str)); + + do_print((hbyte, 1)); + test_assert(strcmp(str, "0x01") == 0); + memset(str, 0, sizeof(str)); + + do_print((hmem, "hello", 6)); + test_assert(strcmp(str, "{0x68,0x65,0x6C,0x6C,0x6F,0x00}") == 0); + memset(str, 0, sizeof(str)); + + char *astr = fmt_asprint(""); + test_assert(astr != NULL && astr[0] == '\0'); + free(astr); + + astr = fmt_asprint("hello ", (base2, 9), (qstr, " world!\n")); + test_assert(strcmp(astr, "hello 1001\" world!\\n\"") == 0); + free(astr); + return 0; } diff --git a/libmisc/utf8.c b/libmisc/utf8.c new file mode 100644 index 0000000..28357f0 --- /dev/null +++ b/libmisc/utf8.c @@ -0,0 +1,44 @@ +/* libmisc/utf8.c - UTF-8 routines + * + * Copyright (C) 2024-2025 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-License-Identifier: AGPL-3.0-or-later + */ + +#include <libmisc/utf8.h> + +void utf8_decode_codepoint(const uint8_t *str, size_t len, uint32_t *ret_ch, uint8_t *ret_chlen) { + uint32_t ch; + uint8_t chlen; + uint32_t chmin; + if ((str[0] & 0b10000000) == 0b00000000) { ch = str[0] & 0b01111111; chlen = 1; chmin = 0; } /* bits=7+(0*6)= 7 */ + else if ((str[0] & 0b11100000) == 0b11000000) { ch = str[0] & 0b00011111; chlen = 2; chmin = UINT32_C(1)<< 7; } /* bits=5+(1*6)=11 */ + else if ((str[0] & 0b11110000) == 0b11100000) { ch = str[0] & 0b00001111; chlen = 3; chmin = UINT32_C(1)<<11; } /* bits=4+(2*6)=16 */ + else if ((str[0] & 0b11111000) == 0b11110000) { ch = str[0] & 0b00000111; chlen = 4; chmin = UINT32_C(1)<<16; } /* bits=3+(3*6)=21 */ + else goto invalid; + if (chlen > len) + goto invalid; + for (uint8_t i = 1; i < chlen; i++) { + if ((str[i] & 0b11000000) != 0b10000000) + goto invalid; + ch = (ch << 6) | (str[i] & 0b00111111); + } + if (ch > 0x10FFFF || ch < chmin) + goto invalid; + *ret_ch = ch; + *ret_chlen = chlen; + return; + invalid: + *ret_chlen = 0; +} + +bool _utf8_is_valid(const uint8_t *str, size_t len, bool forbid_nul) { + for (size_t pos = 0; pos < len;) { + uint32_t ch; + uint8_t chlen; + utf8_decode_codepoint(&str[pos], len-pos, &ch, &chlen); + if (chlen == 0 || (forbid_nul && ch == 0)) + return false; + pos += chlen; + } + return true; +} |