summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt10
-rw-r--r--cmd/sbc_harness/CMakeLists.txt4
-rw-r--r--cmd/sbc_harness/main.c13
-rw-r--r--cmd/srv9p/static9p.c3
-rw-r--r--lib9p/9p.generated.c5
-rwxr-xr-xlib9p/idl.gen3
-rw-r--r--lib9p/include/lib9p/9p.generated.h2
-rw-r--r--lib9p/include/lib9p/9p.h3
-rw-r--r--lib9p/map.h2
-rw-r--r--lib9p/srv.c2
-rw-r--r--libcr/CMakeLists.txt3
-rw-r--r--libcr/coroutine.c16
-rw-r--r--libcr_ipc/include/libcr_ipc/_linkedlist.h2
-rw-r--r--libcr_ipc/include/libcr_ipc/select.h67
-rw-r--r--libdhcp/CMakeLists.txt2
-rw-r--r--libdhcp/dhcp.c710
-rw-r--r--libdhcp/dhcp_client.c969
-rw-r--r--libdhcp/dhcp_common.h305
-rw-r--r--libdhcp/f.md27
-rw-r--r--libdhcp/include/libdhcp/client.h (renamed from libdhcp/include/libdhcp/dhcp.h)89
-rw-r--r--libhw/common_include/libhw/generic/net.h37
-rw-r--r--libhw/host_alarmclock.c2
-rw-r--r--libhw/host_net.c27
-rw-r--r--libhw/rp2040_hwspi.c7
-rw-r--r--libhw/rp2040_hwtimer.c3
-rw-r--r--libhw/rp2040_include/libhw/w5500.h35
-rw-r--r--libhw/w5500.c228
-rw-r--r--libhw/w5500_ll.h4
-rw-r--r--libmisc/CMakeLists.txt3
-rw-r--r--libmisc/assert.c23
-rw-r--r--libmisc/include/libmisc/assert.h24
-rw-r--r--libmisc/include/libmisc/endian.h3
-rw-r--r--libmisc/include/libmisc/rand.h46
-rw-r--r--libmisc/include/libmisc/vcall.h3
-rw-r--r--libusb/CMakeLists.txt1
-rw-r--r--libusb/include/libusb/tusb_helpers.h8
-rwxr-xr-xlibusb/include/libusb/tusb_helpers.h.gen8
-rw-r--r--libusb/usb_common.c3
38 files changed, 1726 insertions, 976 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 100214c..c9774c6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -16,7 +16,15 @@ project(sbc_harness)
pico_sdk_init()
-add_compile_options(-Wall -Wextra -Werror)
+add_compile_options(-Wall -Wextra -Wswitch-enum -Werror)
+
+function(_suppress_tinyusb_warnings)
+ __suppress_tinyusb_warnings()
+ set_source_files_properties(
+ ${PICO_TINYUSB_PATH}/src/device/usbd.c
+ PROPERTIES
+ COMPILE_OPTIONS "-Wno-switch-enum")
+endfunction()
function(target_embed_sources arg_target arg_hdrname)
set(embed_objs)
diff --git a/cmd/sbc_harness/CMakeLists.txt b/cmd/sbc_harness/CMakeLists.txt
index 2018cf7..9a84640 100644
--- a/cmd/sbc_harness/CMakeLists.txt
+++ b/cmd/sbc_harness/CMakeLists.txt
@@ -19,7 +19,7 @@ target_link_libraries(sbc_harness_objs
libmisc
libusb
- #libdhcp
+ libdhcp
libhw
)
pico_enable_stdio_usb(sbc_harness_objs 0)
@@ -27,6 +27,8 @@ pico_enable_stdio_uart(sbc_harness_objs 1)
pico_enable_stdio_semihosting(sbc_harness_objs 0)
pico_enable_stdio_rtt(sbc_harness_objs 0)
+suppress_tinyusb_warnings()
+
# Analyze the stack ############################################################
add_stack_analysis(sbc_harness_stack.c sbc_harness_objs)
diff --git a/cmd/sbc_harness/main.c b/cmd/sbc_harness/main.c
index b9c5330..97751a7 100644
--- a/cmd/sbc_harness/main.c
+++ b/cmd/sbc_harness/main.c
@@ -15,6 +15,7 @@
#include <libhw/w5500.h>
#include <libmisc/hash.h>
#include <libusb/usb_common.h>
+#include <libdhcp/client.h>
#include "usb_keyboard.h"
@@ -34,6 +35,15 @@ COROUTINE hello_world_cr(void *_chan) {
cr_end();
}
+COROUTINE dhcp_cr(void *_chip) {
+ struct w5500 *chip = _chip;
+ cr_begin();
+
+ dhcp_client_main(chip, "harness");
+
+ cr_end();
+}
+
int main() {
/* initialization *****************************************************/
stdio_uart_init();
@@ -80,8 +90,9 @@ int main() {
usb_keyboard_rpc_t keyboard_chan = {0};
coroutine_add(usb_keyboard_cr, &keyboard_chan);
//coroutine_add(hello_world_cr, &keyboard_chan);
- //coroutine_add(dhcp_client_cr, NULL);
+ coroutine_add(dhcp_cr, &dev_w5500);
/* event loop *********************************************************/
+ printf("main\n");
coroutine_main();
}
diff --git a/cmd/srv9p/static9p.c b/cmd/srv9p/static9p.c
index 3632c26..90f3856 100644
--- a/cmd/srv9p/static9p.c
+++ b/cmd/srv9p/static9p.c
@@ -4,8 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <assert.h>
-
+#include <libmisc/assert.h>
#include <libmisc/vcall.h>
#include "static9p.h"
diff --git a/lib9p/9p.generated.c b/lib9p/9p.generated.c
index 6136433..1a00f10 100644
--- a/lib9p/9p.generated.c
+++ b/lib9p/9p.generated.c
@@ -1,11 +1,12 @@
-/* Generated by `./lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */
+/* Generated by `lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */
-#include <assert.h>
#include <stdbool.h>
#include <stddef.h> /* for size_t */
#include <inttypes.h> /* for PRI* macros */
#include <string.h> /* for memset() */
+#include <libmisc/assert.h>
+
#include <lib9p/9p.h>
#include "internal.h"
diff --git a/lib9p/idl.gen b/lib9p/idl.gen
index ec42cfd..3be1d7e 100755
--- a/lib9p/idl.gen
+++ b/lib9p/idl.gen
@@ -681,12 +681,13 @@ def gen_c(versions: set[str], typs: list[Type]) -> str:
ret = f"""/* Generated by `{' '.join(sys.argv)}`. DO NOT EDIT! */
-#include <assert.h>
#include <stdbool.h>
#include <stddef.h> /* for size_t */
#include <inttypes.h> /* for PRI* macros */
#include <string.h> /* for memset() */
+#include <libmisc/assert.h>
+
#include <lib9p/9p.h>
#include "internal.h"
diff --git a/lib9p/include/lib9p/9p.generated.h b/lib9p/include/lib9p/9p.generated.h
index d5e94d1..feca8c9 100644
--- a/lib9p/include/lib9p/9p.generated.h
+++ b/lib9p/include/lib9p/9p.generated.h
@@ -1,4 +1,4 @@
-/* Generated by `./lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */
+/* Generated by `lib9p/idl.gen lib9p/idl/2002-9P2000.9p lib9p/idl/2005-9P2000.u.9p lib9p/idl/2012-9P2000.e.9p`. DO NOT EDIT! */
#ifndef _LIB9P_9P_H_
#error Do not include <lib9p/9p.generated.h> directly; include <lib9p/9p.h> instead
diff --git a/lib9p/include/lib9p/9p.h b/lib9p/include/lib9p/9p.h
index 171ad3b..9d22354 100644
--- a/lib9p/include/lib9p/9p.h
+++ b/lib9p/include/lib9p/9p.h
@@ -7,10 +7,11 @@
#ifndef _LIB9P_9P_H_
#define _LIB9P_9P_H_
-#include <assert.h>
#include <stdbool.h>
#include <sys/types.h> /* for ssize_t */
+#include <libmisc/assert.h>
+
#include <lib9p/linux-errno.h>
/* configuration **************************************************************/
diff --git a/lib9p/map.h b/lib9p/map.h
index c816bde..f42bb2c 100644
--- a/lib9p/map.h
+++ b/lib9p/map.h
@@ -84,7 +84,7 @@ static VAL_T *MAP_METHOD(NAME,store)(struct NAME *m, KEY_T k, VAL_T v) {
m->items[i].val = v;
return &(m->items[i].val);
}
- __builtin_unreachable();
+ assert_notreached("should have returned from inside for() loop");
}
/**
diff --git a/lib9p/srv.c b/lib9p/srv.c
index 9192794..e4212e5 100644
--- a/lib9p/srv.c
+++ b/lib9p/srv.c
@@ -4,7 +4,6 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <assert.h>
#include <alloca.h>
#include <inttypes.h> /* for PRI* */
#include <stdio.h> /* for fprintf(), stderr */
@@ -14,6 +13,7 @@
#include <libcr_ipc/chan.h>
#include <libcr_ipc/mutex.h>
#include <libcr_ipc/select.h>
+#include <libmisc/assert.h>
#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIB9P_SRV_H YES
diff --git a/libcr/CMakeLists.txt b/libcr/CMakeLists.txt
index ae7c8fe..fbc7618 100644
--- a/libcr/CMakeLists.txt
+++ b/libcr/CMakeLists.txt
@@ -8,6 +8,9 @@ target_include_directories(libcr SYSTEM INTERFACE ${CMAKE_CURRENT_LIST_DIR}/incl
target_sources(libcr INTERFACE
coroutine.c
)
+target_link_libraries(libcr INTERFACE
+ libmisc
+)
target_compile_options(libcr INTERFACE
-fno-split-stack
)
diff --git a/libcr/coroutine.c b/libcr/coroutine.c
index 41d987e..ed1c8e0 100644
--- a/libcr/coroutine.c
+++ b/libcr/coroutine.c
@@ -4,12 +4,13 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <assert.h>
#include <setjmp.h> /* for setjmp(), longjmp(), jmp_buf */
#include <stdint.h> /* for uint8_t */
#include <stdio.h> /* for printf(), fprintf(), stderr */
#include <stdlib.h> /* for aligned_alloc(), free() */
+#include <libmisc/assert.h>
+
#include <libcr/coroutine.h>
/* Configuration **************************************************************/
@@ -363,17 +364,6 @@ static cid_t coroutine_running = 0;
#define debugf(...)
#endif
-#ifdef __GLIBC__
- #define assertf(expr, ...) ({ \
- if (!(expr)) { \
- errorf("assertion: " __VA_ARGS__); \
- __assert_fail(#expr, __FILE__, __LINE__, __func__); \
- } \
- })
-#else
- #define assertf(expr, ...) assert(expr)
-#endif
-
static inline const char* coroutine_state_str(enum coroutine_state state) {
assert(state < ARRAY_LEN(coroutine_state_strs));
return coroutine_state_strs[state];
@@ -470,7 +460,7 @@ cid_t coroutine_add_with_stack_size(size_t stack_size, cr_fn_t fn, void *args) {
debugf("...stack_base=%p\n", stack_base);
/* run until cr_begin() */
cr_plat_call_with_stack(stack_base, fn, args);
- __builtin_unreachable(); /* should cr_begin() instead of returning */
+ assert_notreached("should cr_begin() instead of returning");
}
assert_cid_state(child, state == CR_RUNNABLE);
if (parent)
diff --git a/libcr_ipc/include/libcr_ipc/_linkedlist.h b/libcr_ipc/include/libcr_ipc/_linkedlist.h
index 43b7286..e5aa52a 100644
--- a/libcr_ipc/include/libcr_ipc/_linkedlist.h
+++ b/libcr_ipc/include/libcr_ipc/_linkedlist.h
@@ -7,7 +7,7 @@
#ifndef _LIBCR_IPC__LINKEDLIST_H_
#define _LIBCR_IPC__LINKEDLIST_H_
-#include <assert.h>
+#include <libmisc/assert.h>
/* singly linked list *********************************************************/
diff --git a/libcr_ipc/include/libcr_ipc/select.h b/libcr_ipc/include/libcr_ipc/select.h
index f094662..788bf53 100644
--- a/libcr_ipc/include/libcr_ipc/select.h
+++ b/libcr_ipc/include/libcr_ipc/select.h
@@ -5,10 +5,10 @@
*/
#include <alloca.h> /* for alloca() */
-#include <assert.h> /* for assert() */
-#include <stdarg.h> /* for va_* */
#include <stddef.h> /* for size_t */
-#include <stdlib.h> /* for random() */
+
+#include <libmisc/assert.h>
+#include <libmisc/rand.h>
#include <libcr_ipc/chan.h>
@@ -23,7 +23,6 @@
*/
struct cr_select_arg {
enum {
- _CR_SELECT_OP_NONE,
_CR_SELECT_OP_RECV,
_CR_SELECT_OP_SEND,
_CR_SELECT_OP_DEFAULT,
@@ -61,22 +60,9 @@ struct cr_select_arg {
((struct cr_select_arg){ \
.op = _CR_SELECT_OP_DEFAULT, \
})
-#define _CR_SELECT_END \
- ((struct cr_select_arg){ \
- .op = _CR_SELECT_OP_NONE, \
- })
/* cr_select_v(arg_cnt, arg_vec) **********************************************/
-static inline size_t _cr_select_pickone(size_t cnt) {
- long fair_cnt = (0x80000000L / cnt) * cnt;
- long rnd;
- do {
- rnd = random();
- } while (rnd >= fair_cnt);
- return rnd % cnt;
-}
-
enum _cr_select_class {
_CR_SELECT_CLASS_DEFAULT,
_CR_SELECT_CLASS_BLOCKING,
@@ -98,7 +84,7 @@ static inline enum _cr_select_class _cr_select_getclass(struct cr_select_arg arg
case _CR_SELECT_OP_DEFAULT:
return _CR_SELECT_CLASS_DEFAULT;
default:
- __builtin_unreachable();
+ assert_notreached("invalid arg.op");
}
}
@@ -139,7 +125,7 @@ static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
}
if (cnt_nonblock) {
- size_t choice = _cr_select_pickone(cnt_nonblock);
+ size_t choice = rand_uint63n(cnt_nonblock);
for (size_t i = 0, seen = 0; i < arg_cnt; i++) {
if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_NONBLOCK) {
if (seen == choice) {
@@ -154,14 +140,14 @@ static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
seen++;
}
}
- __builtin_unreachable();
+ assert_notreached("should have returned from inside for() loop");
}
if (cnt_default) {
for (size_t i = 0; i < arg_cnt; i++)
if (_cr_select_getclass(arg_vec[i]) == _CR_SELECT_CLASS_DEFAULT)
return i;
- __builtin_unreachable();
+ assert_notreached("should have returned from inside for() loop");
}
struct _cr_select_waiters waiters = {
@@ -185,40 +171,9 @@ static size_t cr_select_v(size_t arg_cnt, struct cr_select_arg arg_vec[]) {
/* cr_select_l(arg1, arg2, arg3, ...) ******************************************/
-#define cr_select_l(...) _cr_selectl(__VA_ARGS__ __VA_OPT__(,) _CR_SELECT_END)
-static inline size_t _cr_select_l(struct cr_select_arg first_arg, ...) {
- va_list list;
-
- /* Pass 1: Count how many args we have. */
- size_t arg_cnt = 0;
- va_start(list, first_arg);
- for (;;) {
- struct cr_select_arg arg;
- if (arg_cnt == 0)
- arg = first_arg;
- else
- arg = va_arg(list, struct cr_select_arg);
- if (arg.op == _CR_SELECT_OP_NONE)
- break;
- arg_cnt++;
- }
- va_end(list);
-
- /* Pass 2: Copy the args to a simple vector on the stack. */
- struct cr_select_arg *arg_vec = alloca(sizeof(struct cr_select_arg) * arg_cnt);
- va_start(list, first_arg);
- for (size_t i = 0; i < arg_cnt; i++) {
- struct cr_select_arg arg;
- if (i == 0)
- arg = first_arg;
- else
- arg = va_arg(list, struct cr_select_arg);
- arg_vec[i] = arg;
- }
- va_end(list);
-
- /* Now pass that to cr_select_v() for the real work. */
- return cr_select_v(arg_cnt, arg_vec);
-}
+#define cr_select_l(...) ({ \
+ struct cr_select_arg _cr_select_args[] = { __VA_ARGS__ }; \
+ cr_select_v(sizeof(_cr_select_args)/sizeof(_cr_select_args[0])); \
+})
#endif /* _LIBCR_IPC_SELECT_H_ */
diff --git a/libdhcp/CMakeLists.txt b/libdhcp/CMakeLists.txt
index a14fd50..2ded1f4 100644
--- a/libdhcp/CMakeLists.txt
+++ b/libdhcp/CMakeLists.txt
@@ -6,7 +6,7 @@
add_library(libdhcp INTERFACE)
target_include_directories(libdhcp SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_sources(libdhcp INTERFACE
- dhcp.c
+ dhcp_client.c
)
target_link_libraries(libdhcp INTERFACE
libmisc
diff --git a/libdhcp/dhcp.c b/libdhcp/dhcp.c
deleted file mode 100644
index 9786018..0000000
--- a/libdhcp/dhcp.c
+++ /dev/null
@@ -1,710 +0,0 @@
-/* libdhcp/dhcp.c - A DHCP client
- *
- * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
- * SPDX-License-Identifier: AGPL-3.0-or-later
- *
- * -----------------------------------------------------------------------------
- * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c
- *
- * Copyright (c) 2013, WIZnet Co., LTD.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * * Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * * Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * * Neither the name of the <ORGANIZATION> nor the names of its
- * contributors may be used to endorse or promote products derived
- * from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
- * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
- * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
- * THE POSSIBILITY OF SUCH DAMAGE.
- *
- * SPDX-License-Identifier: BSD-3-Clause
- *
- * -----------------------------------------------------------------------------
- * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt
- *
- * Copyright (c) 2014 WIZnet Co.,Ltd.
- * Copyright (c) WIZnet ioLibrary Project.
- * All rights reserved.
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in all
- * copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
- * SOFTWARE.
- *
- * SPDX-License-Identifier: MIT
- */
-
-#include <string.h> /* for strlen(), memcpy(), memset() */
-
-#include <libmisc/endian.h>
-#include <libmisc/vcall.h>
-#include <libdhcp/dhcp.h>
-
-/* Config *********************************************************************/
-
-#include "config.h"
-
-#ifndef CONFIG_DHCP_DEBUG
- #define CONFIG_DHCP_DEBUG 1
-#endif
-#ifndef CONFIG_DHCP_HOPS
- #define CONFIG_DHCP_HOPS 0
-#endif
-#ifndef CONFIG_DHCP_SECS
- #define CONFIG_DHCP_SECS 0
-#endif
-#ifndef CONFIG_DHCP_OPT_SIZE
- #define CONFIG_DHCP_OPT_SIZE 312
-#endif
-
-/* RFC 2131 definitions *******************************************************/
-
-/** https://datatracker.ietf.org/doc/html/rfc2131#page-9 */
-struct dhcp_msg {
- uint8_t op; /* DHCP_OP_{x} */
- uint8_t htype; /* DHCP_HTYPE_{x} */
- uint8_t hlen; /* length of ->chaddr (`sizeof(struct net_eth_addr)`) */
- uint8_t hops;
-
- uint32be_t xid; /* transaction ID */
-
- uint16be_t secs;
- uint16be_t flags; /* DHCP_FLAG_{x} */
-
- struct net_ip4_addr ciaddr; /* Request IP to DHCP sever */
- struct net_ip4_addr yiaddr; /* Offered IP from DHCP server */
- struct net_ip4_addr siaddr; /* next-server IP address (not used) */
- struct net_ip4_addr giaddr; /* relay-agent IP address (not used) */
- uint8_t chaddr[16]; /* client hardware (MAC) address */
- uint8_t sname[64]; /* server name (not used) */
- uint8_t file[128]; /* boot file name (not used) */
-
- uint8_t options[CONFIG_DHCP_OPT_SIZE];
-};
-static_assert(offsetof(struct dhcp_msg, options) == 236);
-#define DHCP_MSG_BASE_SIZE offsetof(struct dhcp_msg, options)
-
-/** https://datatracker.ietf.org/doc/html/rfc2131#page-10 */
-#define DHCP_OP_BOOTREQUEST 1
-#define DHCP_OP_BOOTREPLY 2
-
-/** https://datatracker.ietf.org/doc/html/rfc2131#page-11 */
-#define DHCP_FLAG_BROADCAST 0x8000
-
-/** https://datatracker.ietf.org/doc/html/rfc2131#section-3 */
-static const uint8_t dhcp_magic_cookie[] = {99, 130, 83, 99};
-
-/** https://datatracker.ietf.org/doc/html/rfc2131#section-3.3 */
-#define DHCP_INFINITY 0xffffffff
-
-/* IANA assignments ***********************************************************/
-
-/**
- * Port Numbers
- * https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
- */
-#define DHCP_PORT_SERVER ((uint16_t)67)
-#define DHCP_PORT_CLIENT ((uint16_t)68)
-
-/**
- * Hardware Types
- * https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2
- */
-#define DHCP_HTYPE_ETHERNET ((uint8_t) 1)
-
-/**
- * DHCP Options
- * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options
- */
-#define DHCP_OPT_PAD ((uint8_t) 0) /* RFC2132: length: 0; meaning: None */
-#define DHCP_OPT_SUBNET_MASK ((uint8_t) 1) /* RFC2132: length: 4; meaning: Subnet Mask Value */
-#define DHCP_OPT_TIME_OFFSET ((uint8_t) 2) /* RFC2132: length: 4; meaning: Time Offset in Seconds from UTC (note: deprecated by 100 and 101 */
-#define DHCP_OPT_ROUTER ((uint8_t) 3) /* RFC2132: length: N; meaning: N/4 Router addresses */
-#define DHCP_OPT_TIME_SERVER ((uint8_t) 4) /* RFC2132: length: N; meaning: N/4 Timeserver addresses */
-#define DHCP_OPT_NAME_SERVER ((uint8_t) 5) /* RFC2132: length: N; meaning: N/4 IEN-116 Server addresses */
-#define DHCP_OPT_DOMAIN_SERVER ((uint8_t) 6) /* RFC2132: length: N; meaning: N/4 DNS Server addresses */
-#define DHCP_OPT_LOG_SERVER ((uint8_t) 7) /* RFC2132: length: N; meaning: N/4 Logging Server addresses */
-#define DHCP_OPT_QUOTES_SERVER ((uint8_t) 8) /* RFC2132: length: N; meaning: N/4 Quotes Server addresses */
-#define DHCP_OPT_LPR_SERVER ((uint8_t) 9) /* RFC2132: length: N; meaning: N/4 Printer Server addresses */
-#define DHCP_OPT_IMPRESS_SERVER ((uint8_t) 10) /* RFC2132: length: N; meaning: N/4 Impress Server addresses */
-#define DHCP_OPT_RLP_SERVER ((uint8_t) 11) /* RFC2132: length: N; meaning: N/4 RLP Server addresses */
-#define DHCP_OPT_HOSTNAME ((uint8_t) 12) /* RFC2132: length: N; meaning: Hostname string */
-#define DHCP_OPT_BOOT_FILE_SIZE ((uint8_t) 13) /* RFC2132: length: 2; meaning: Size of boot file in 512 byte chunks */
-#define DHCP_OPT_MERIT_DUMP_FILE ((uint8_t) 14) /* RFC2132: length: N; meaning: Client to dump and name the file to dump it to */
-#define DHCP_OPT_DOMAIN_NAME ((uint8_t) 15) /* RFC2132: length: N; meaning: The DNS domain name of the client */
-#define DHCP_OPT_SWAP_SERVER ((uint8_t) 16) /* RFC2132: length: N; meaning: Swap Server address */
-#define DHCP_OPT_ROOT_PATH ((uint8_t) 17) /* RFC2132: length: N; meaning: Path name for root disk */
-#define DHCP_OPT_EXTENSION_FILE ((uint8_t) 18) /* RFC2132: length: N; meaning: Path name for more BOOTP info */
-#define DHCP_OPT_FORWARD_ONOFF ((uint8_t) 19) /* RFC2132: length: 1; meaning: Enable/Disable IP Forwarding */
-#define DHCP_OPT_SRCRTE_ONOFF ((uint8_t) 20) /* RFC2132: length: 1; meaning: Enable/Disable Source Routing */
-#define DHCP_OPT_POLICY_FILTER ((uint8_t) 21) /* RFC2132: length: N; meaning: Routing Policy Filters */
-#define DHCP_OPT_MAX_DG_ASSEMBLY ((uint8_t) 22) /* RFC2132: length: 2; meaning: Max Datagram Reassembly Size */
-#define DHCP_OPT_DEFAULT_IP_TTL ((uint8_t) 23) /* RFC2132: length: 1; meaning: Default IP Time to Live */
-#define DHCP_OPT_MTU_TIMEOUT ((uint8_t) 24) /* RFC2132: length: 4; meaning: Path MTU Aging Timeout */
-#define DHCP_OPT_MTU_PLATEAU ((uint8_t) 25) /* RFC2132: length: N; meaning: Path MTU Plateau Table */
-#define DHCP_OPT_MTU_INTERFACE ((uint8_t) 26) /* RFC2132: length: 2; meaning: Interface MTU Size */
-#define DHCP_OPT_MTU_SUBNET ((uint8_t) 27) /* RFC2132: length: 1; meaning: All Subnets are Local */
-#define DHCP_OPT_BROADCAST_ADDRESS ((uint8_t) 28) /* RFC2132: length: 4; meaning: Broadcast Address */
-#define DHCP_OPT_MASK_DISCOVERY ((uint8_t) 29) /* RFC2132: length: 1; meaning: Perform Mask Discovery */
-#define DHCP_OPT_MASK_SUPPLIER ((uint8_t) 30) /* RFC2132: length: 1; meaning: Provide Mask to Others */
-#define DHCP_OPT_ROUTER_DISCOVERY ((uint8_t) 31) /* RFC2132: length: 1; meaning: Perform Router Discovery */
-#define DHCP_OPT_ROUTER_REQUEST ((uint8_t) 32) /* RFC2132: length: 4; meaning: Router Solicitation Address */
-#define DHCP_OPT_STATIC_ROUTE ((uint8_t) 33) /* RFC2132: length: N; meaning: Static Routing Table */
-#define DHCP_OPT_TRAILERS ((uint8_t) 34) /* RFC2132: length: 1; meaning: Trailer Encapsulation */
-#define DHCP_OPT_ARP_TIMEOUT ((uint8_t) 35) /* RFC2132: length: 4; meaning: ARP Cache Timeout */
-#define DHCP_OPT_ETHERNET ((uint8_t) 36) /* RFC2132: length: 1; meaning: Ethernet Encapsulation */
-#define DHCP_OPT_DEFAULT_TCP_TTL ((uint8_t) 37) /* RFC2132: length: 1; meaning: Default TCP Time to Live */
-#define DHCP_OPT_KEEPALIVE_TIME ((uint8_t) 38) /* RFC2132: length: 4; meaning: TCP Keepalive Interval */
-#define DHCP_OPT_KEEPALIVE_DATA ((uint8_t) 39) /* RFC2132: length: 1; meaning: TCP Keepalive Garbage */
-#define DHCP_OPT_NIS_DOMAIN ((uint8_t) 40) /* RFC2132: length: N; meaning: NIS Domain Name */
-#define DHCP_OPT_NIS_SERVERS ((uint8_t) 41) /* RFC2132: length: N; meaning: NIS Server Addresses */
-#define DHCP_OPT_NTP_SERVERS ((uint8_t) 42) /* RFC2132: length: N; meaning: NTP Server Addresses */
-#define DHCP_OPT_VENDOR_SPECIFIC ((uint8_t) 43) /* RFC2132: length: N; meaning: Vendor Specific Information */
-#define DHCP_OPT_NETBIOS_NAME_SRV ((uint8_t) 44) /* RFC2132: length: N; meaning: NETBIOS Name Servers */
-#define DHCP_OPT_NETBIOS_DIST_SRV ((uint8_t) 45) /* RFC2132: length: N; meaning: NETBIOS Datagram Distribution */
-#define DHCP_OPT_NETBIOS_NODE_TYPE ((uint8_t) 46) /* RFC2132: length: 1; meaning: NETBIOS Node Type */
-#define DHCP_OPT_NETBIOS_SCOPE ((uint8_t) 47) /* RFC2132: length: N; meaning: NETBIOS Scope */
-#define DHCP_OPT_X_WINDOW_FONT ((uint8_t) 48) /* RFC2132: length: N; meaning: X Window Font Server */
-#define DHCP_OPT_X_WINDOW_MANAGER ((uint8_t) 49) /* RFC2132: length: N; meaning: X Window Display Manager */
-#define DHCP_OPT_ADDRESS_REQUEST ((uint8_t) 50) /* RFC2132: length: 4; meaning: Requested IP Address */
-#define DHCP_OPT_ADDRESS_TIME ((uint8_t) 51) /* RFC2132: length: 4; meaning: IP Address Lease Time */
-#define DHCP_OPT_OVERLOAD ((uint8_t) 52) /* RFC2132: length: 1; meaning: Overload "sname" or "file */
-#define DHCP_OPT_DHCP_MSG_TYPE ((uint8_t) 53) /* RFC2132: length: 1; meaning: DHCP Message Type */
-#define DHCP_OPT_DHCP_SERVER_ID ((uint8_t) 54) /* RFC2132: length: 4; meaning: DHCP Server Identification */
-#define DHCP_OPT_PARAMETER_LIST ((uint8_t) 55) /* RFC2132: length: N; meaning: Parameter Request List */
-#define DHCP_OPT_DHCP_MESSAGE ((uint8_t) 56) /* RFC2132: length: N; meaning: DHCP Error Message */
-#define DHCP_OPT_DHCP_MAX_MSG_SIZE ((uint8_t) 57) /* RFC2132: length: 2; meaning: DHCP Maximum Message Size */
-#define DHCP_OPT_RENEWAL_TIME ((uint8_t) 58) /* RFC2132: length: 4; meaning: DHCP Renewal (T1) Time */
-#define DHCP_OPT_REBINDING_TIME ((uint8_t) 59) /* RFC2132: length: 4; meaning: DHCP Rebinding (T2) Time */
-#define DHCP_OPT_CLASS_ID ((uint8_t) 60) /* RFC2132: length: N; meaning: Class Identifier */
-#define DHCP_OPT_CLIENT_ID ((uint8_t) 61) /* RFC2132: length: N; meaning: Client Identifier */
-#define DHCP_OPT_END ((uint8_t)255) /* RFC2132: length: 0; meaning: None */
-
-/**
- * DHCP Message Type 53 Values
- * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53
- */
-#define DHCP_MSGTYP_DISCOVER ((uint8_t) 1) /* RFC2132 */
-#define DHCP_MSGTYP_OFFER ((uint8_t) 2) /* RFC2132 */
-#define DHCP_MSGTYP_REQUEST ((uint8_t) 3) /* RFC2132 */
-#define DHCP_MSGTYP_DECLINE ((uint8_t) 4) /* RFC2132 */
-#define DHCP_MSGTYP_ACK ((uint8_t) 5) /* RFC2132 */
-#define DHCP_MSGTYP_NAK ((uint8_t) 6) /* RFC2132 */
-#define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132 */
-#define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132 */
-
-/* Implementation *************************************************************/
-
-#if CONFIG_DHCP_DEBUG
- #include <stdio.h>
- #define debugf(fmt, ...) printf(fmt "\n" __VA_OPT__(,) __VA_ARGS__)
-#else
- #define debugf(fmt, ...) ((void)0)
-#endif
-
-/* DHCP state machine. */
-#define STATE_DHCP_INIT 0 /* Initialize */
-#define STATE_DHCP_DISCOVER 1 /* send DISCOVER and wait OFFER */
-#define STATE_DHCP_REQUEST 2 /* send REQEUST and wait ACK or NACK */
-#define STATE_DHCP_LEASED 3 /* ReceiveD ACK and IP leased */
-#define STATE_DHCP_REREQUEST 4 /* send REQUEST for maintaining leased IP */
-#define STATE_DHCP_RELEASE 5 /* No use */
-#define STATE_DHCP_STOP 6 /* Stop processing DHCP */
-
-/* Global state. */
-struct net_ip4_addr DHCP_SIP; /* DHCP Server IP address */
-struct net_ip4_addr DHCP_REAL_SIP; /* For extract my DHCP server in a few DHCP server */
-struct net_ip4_addr OLD_allocated_ip = {0}; /* Previous IP address */
-struct dhcp_lease global_lease = {.lifetime = DHCP_INFINITY};
-int8_t dhcp_state = STATE_DHCP_INIT; /* DHCP state */
-int8_t dhcp_retry_count = 0;
-volatile uint32_t dhcp_tick_1s = 0; /* unit 1 second */
-uint32_t dhcp_tick_next = DHCP_WAIT_TIME ;
-char global_hostname[64];
-struct net_eth_addr global_eth_addr; /* DHCP Client MAC address. */
-
-#define mem_encode(dst, obj) ({ \
- typeof(obj) _obj = obj; \
- memcpy(dst, &_obj, sizeof(_obj)); \
- sizeof(_obj); \
-})
-
-static inline size_t str_encode(void *dst, char *str) {
- size_t len = strlen(str);
- memcpy(dst, str, len);
- return len;
-}
-
-/* make the common DHCP message */
-static inline void dhcp_msg_init(struct dhcp_msg *msg, size_t *optlen,
- struct net_eth_addr self_eth_addr,
- uint16_t flags, uint32_t xid) {
- size_t tmp;
-
- assert(msg);
- assert(optlen);
-
- *msg = (struct dhcp_msg){0};
-
- msg->op = DHCP_OP_BOOTREQUEST;
- msg->htype = DHCP_HTYPE_ETHERNET;
- msg->hlen = sizeof(self_eth_addr);
- msg->hops = CONFIG_DHCP_HOPS;
- msg->xid = uint32be_marshal(xid);
- msg->secs = uint16be_marshal(CONFIG_DHCP_SECS);
- msg->flags = uint16be_marshal(flags);
- msg->ciaddr = (struct net_ip4_addr){0};
- msg->yiaddr = (struct net_ip4_addr){0};
- msg->siaddr = (struct net_ip4_addr){0};
- msg->siaddr = (struct net_ip4_addr){0};
- msg->giaddr = (struct net_ip4_addr){0};
-
- tmp = mem_encode(msg->chaddr, self_eth_addr);
- memset(&msg->chaddr[tmp], 0, sizeof(msg->chaddr)-tmp);
-
- memset(msg->sname, 0, sizeof(msg->sname));
- memset(msg->file, 0, sizeof(msg->file));
-
- *optlen = 0;
- msg->options[*optlen++] = dhcp_magic_cookie[0];
- msg->options[*optlen++] = dhcp_magic_cookie[1];
- msg->options[*optlen++] = dhcp_magic_cookie[2];
- msg->options[*optlen++] = dhcp_magic_cookie[3];
-}
-
-static void dhcp_send_DISCOVER(implements_net_packet_conn *sock) {
- struct dhcp_msg msg;
- size_t k;
-
- dhcp_msg_init(&msg, &k, DHCP_FLAG_BROADCAST);
- DHCP_SIP = (struct net_ip4_addr){0};
- DHCP_REAL_SIP = (struct net_ip4_addr){0};
-
- /* Message type. */
- msg->options[k++] = DHCP_OPT_DHCP_MSG_TYPE;
- msg->options[k++] = 1;
- msg->options[k++] = DHCP_MSGTYP_DISCOVER;
-
- /* Our Client ID. */
- msg->options[k++] = DHCP_OPT_CLIENT_ID;
- msg->options[k++] = 1+sizeof(self_eth_addr);
- msg->options[k++] = DHCP_HTYPE_ETHERNET;
- k += mem_encode(&msg->options[k], self_eth_addr);
-
- /* Our requested hostname. */
- msg->options[k++] = DHCP_OPT_HOSTNAME;
- msg->options[k++] = strlen(global_hostname);
- k += str_encode(&msg->options[k], global_hostname);
-
- /* Which parameters we want back. */
- msg->options[k++] = DHCP_OPT_PARAMETER_LIST;
- msg->options[k++] = 6;
- msg->options[k++] = DHCP_OPT_SUBNET_MASK; /* 1 */
- msg->options[k++] = DHCP_OPT_ROUTER; /* 2 */
- msg->options[k++] = DHCP_OPT_DOMAIN_SERVER; /* 3 */
- msg->options[k++] = DHCP_OPT_DOMAIN_NAME; /* 4 */
- msg->options[k++] = DHCP_OPT_RENEWAL_TIME; /* 5 */
- msg->options[k++] = DHCP_OPT_REBINDING_TIME; /* 6 */
-
- /* End. */
- msg->options[k++] = DHCP_OPT_END;
- assert(k <= CONFIG_DHCP_OPT_SIZE);
-
- /* Send. */
- debugf("> Send DHCP_DISCOVER");
- VCALL(sock, sendto, msg, DHCP_MSG_BASE_SIZE + k, net_ip4_addr_broadcast, DHCP_PORT_SERVER);
-}
-
-static void dhcp_send_REQUEST(implements_net_packet_conn *sock) {
- struct net_ip4_addr ip;
- struct dhcp_msg msg;
- size_t k;
-
- dhcp_msg_init(msg, &k,
- (dhcp_state != STATE_DHCP_LEASED && dhcp_state != STATE_DHCP_REREQUEST) ? DHCP_FLAG_BROADCAST : 0);
-
- switch (dhcp_state) {
- case STATE_DHCP_LEASED:
- case STATE_DHCP_REREQUEST:
- msg->ciaddr = global_lease.addr;
- ip = DHCP_SIP;
- break;
- default:
- ip = net_ip4_addr_broadcast;
- }
-
- /* Message type. */
- msg->options[k++] = DHCP_OPT_DHCP_MSG_TYPE;
- msg->options[k++] = 1;
- msg->options[k++] = DHCP_MSGTYP_REQUEST;
-
- /* Our Client ID. */
- msg->options[k++] = DHCP_OPT_CLIENT_ID;
- msg->options[k++] = 1+sizeof(self_eth_addr);
- msg->options[k++] = DHCP_HTYPE_ETHERNET;
- k += mem_encode(&msg->options[k], self_eth_addr);
-
- switch (dhcp_state) {
- case STATE_DHCP_LEASED:
- case STATE_DHCP_REREQUEST:
- /* Our current IP address. */
- msg->options[k++] = DHCP_OPT_ADDRESS_REQUEST;
- msg->options[k++] = sizeof(global_lease.addr);
- k += mem_encode(&msg->options[k], global_lease.addr);
-
- /* The server we got it from. */
- msg->options[k++] = DHCP_OPT_DHCP_SERVER_ID;
- msg->options[k++] = sizeof(DHCP_SIP);
- k += mem_encode(&msg->options[k], DHCP_SIP);
- }
-
- /* Our requested hostname. */
- msg->options[k++] = DHCP_OPT_HOSTNAME;
- msg->options[k++] = strlen(global_hostname);
- k += str_encode(&msg->options[k], global_hostname);
-
- /* Which parameters we want back. */
- msg->options[k++] = DHCP_OPT_PARAMETER_LIST;
- msg->options[k++] = 8;
- msg->options[k++] = DHCP_OPT_SUBNET_MASK; /* 1 */
- msg->options[k++] = DHCP_OPT_ROUTER; /* 2 */
- msg->options[k++] = DHCP_OPT_DOMAIN_SERVER; /* 3 */
- msg->options[k++] = DHCP_OPT_DOMAIN_NAME; /* 4 */
- msg->options[k++] = DHCP_OPT_RENEWAL_TIME; /* 5 */
- msg->options[k++] = DHCP_OPT_REBINDING_TIME; /* 6 */
- msg->options[k++] = DHCP_OPT_ROUTER_DISCOVERY; /* 7 */
- msg->options[k++] = DHCP_OPT_STATIC_ROUTE; /* 8 */
-
- /* End. */
- msg->options[k++] = DHCP_OPT_END;
- assert(k <= CONFIG_DHCP_OPT_SIZE);
-
- debugf("> Send DHCP_REQUEST");
- VCALL(sock, sendto, msg, DHCP_MSG_BASE_SIZE + k, ip, DHCP_PORT_SERVER);
-}
-
-static void dhcp_send_DECLINE(implements_net_packet_conn *sock) {
- size_t k;
-
- dhcp_msg_init(pDHCPMSG, &k, DHCP_FLAG_BROADCAST);
- pDHCPMSG->flags = uint16be_marshal(0);
-
- /* Option Request Param. */
- pDHCPMSG->options[k++] = DHCP_OPT_DHCP_MSG_TYPE;
- pDHCPMSG->options[k++] = 1;
- pDHCPMSG->options[k++] = DHCP_MSGTYP_DECLINE;
-
- /* Our Client ID. */
- pDHCPMSG->options[k++] = DHCP_OPT_CLIENT_ID;
- pDHCPMSG->options[k++] = 1+sizeof(self_eth_addr);
- pDHCPMSG->options[k++] = DHCP_HTYPE_ETHERNET;
- memcpy(&pDHCPMSG->options[k], self_eth_addr.octets, sizeof(self_eth_addr));
- k += sizeof(self_eth_addr);
-
- /* Our current IP address. */
- pDHCPMSG->options[k++] = DHCP_OPT_ADDRESS_REQUEST;
- pDHCPMSG->options[k++] = sizeof(global_lease.addr);
- k += mem_encode(&pDHCPMSG->options[k], global_lease.addr);
-
- /* The server we got it from. */
- pDHCPMSG->options[k++] = DHCP_OPT_DHCP_SERVER_ID;
- pDHCPMSG->options[k++] = sizeof(DHCP_SIP);
- k += mem_encode(&pDHCPMSG->options[k], DHCP_SIP);
-
- /* End. */
- pDHCPMSG->options[k++] = DHCP_OPT_END;
-
- debugf("> Send DHCP_DECLINE");
- assert(k <= CONFIG_DHCP_OPT_SIZE);
- VCALL(sock, sendto, pDHCPMSG, DHCP_MSG_BASE_SIZE + k, net_ip4_addr_broadcast, DHCP_PORT_SERVER);
-}
-
-static int8_t dhcp_msg_parse(implements_net_packet_conn *sock) {
- struct net_ip4_addr srv_addr;
- uint16_t srv_port;
- size_t msg_len;
- msg_len = VCALL(sock, recvfrom, pDHCPMSG, sizeof(*pDHCPMSG), &srv_addr, &srv_port);
- debugf("DHCP message : %d.%d.%d.%d(%d) %d received.",
- srv_addr.octets[0], srv_addr.octets[1], srv_addr.octets[2], srv_addr.octets[3], srv_port, msg_len);
- /* Compare server port. */
- if (srv_port != DHCP_PORT_SERVER) {
- return 0;
- }
- /* Compare our MAC address. */
- if (memcmp(pDHCPMSG->chaddr, self_eth_addr.octets, sizeof(self_eth_addr))) {
- debugf("Not my DHCP Message. This message is ignored.");
- return 0;
- }
- /* Compare server IP address. */
- if (memcmp(DHCP_SIP.octets, ((struct net_ip4_addr){0}).octets, sizeof(struct net_ip4_addr))) {
- if ( memcmp(srv_addr.octets, DHCP_SIP.octets, sizeof(srv_addr)) &&
- memcmp(srv_addr.octets, DHCP_REAL_SIP.octets, sizeof(srv_addr)) ) {
- debugf("Another DHCP sever send a response message. This is ignored.");
- return 0;
- }
- }
-
- uint8_t msg_type = 0;
- for (size_t k = 4, k_max = msg_len - DHCP_MSG_BASE_SIZE, opt_len; k < k_max; k += opt_len) {
- uint8_t opt_typ;
- opt_typ = pDHCPMSG->options[k++];
- switch (opt_typ) {
- case DHCP_OPT_END:
- opt_len = k_max - k;
- break;
- case DHCP_OPT_PAD:
- opt_len = 0;
- break;
- default:
- opt_len = pDHCPMSG->options[k++];
- xhandle(opt_typ, opt_len, &pDHCPMSG->options[k]);
- }
- switch (opt_typ) {
- case DHCP_OPT_DHCP_MSG_TYPE:
- if (opt_len != 1)
- continue;
- msg_type = pDHCPMSG->options[k];
- break;
- case DHCP_OPT_ADDRESS_TIME:
- if (opt_len != 4)
- continue;
- global_lease.lifetime = uint32be_decode(&pDHCPMSG->options[k]);
-#if CONFIG_DHCP_DEBUG
- global_lease.lifetime = 10;
-#endif
- break;
- case DHCP_OPT_DHCP_SERVER_ID:
- if (opt_len != 4)
- continue;
- DHCP_SIP.octets[0] = pDHCPMSG->options[k+0];
- DHCP_SIP.octets[1] = pDHCPMSG->options[k+1];
- DHCP_SIP.octets[2] = pDHCPMSG->options[k+2];
- DHCP_SIP.octets[3] = pDHCPMSG->options[k+3];
- DHCP_REAL_SIP = srv_addr;
- break;
- }
- }
- return msg_type;
-}
-
-static void dhcp_reset_timeout(void) {
- dhcp_tick_1s = 0;
- dhcp_tick_next = DHCP_WAIT_TIME;
- dhcp_retry_count = 0;
-}
-
-static uint8_t dhcp_check_timeout(implements_net_packet_conn *sock) {
- uint8_t ret = DHCP_RET_RUNNING;
-
- if (dhcp_retry_count < MAX_DHCP_RETRY) {
- if (dhcp_tick_next < dhcp_tick_1s) {
-
- switch ( dhcp_state ) {
- case STATE_DHCP_DISCOVER :
- /*debugf("<<timeout>> state : STATE_DHCP_DISCOVER");*/
- dhcp_send_DISCOVER(sock);
- break;
-
- case STATE_DHCP_REQUEST :
- /*debugf("<<timeout>> state : STATE_DHCP_REQUEST");*/
- dhcp_send_REQUEST(sock);
- break;
-
- case STATE_DHCP_REREQUEST :
- /*debugf("<<timeout>> state : STATE_DHCP_REREQUEST");*/
- dhcp_send_REQUEST(sock);
- break;
-
- default :
- break;
- }
-
- dhcp_tick_1s = 0;
- dhcp_tick_next = dhcp_tick_1s + DHCP_WAIT_TIME;
- dhcp_retry_count++;
- }
- } else { /* timeout occurred */
-
- switch(dhcp_state) {
- case STATE_DHCP_DISCOVER:
- dhcp_state = STATE_DHCP_INIT;
- ret = DHCP_RET_FAILED;
- break;
- case STATE_DHCP_REQUEST:
- case STATE_DHCP_REREQUEST:
- dhcp_send_DISCOVER(sock);
- dhcp_state = STATE_DHCP_DISCOVER;
- break;
- default :
- break;
- }
- dhcp_reset_timeout();
- }
- return ret;
-}
-
-static int8_t dhcp_check_leasedIP(implements_net_packet_conn *sock) {
- int32_t ret;
-
- /* IP conflict detection : ARP request - ARP reply */
- /* Broadcasting ARP Request for check the IP conflict using UDP sendto() function */
- ret = VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, global_lease.addr, 5000);
-
- if (ret == NET_ETIMEDOUT) {
- /* UDP send Timeout occurred : allocated IP address is unique, DHCP Success */
- debugf("\r\n> Check leased IP - OK");
- return 1;
- }
-
- /* Received ARP reply or etc : IP address conflict occur, DHCP Failed */
- dhcp_send_DECLINE(sock);
-
- ret = dhcp_tick_1s;
- while ((dhcp_tick_1s - ret) < 2) {} /* wait for 1s over; wait to complete to send DECLINE message; */
-
- return 0;
-}
-
-COROUTINE DHCP_cr(struct w5500 *chip, uint8_t socknum,
- struct net_eth_addr self_eth_addr,
- char *self_hostname,
- dhcp_callback_t cb) {
- uint32_t xid;
- implements_net_packet_conn *sock;
-
- xid = 0x12345678;
- xid += self_eth_addr.octets[3];
- xid += self_eth_addr.octets[4];
- xid += self_eth_addr.octets[5];
- xid += (self_eth_addr.octets[3] ^
- self_eth_addr.octets[4] ^
- self_eth_addr.octets[5]);
-
- dhcp_tick_1s = 0;
- dhcp_tick_next = DHCP_WAIT_TIME;
- dhcp_retry_count = 0;
- dhcp_state = STATE_DHCP_INIT;
-
- sock = w5500_udp_conn(chip, socknum, DHCP_PORT_CLIENT);
-
- ret = DHCP_RET_RUNNING;
- msg_type = dhcp_msg_parse(sock);
-
- for (;;) {
- /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */
- switch (state) {
- case STATE_DHCP_INIT:
- dhcp_send_DISCOVER(sock);
- dhcp_state = STATE_DHCP_DISCOVER;
- break;
- case STATE_DHCP_DISCOVER :
- if (msg_type == DHCP_MSGTYP_OFFER){
- debugf("> Receive DHCP_OFFER");
- global_lease.addr = pDHCPMSG->yiaddr;
-
- dhcp_send_REQUEST(sock);
- dhcp_state = STATE_DHCP_REQUEST;
- } else
- ret = dhcp_check_timeout(sock);
- break;
-
- case STATE_DHCP_REQUEST :
- if (msg_type == DHCP_MSGTYP_ACK) {
- debugf("> Receive DHCP_ACK");
- if (dhcp_check_leasedIP(sock)) {
- /* Network info assignment from DHCP */
- cb(DHCP_ASSIGN, global_lease);
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_LEASED;
- } else {
- /* IP address conflict occurred */
- dhcp_reset_timeout();
- cb(DHCP_CONFLICT, global_lease);
- dhcp_state = STATE_DHCP_INIT;
- }
- } else if (msg_type == DHCP_MSGTYP_NAK) {
- debugf("> Receive DHCP_NACK");
-
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_DISCOVER;
- } else
- ret = dhcp_check_timeout(sock);
- break;
-
- case STATE_DHCP_LEASED :
- ret = DHCP_RET_IP_LEASED;
- if ((global_lease.lifetime != DHCP_INFINITY) && ((global_lease.lifetime/2) < dhcp_tick_1s)) {
- debugf("> Maintains the IP address");
-
- msg_type = 0;
- OLD_allocated_ip = global_lease.addr;
-
- xid++;
-
- dhcp_send_REQUEST(sock);
-
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_REREQUEST;
- }
- break;
-
- case STATE_DHCP_REREQUEST :
- ret = DHCP_RET_IP_LEASED;
- if (msg_type == DHCP_MSGTYP_ACK) {
- dhcp_retry_count = 0;
- if (memcmp(OLD_allocated_ip.octets, global_lease.addr.octets, sizeof(global_lease.addr))) {
- ret = DHCP_RET_IP_CHANGED;
- cb(DHCP_UPDATE, global_lease);
- debugf(">IP changed.");
- } else {
- debugf(">IP is continued.");
- }
- dhcp_reset_timeout();
- dhcp_state = STATE_DHCP_LEASED;
- } else if (msg_type == DHCP_MSGTYP_NAK) {
- debugf("> Receive DHCP_NACK, Failed to maintain ip");
- dhcp_reset_timeout();
-
- dhcp_state = STATE_DHCP_DISCOVER;
- } else
- ret = dhcp_check_timeout(sock);
- break;
- default :
- break;
- }
-
- return ret;
-}
diff --git a/libdhcp/dhcp_client.c b/libdhcp/dhcp_client.c
new file mode 100644
index 0000000..94806a6
--- /dev/null
+++ b/libdhcp/dhcp_client.c
@@ -0,0 +1,969 @@
+/* libdhcp/dhcp_client.c - A DHCP client
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * -----------------------------------------------------------------------------
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c
+ *
+ * Copyright (c) 2013, WIZnet Co., LTD.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <ORGANIZATION> nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * -----------------------------------------------------------------------------
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt
+ *
+ * Copyright (c) 2014 WIZnet Co.,Ltd.
+ * Copyright (c) WIZnet ioLibrary Project.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+/* Implemented:
+ * - DHCPv4: https://datatracker.ietf.org/doc/html/rfc2131
+ * - DHCPv4 long options: https://datatracker.ietf.org/doc/html/rfc3396
+ * - DHCPv4 client identifiers in replies: https://datatracker.ietf.org/doc/html/rfc6842
+ *
+ * TODO:
+ * - AutoIPv4: https://datatracker.ietf.org/doc/html/rfc3927
+ * - mDNS responder: https://datatracker.ietf.org/doc/html/rfc6762
+ *
+ * Not implemented:
+ * - DHCPv4 node-specific client identifiers: https://datatracker.ietf.org/doc/html/rfc4361
+ * - DNAv4: https://datatracker.ietf.org/doc/html/rfc4436
+ * - LLMNR responder: https://datatracker.ietf.org/doc/html/rfc4795
+ *
+ * Not implemented, because the W5500 doesn't support IPv6:
+ * - SLAAC: https://datatracker.ietf.org/doc/html/rfc2462
+ * - DNAv6: https://datatracker.ietf.org/doc/html/rfc6059
+ * - DHCPv6: https://datatracker.ietf.org/doc/html/rfc8415
+ */
+
+#include <string.h> /* for strlen(), memcpy(), memset() */
+
+#include <libmisc/rand.h>
+#include <libmisc/vcall.h>
+#include <libhw/generic/alarmclock.h>
+
+#include <libdhcp/client.h>
+
+#include "dhcp_common.h"
+
+/* Config *********************************************************************/
+
+#include "config.h"
+
+#ifndef CONFIG_DHCP_DEBUG
+ #define CONFIG_DHCP_DEBUG 1
+#endif
+#ifndef CONFIG_DHCP_SELECTING_NS
+ #define CONFIG_DHCP_SELECTING_NS (5*NS_PER_S)
+#endif
+#ifndef CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP
+ #define CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP false
+#endif
+
+/* Implementation *************************************************************/
+
+#if CONFIG_DHCP_DEBUG
+ #include <stdio.h>
+ #define debugf(fmt, ...) printf(fmt "\n" __VA_OPT__(,) __VA_ARGS__)
+#else
+ #define debugf(fmt, ...) ((void)0)
+#endif
+
+
+enum requirement {
+ MUST,
+ MUST_NOT,
+ SHOULD,
+ SHOULD_NOT,
+ MAY,
+
+ _SHOULD_NOT_HAPPEN,
+};
+
+struct dhcp_client {
+ /* Static. */
+ implements_net_iface *iface;
+ implements_net_packet_conn *sock;
+ struct net_eth_addr self_eth_addr;
+ char self_hostname[63];
+ size_t self_id_len;
+ void *self_id_dat;
+
+ /* Mutable. */
+ enum {
+ STATE_INIT,
+ STATE_SELECTING,
+ STATE_REQUESTING,
+ STATE_BOUND,
+ STATE_RENEWING,
+ STATE_REBINDING,
+
+ STATE_INIT_REBOOT,
+ STATE_REBOOTING,
+ } state;
+ uint8_t last_sent_msgtyp;
+ uint16_t last_discover_secs;
+ /* Lifetime of xid:
+ * 1. initial : [INIT]->DHCPDISCOVER->DHCPOFFER->DHCPREQUEST-+->DHCPNAK-------------->[INIT]
+ * |->DHCPACK->DHCPDECLINE->[INIT]
+ * `->DHCPACK-------------->[BOUND]
+ * 2. renew/rebind : [RENEWING/REBINDING]->DHCPREQUEST-+->DHCPNAK->[INIT]
+ * `->DHCPACK->[BOUND]
+ */
+ uint32_t xid;
+ uint64_t time_ns_init;
+ uint64_t transmit_time_ns;
+ uint64_t transmit_delay_ns;
+ uint64_t transmit_deadline_ns;
+ struct net_ip4_addr lease_client_addr; /* .yiaddr */
+ struct net_ip4_addr lease_server_id; /* .options[DHCP_OPT_DHCP_SERVER_ID] */
+ uint64_t lease_time_ns_t1; /* .options[DHCP_OPT_RENEWAL_TIME] + time_ns_init */
+ uint64_t lease_time_ns_t2; /* .options[DHCP_OPT_REBINDING_TIME] + time_ns_init */
+ uint64_t lease_time_ns_end; /* .options[DHCP_OPT_ADDRESS_TIME] + time_ns_init */
+ struct {
+ struct net_ip4_addr subnet_mask; /* .options[DHCP_OPT_SUBNET_MASK] */
+ struct net_ip4_addr gateway_addr; /* .options[DHCP_OPT_ROUTER] */
+ } lease_auxdata;
+
+};
+
+/**
+ * @param client->state
+ * @param client->self_eth_addr
+ * @param client->xid
+ * @param client->time_ns_init
+ * @param client->lease_client_addr (sometimes)
+ * @param client->lease_server_id (sometimes)
+ * @param client->sock
+ *
+ * @return client->last_sent_msgtyp
+ * @return whether there was an error
+ */
+static bool dhcp_client_send(struct dhcp_client *client, uint8_t msgtyp, const char *errstr) {
+ /**********************************************************************\
+ * Preconditions *
+ \**********************************************************************/
+
+ assert(client);
+ assert(msgtyp == DHCP_MSGTYP_DISCOVER ||
+ msgtyp == DHCP_MSGTYP_INFORM ||
+ msgtyp == DHCP_MSGTYP_REQUEST ||
+ msgtyp == DHCP_MSGTYP_DECLINE ||
+ msgtyp == DHCP_MSGTYP_RELEASE );
+ if (msgtyp == DHCP_MSGTYP_REQUEST)
+ assert(client->state == STATE_SELECTING || /* initial selection */
+ client->state == STATE_INIT_REBOOT || /* variant initial selection */
+ client->state == STATE_BOUND || /* T1 expired, start renew */
+ client->state == STATE_RENEWING ); /* T2 expired, start rebind */
+
+ /* For convenience in switch blocks, a list of the states that
+ * are not in the above assert(). */
+#define IMPOSSIBLE_REQUEST_STATES \
+ STATE_INIT: \
+ case STATE_REQUESTING: \
+ case STATE_REBINDING: \
+ case STATE_REBOOTING
+
+ /**********************************************************************\
+ * Setup *
+ \**********************************************************************/
+
+ bool server_broadcasts, client_broadcasts;
+
+ server_broadcasts = !CONFIG_DHCP_CAN_RECV_UNICAST_IP_WITHOUT_IP;
+ if (msgtyp == DHCP_MSGTYP_REQUEST &&
+ (client->state == STATE_BOUND || client->state == STATE_RENEWING))
+ server_broadcasts = false;
+
+ /* https://datatracker.ietf.org/doc/html/rfc2131#section-4.4.4 */
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: client_broadcasts = true; break;
+ case DHCP_MSGTYP_INFORM: client_broadcasts = true; break; /* may unicast if it knows the server */
+ case DHCP_MSGTYP_REQUEST: client_broadcasts = client->state != STATE_BOUND; break;
+ case DHCP_MSGTYP_DECLINE: client_broadcasts = true; break;
+ case DHCP_MSGTYP_RELEASE: client_broadcasts = false; break;
+ default: assert_notreached("invalid message type for client to send");
+ }
+
+ /**********************************************************************\
+ * Build the message *
+ \**********************************************************************/
+
+ struct dhcp_msg msg = {0};
+ size_t optlen = 0;
+
+ /* Base structure.
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-37 */
+ msg.op = DHCP_OP_BOOTREQUEST;
+ msg.htype = DHCP_HTYPE_ETHERNET;
+ msg.hlen = sizeof(client->self_eth_addr);
+ msg.hops = 0; /* relays increment this when they forward it along */
+ msg.xid = uint32be_marshal(client->xid);
+ msg.secs = uint16be_marshal( ({
+ uint16_t secs;
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: case DHCP_MSGTYP_INFORM:
+ case DHCP_MSGTYP_REQUEST:
+ secs = (VCALL(bootclock, get_time_ns) - client->time_ns_init)/NS_PER_S;
+ if (!secs)
+ /* systemd's sd-dhcp-client.c asserts that some
+ * servers are broken and require .secs to be
+ * non-zero, even though RFC 2131 explicitly
+ * says that zero is valid. */
+ secs = 1;
+ if (msgtyp == DHCP_MSGTYP_REQUEST &&
+ client->state == STATE_SELECTING)
+ /* "The DHCPREQUEST message MUST use the same
+ * value in the DHCP message header's 'secs'
+ * field ... as the original DHCPDISCOVER
+ * message" -- RFC 2131
+ */
+ secs = client->last_discover_secs;
+ if (msgtyp == DHCP_MSGTYP_DISCOVER)
+ /* Record the value to make the above
+ * possible. */
+ client->last_discover_secs = secs;
+ break;
+ case DHCP_MSGTYP_DECLINE: case DHCP_MSGTYP_RELEASE:
+ secs = 0;
+ break;
+ default:
+ assert_notreached("invalid message type for client to send");
+ }
+ secs;
+ }) );
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: case DHCP_MSGTYP_INFORM: case DHCP_MSGTYP_REQUEST:
+ msg.flags = uint16be_marshal(server_broadcasts ? DHCP_FLAG_BROADCAST : 0);
+ break;
+ case DHCP_MSGTYP_DECLINE: case DHCP_MSGTYP_RELEASE:
+ msg.flags = uint16be_marshal(0);
+ break;
+ }
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: msg.ciaddr = net_ip4_addr_zero; break;
+ case DHCP_MSGTYP_INFORM: msg.ciaddr = client->lease_client_addr; break;
+ case DHCP_MSGTYP_REQUEST: switch (client->state) {
+ case STATE_SELECTING: case STATE_INIT_REBOOT:
+ msg.ciaddr = net_ip4_addr_zero; break;
+ case STATE_BOUND: case STATE_RENEWING: /* case STATE_REBINDING: */
+ /* RFC 2131 includes "REBINDING" here, but a
+ * DHCPREQUEST is never sent in the REBINDING
+ * state. */
+ msg.ciaddr = client->lease_client_addr; break;
+ case IMPOSSIBLE_REQUEST_STATES:
+ assert_notreached("invalid client state for sending DHCPREQUEST");
+ } break;
+ case DHCP_MSGTYP_DECLINE: msg.ciaddr = net_ip4_addr_zero; break;
+ case DHCP_MSGTYP_RELEASE: msg.ciaddr = client->lease_client_addr; break;
+ }
+ msg.yiaddr = net_ip4_addr_zero; /* only set by servers */
+ msg.siaddr = net_ip4_addr_zero; /* only set by servers */
+ msg.giaddr = net_ip4_addr_zero; /* only set by relays */
+ memcpy(msg.chaddr, client->self_eth_addr.octets, sizeof(client->self_eth_addr));
+ /* msg.sname = "options, if indicated in 'sname/file' option'; otherwise unused"; */
+ /* msg.file = "options, if indicated in 'sname/file' option'; otherwise unused"; */
+
+ /* Magic cookie.
+ * https://datatracker.ietf.org/doc/html/rfc2131#section-4.1 */
+ msg.options[optlen++] = dhcp_magic_cookie[0];
+ msg.options[optlen++] = dhcp_magic_cookie[1];
+ msg.options[optlen++] = dhcp_magic_cookie[2];
+ msg.options[optlen++] = dhcp_magic_cookie[3];
+
+ /* Options.
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-38 */
+ static uint8_t parameter_request_list[] = {
+ DHCP_OPT_SUBNET_MASK,
+ DHCP_OPT_ROUTER,
+ DHCP_OPT_RENEWAL_TIME,
+ DHCP_OPT_REBINDING_TIME,
+ };
+ const struct {
+ bool have_cols;
+ enum requirement cols[5];
+ size_t val_len;
+ const void *val_ptr;
+ } options[0x100] = {
+#define V(x) sizeof(x), &(x)
+#define NONE 0, NULL
+ /* Encode the table from
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-38 */
+#define INC_ADDR ({ \
+ enum requirement req; \
+ switch (client->state) { \
+ case STATE_SELECTING: case STATE_INIT_REBOOT: req = MUST; break; \
+ case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \
+ case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \
+ } \
+ req; \
+ })
+#define INC_SERVER ({ \
+ enum requirement req; \
+ switch (client->state) { \
+ case STATE_SELECTING: req = MUST; break; \
+ case STATE_INIT_REBOOT: case STATE_BOUND: case STATE_RENEWING: req = MUST_NOT; break; \
+ case IMPOSSIBLE_REQUEST_STATES: default: req = _SHOULD_NOT_HAPPEN; \
+ } \
+ req; \
+ })
+ /* Option DISCOVER INFORM REQUEST DECLINE RELEASE */
+ /* ------ ---------- ---------- ---------- -------- -------- */
+ [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MAY, MUST_NOT, INC_ADDR, MUST, MUST_NOT }, V(client->lease_client_addr) },
+ [DHCP_OPT_ADDRESS_TIME] = { 1, { MAY, MUST_NOT, MAY, MUST_NOT, MUST_NOT }, NONE },
+ [DHCP_OPT_OVERLOAD] = { 1, { MAY, MAY, MAY, MAY, MAY }, NONE },
+ [DHCP_OPT_DHCP_MSG_TYPE] = { 1, { MUST, MUST, MUST, MUST, MUST }, V(msgtyp) },
+ [DHCP_OPT_CLIENT_ID] = { 1, { MAY, MAY, MAY, MAY, MAY }, client->self_id_len, client->self_id_dat },
+ [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE },
+ [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST_NOT, MUST_NOT, INC_SERVER, MUST, MUST }, V(client->lease_server_id) },
+ [DHCP_OPT_PARAMETER_LIST] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, V(parameter_request_list) },
+ [DHCP_OPT_DHCP_MAX_MSG_SIZE] = { 1, { MAY, MAY, MAY, MUST_NOT, MUST_NOT },
+ CONFIG_DHCP_OPT_SIZE > DHCP_MSG_MIN_MAX_OPT_SIZE ? 2 : 0,
+ CONFIG_DHCP_OPT_SIZE > DHCP_MSG_MIN_MAX_OPT_SIZE ? &(struct{uint16be_t;}){uint16be_marshal(/*IP*/20+/*UDP*/8+sizeof(msg))} : NULL },
+ [DHCP_OPT_DHCP_MESSAGE] = { 1, { SHOULD_NOT, SHOULD_NOT, SHOULD_NOT, SHOULD, SHOULD }, errstr ? strlen(errstr) : 0, errstr },
+ [0]/* All others */ = { 0, { MAY, MAY, MAY, MUST_NOT, MUST_NOT }, NONE },
+
+ [DHCP_OPT_HOSTNAME] = { 0, {0},
+ strnlen(client->self_hostname, sizeof(client->self_hostname)),
+ client->self_hostname },
+ };
+ int col;
+ switch (msgtyp) {
+ case DHCP_MSGTYP_DISCOVER: col = 0; break;
+ case DHCP_MSGTYP_INFORM: col = 1; break;
+ case DHCP_MSGTYP_REQUEST: col = 2; break;
+ case DHCP_MSGTYP_DECLINE: col = 3; break;
+ case DHCP_MSGTYP_RELEASE: col = 4; break;
+ default: assert_notreached("invalid message type for client to send");
+ }
+ for (uint8_t opt = 1; opt < 255; opt++) {
+ enum requirement req = options[opt].have_cols
+ ? options[opt].cols[col]
+ : options[0].cols[col];
+ switch (req) {
+ case MUST_NOT:
+ /* Do nothing. */
+ break;
+ case MUST:
+ assert(options[opt].val_len);
+ /* fall-through */
+ case SHOULD:
+ case SHOULD_NOT:
+ case MAY:
+ if (options[opt].val_len) {
+ assert(options[opt].val_len <= UINT16_MAX);
+ for (size_t done = 0; done < options[opt].val_len;) {
+ uint8_t len = options[opt].val_len - done > UINT8_MAX
+ ? UINT8_MAX
+ : options[opt].val_len - done;
+ msg.options[optlen++] = opt;
+ msg.options[optlen++] = len;
+ memcpy(&msg.options[optlen], &((char*)options[opt].val_ptr)[done], len);
+ optlen += len;
+ done += len;
+ }
+ }
+ break;
+ case _SHOULD_NOT_HAPPEN:
+ assert_notreached("bad table");
+ }
+ }
+ msg.options[optlen++] = DHCP_OPT_END;
+ assert(optlen <= CONFIG_DHCP_OPT_SIZE);
+
+ /**********************************************************************\
+ * Send *
+ \**********************************************************************/
+ ssize_t r = VCALL(client->sock, sendto, &msg, DHCP_MSG_BASE_SIZE + optlen,
+ client_broadcasts ? net_ip4_addr_broadcast : client->lease_server_id, DHCP_PORT_SERVER);
+ if (r < 0) {
+ debugf("error: sendto: %zd", r);
+ return true;
+ }
+ client->last_sent_msgtyp = msgtyp;
+ return false;
+}
+
+struct dhcp_recv_msg {
+ struct dhcp_msg raw;
+ struct {
+ uint16_t len;
+ uint8_t *dat;
+ } options[0x100];
+ uint8_t _option_data[sizeof((struct dhcp_msg){}.options)+
+ sizeof((struct dhcp_msg){}.file)+
+ sizeof((struct dhcp_msg){}.sname)];
+};
+
+/** @return whether there is an error */
+static inline bool _dhcp_client_recv_measure_opts(struct dhcp_recv_msg *ret, uint8_t *optoverload, uint8_t *dat, size_t len, bool require_pad) {
+ for (size_t pos = 0, opt_len; pos < len; pos += opt_len) {
+ uint8_t opt_typ = dat[pos++];
+ switch (opt_typ) {
+ case DHCP_OPT_END:
+ if (require_pad)
+ while (pos < len)
+ if (dat[pos++] != DHCP_OPT_PAD)
+ return true;
+ return false;
+ case DHCP_OPT_PAD:
+ opt_len = 0;
+ break;
+ default:
+ if (pos == len)
+ return true;
+ opt_len = dat[pos++];
+ if (pos+opt_len > len)
+ return true;
+ ret->options[opt_typ].len += opt_len;
+ if (opt_typ == DHCP_OPT_OVERLOAD && opt_len == 1)
+ *optoverload = *optoverload | dat[pos];
+ }
+ }
+ return true;
+}
+
+static inline void _dhcp_client_recv_consolidate_opts(struct dhcp_recv_msg *ret, uint8_t *dat, size_t len) {
+ for (size_t pos = 0, opt_len; pos < len; pos += opt_len) {
+ uint8_t opt_typ = dat[pos++];
+ switch (opt_typ) {
+ case DHCP_OPT_END:
+ return;
+ case DHCP_OPT_PAD:
+ opt_len = 0;
+ break;
+ default:
+ opt_len = dat[pos++];
+ memcpy(&ret->options[opt_typ].dat[ret->options[opt_typ].len], &dat[pos], opt_len);
+ ret->options[opt_typ].len += opt_len;
+ }
+ }
+}
+
+/**
+ * @param client->sock
+ * @param client->self_eth_addr
+ * @param client->xid
+ * @param client->lease_server_id
+ *
+ * @return
+ * - <0: -errno
+ * - 0: success
+ * - >0: should not happen
+ */
+static ssize_t dhcp_client_recv(struct dhcp_client *client, struct dhcp_recv_msg *ret) {
+ struct net_ip4_addr srv_addr;
+ uint16_t srv_port;
+ ssize_t msg_len;
+
+ assert(client);
+
+ ignore:
+ msg_len = VCALL(client->sock, recvfrom, &ret->raw, sizeof(ret->raw), &srv_addr, &srv_port);
+ if (msg_len < 0)
+ /* msg_len is -errno */
+ return msg_len;
+
+ /* Validate L3: IP */
+ /* Don't validate that srv_addr matches client->server_id
+ * because there may be a relay between us and the server, and
+ * don't bother to track the relay IP either, because it may
+ * change. */
+
+ /* Validate L4: UDP. */
+ if (srv_port != DHCP_PORT_SERVER)
+ goto ignore;
+
+ /* Validate L5: DHCP. */
+ if ((size_t)msg_len < DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie))
+ /* ignore impossibly short message */
+ goto ignore;
+ if ((size_t)msg_len > sizeof(ret->raw))
+ /* ignore message that is larger than the specified
+ * DHCP_OPT_DHCP_MAX_MSG_SIZE */
+ goto ignore;
+ if (ret->raw.op != DHCP_OP_BOOTREPLY)
+ /* ignore non-replies */
+ goto ignore;
+ if (memcmp(ret->raw.chaddr, client->self_eth_addr.octets, sizeof(client->self_eth_addr)))
+ /* ignore messages that aren't to us */
+ goto ignore;
+ if (uint32be_unmarshal(ret->raw.xid) != client->xid)
+ /* ignore messages that aren't in response to what we've said */
+ goto ignore;
+ if (memcmp(client->lease_server_id.octets, net_ip4_addr_zero.octets, 4))
+ if (memcmp(srv_addr.octets, client->lease_server_id.octets, 4))
+ /* ignore messages from the wrong server */
+ goto ignore;
+ if (memcmp(ret->raw.options, dhcp_magic_cookie, sizeof(dhcp_magic_cookie)))
+ /* ignore messages without options */
+ goto ignore;
+
+ /* Consolidate split options into contiguous buffers.
+ *
+ * RFC 2131 and RFC 2132 specify this behavior, but are a bit
+ * ambiguous about it; RFC 3396 provides clarification.
+ *
+ * "The aggregate option buffer is made up of the optional
+ * parameters field, the file field, and the sname field, in
+ * that order." -- RFC 3396
+ */
+ uint8_t optoverload = 0;
+ memset(&ret->options, 0, sizeof(ret->options));
+ /* Size the buffers. */
+ if (_dhcp_client_recv_measure_opts(ret, &optoverload,
+ &ret->raw.options[sizeof(dhcp_magic_cookie)],
+ msg_len - (DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie)),
+ false))
+ goto ignore;
+ if (optoverload & 1u)
+ if (_dhcp_client_recv_measure_opts(ret, &optoverload,
+ ret->raw.file, sizeof(ret->raw.file),
+ true))
+ goto ignore;
+ if (optoverload & 2u)
+ if (_dhcp_client_recv_measure_opts(ret, &optoverload,
+ ret->raw.sname, sizeof(ret->raw.sname),
+ true))
+ goto ignore;
+ /* Validate sizes, allocate buffers. */
+ for (uint8_t opt = 1, allocated = 0; opt < 255; opt++) {
+ if (!ret->options[opt].len)
+ continue;
+ if (!dhcp_opt_length_is_valid(opt, ret->options[opt].len))
+ goto ignore;
+ ret->options[opt].dat = &ret->_option_data[allocated];
+ allocated += ret->options[opt].len;
+ ret->options[opt].len = 0;
+ }
+ /* Fill the buffers. */
+ _dhcp_client_recv_consolidate_opts(ret,
+ &ret->raw.options[sizeof(dhcp_magic_cookie)],
+ msg_len - (DHCP_MSG_BASE_SIZE + sizeof(dhcp_magic_cookie)));
+ if (optoverload & 1u)
+ _dhcp_client_recv_consolidate_opts(ret, ret->raw.file, sizeof(ret->raw.file));
+ if (optoverload & 2u)
+ _dhcp_client_recv_consolidate_opts(ret, ret->raw.sname, sizeof(ret->raw.sname));
+ /* Validate presence of options. */
+ const struct {
+ bool have_cols;
+ enum requirement cols[3];
+ } option_req[0x100] = {
+ /* Enocde the table from
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-29, with modifications from
+ * https://datatracker.ietf.org/doc/html/rfc6842#page-4 */
+#define REQ_TIME client->last_sent_msgtyp == DHCP_MSGTYP_REQUEST ? MUST : MUST_NOT
+ /* Option DHCPOFFER DHCPACK DHCPNAK */
+ /* ------ -------- ---------- -------- */
+ [DHCP_OPT_ADDRESS_REQUEST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
+ [DHCP_OPT_ADDRESS_TIME] = { 1, { MUST, REQ_TIME, MUST_NOT } },
+ [DHCP_OPT_OVERLOAD] = { 1, { MAY, MAY, MUST_NOT } },
+ [DHCP_OPT_DHCP_MSG_TYPE] = { 1, { MUST, MUST, MUST } },
+ [DHCP_OPT_PARAMETER_LIST] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
+ [DHCP_OPT_DHCP_MESSAGE] = { 1, { SHOULD, SHOULD, SHOULD } },
+ [DHCP_OPT_CLIENT_ID] = { 1, { MAY, MAY, MAY } }, /* MUST_NOTs changed to MAY, per RFC 6842 */
+ [DHCP_OPT_CLASS_ID] = { 1, { MAY, MAY, MAY } },
+ [DHCP_OPT_DHCP_SERVER_ID] = { 1, { MUST, MUST, MUST } },
+ [DHCP_OPT_DHCP_MAX_MSG_SIZE] = { 1, { MUST_NOT, MUST_NOT, MUST_NOT } },
+ [0]/* All others */ = { 0, { MAY, MAY, MUST_NOT } },
+ };
+ if (!ret->options[DHCP_OPT_DHCP_MSG_TYPE].len)
+ goto ignore;
+ int col;
+ switch (ret->options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_OFFER: col = 0; break;
+ case DHCP_MSGTYP_ACK: col = 1; break;
+ case DHCP_MSGTYP_NAK: col = 2; break;
+ default: goto ignore;
+ }
+ for (uint8_t opt = 1; opt < 255; opt++) {
+ enum requirement req = option_req[opt].have_cols
+ ? option_req[opt].cols[col]
+ : option_req[0].cols[col];
+ switch (req) {
+ case MUST:
+ if (!ret->options[opt].len)
+ goto ignore;
+ break;
+ case MUST_NOT:
+ if (ret->options[opt].len)
+ goto ignore;
+ break;
+ case SHOULD:
+ case SHOULD_NOT:
+ case MAY:
+ /* Do nothing. */
+ break;
+ case _SHOULD_NOT_HAPPEN:
+ assert_notreached("bad table");
+ }
+ }
+ /* Validate values of options. */
+ if (ret->options[DHCP_OPT_CLASS_ID].len) {
+ /* https://datatracker.ietf.org/doc/html/rfc6842#page-4 */
+ if (ret->options[DHCP_OPT_CLASS_ID].len != client->self_id_len ||
+ memcmp(ret->options[DHCP_OPT_CLASS_ID].dat, client->self_id_dat, client->self_id_len))
+ /* ignore messages that aren't to us */
+ goto ignore;
+ }
+
+ return 0;
+}
+
+/** @return true if there's a conflict, false if the addr appears to be unused */
+static bool dhcp_check_conflict(implements_net_packet_conn *sock, struct net_ip4_addr addr) {
+ assert(sock);
+ return VCALL(sock, sendto, "CHECK_IP_CONFLICT", 17, addr, 5000) != NET_ETIMEDOUT;
+}
+
+static void dhcp_client_take_lease(struct dhcp_client *client, struct dhcp_recv_msg *msg, bool ifup) {
+ assert(client);
+ assert(msg);
+
+ client->lease_client_addr = msg->raw.yiaddr;
+ client->lease_auxdata.subnet_mask = msg->options[DHCP_OPT_SUBNET_MASK].len
+ ? *((struct net_ip4_addr *)msg->options[DHCP_OPT_SUBNET_MASK].dat)
+ : net_ip4_addr_zero;
+ client->lease_auxdata.gateway_addr = msg->options[DHCP_OPT_ROUTER].len
+ ? *((struct net_ip4_addr *)msg->options[DHCP_OPT_SUBNET_MASK].dat)
+ : net_ip4_addr_zero;
+ client->lease_server_id =
+ *((struct net_ip4_addr *)msg->options[DHCP_OPT_DHCP_SERVER_ID].dat);
+
+ uint64_t dur_ns_end =
+ ((uint64_t)uint32be_decode(msg->options[DHCP_OPT_ADDRESS_TIME].dat))*NS_PER_S;
+ uint64_t dur_ns_t1 = msg->options[DHCP_OPT_RENEWAL_TIME].len
+ ? ((uint64_t)uint32be_decode(msg->options[DHCP_OPT_RENEWAL_TIME].dat))*NS_PER_S
+ : (dur_ns_end == DHCP_INFINITY * NS_PER_S)
+ ? DHCP_INFINITY * NS_PER_S
+ : dur_ns_end/2;
+ uint64_t dur_ns_t2 = msg->options[DHCP_OPT_REBINDING_TIME].len
+ ? ((uint64_t)uint32be_decode(msg->options[DHCP_OPT_RENEWAL_TIME].dat))*NS_PER_S
+ : (dur_ns_end == DHCP_INFINITY * NS_PER_S)
+ ? DHCP_INFINITY * NS_PER_S
+ : (dur_ns_end*7)/8; /* 0.875 = 7/8 */
+
+ client->lease_time_ns_t1 = (dur_ns_t1 == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_t1;
+ client->lease_time_ns_t2 = (dur_ns_t2 == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_t2;
+ client->lease_time_ns_end = (dur_ns_end == DHCP_INFINITY * NS_PER_S) ? 0 : client->time_ns_init + dur_ns_end;
+
+ if (ifup)
+ VCALL(client->iface, ifup, (struct net_iface_config){
+ .addr = client->lease_client_addr,
+ .gateway_addr = client->lease_auxdata.gateway_addr,
+ .subnet_mask = client->lease_auxdata.subnet_mask,
+ });
+}
+
+/* "in a 10Mb/sec Ethernet internetwork, the delay before the first
+ * retransmission SHOULD be 4 seconds" -- RFC 2131 */
+#define CONFIG_DHCP_RETRANSMIT_INIT_NS (4*NS_PER_S)
+/* "... randomized by the value of a uniform random number chosen from
+ * the range -1 to +1 [seconds]" -- RFC 2131 */
+#define CONFIG_DHCP_RETRANSMIT_RAND_NS (2*NS_PER_S)
+/* "... up to a maximum of 64 seconds." -- RFC 2131 */
+#define CONFIG_DHCP_RETRANSMIT_MAX_NS (64*NS_PER_S)
+
+#define CONFIG_DHCP_TIMEOUT_SELECTING_NS TODO
+#define CONFIG_DHCP_TIMEOUT_REQUESTING_NS TODO
+#define CONFIG_DHCP_TIMEOUT_PROBING_NS TODO
+
+/* Given `init`, the nth transmission is sent at
+ *
+ * n-1
+ * t = ∑ init*2^(i-1) = init*((2^n)-1)
+ * i=0
+ *
+ * This means that the at time t=x, the number of transmissions is
+ *
+ * n = floor(log_2(ceil(x/init)+1))
+ */
+#define _ceildiv(n, d) (( (n)+(d)-1 ) / (d))
+#define _floorlog2(x) (64-__builtin_clzll(x)-1)
+#define _transmissions_at(init, x) _floorlog2(_ceildiv(x, init)+1)
+#define MAX_TRANSMISSIONS(stage) _transmissions_at(CONFIG_DHCP_RETRANSMIT_INIT_NS, CONFIG_DHCP_TIMEOUT_##stage##_NS)
+
+/** "the retransmission algorithm in section 4.1" */
+static x retransmit_4_1(struct dhcp_client *client, uint64_t timeout_ns) {
+ /* "The client MUST adopt a retransmission strategy that
+ * incorporates a randomized exponential backoff algorithm to
+ * determine the delay between retransmissions."
+ *
+ * However, the exact algorithm is implementation-defined,
+ * section 4.1 only provides a suggested ("SHOULD") algorithm
+ * that meets these requirements. I'm not sure why I point
+ * this out; this implements the section 4.1 algorithm
+ * faithfully. */
+
+ if (!client->transmit_delay_ns)
+
+ uint64_t deadline = timeout_ns +
+ rand_uint63n(CONFIG_DHCP_RETRANSMIT_RAND_NS) - CONFIG_DHCP_RETRANSMIT_RAND_NS/2;
+
+ /* "Selecting a new 'xid' for each retransmission is an
+ * implementation decision. A client may choose to reuse the
+ * same 'xid' or select a new 'xid' for each retransmitted
+ * message." -- RFC 2131 */
+
+}
+
+static inline uint64_t dhcp_renew_rebind_deadline(struct dhcp_client *client) {
+ /* "In both RENEWING and REBINDING states, if the client
+ * receives no response to its DHCPREQUEST message, the
+ * client SHOULD wait one-half of the remaining time
+ * until T2 (in RENEWING state) and one-half of the
+ * remaining lease time (in REBINDING state), down to a
+ * minimum of 60 seconds, before retransmitting the
+ * DHCPREQUEST message." -- RFC 2131 */
+
+ uint64_t final_deadline = client->state == STATE_RENEWING
+ ? client->lease_time_ns_t2
+ : client->lease_time_ns_end;
+ if (!final_deadline)
+ return 0;
+
+ uint64_t now = VCALL(bootclock, get_time_ns);
+ if (final_deadline < now + 60*NS_PER_S)
+ return final_deadline;
+ uint64_t half_remaining = (final_deadline - now) / 2;
+ if (half_remaining < 60*NS_PER_S)
+ return 60*NS_PER_S;
+ return half_remaining;
+}
+
+static void dhcp_client_setstate(struct dhcp_client *client,
+ typeof((struct dhcp_client){}.state) newstate,
+ uint8_t send_msgtyp, const char *errstr) {
+ if (send_msgtyp)
+ (void)dhcp_client_send(client, send_msgtyp, errstr);
+ client->state = newstate;
+}
+
+static __attribute__((noreturn)) void dhcp_client_run(struct dhcp_client *client) {
+ assert(client);
+
+ struct dhcp_recv_msg msg;
+ ssize_t r;
+
+ /* State transition diagram: https://datatracker.ietf.org/doc/html/rfc2131#page-35 */
+ for (;;) {
+ switch (client->state) {
+ case STATE_INIT:
+ client->xid = rand_uint63n(UINT32_MAX);
+ client->time_ns_init = VCALL(bootclock, get_time_ns);
+ dhcp_client_setstate(client, STATE_SELECTING, DHCP_MSGTYP_DISCOVER, NULL);
+ break;
+ case STATE_SELECTING:
+ /* TODO: "The client times out and retransmits
+ * the DHCPDISCOVER message if the client
+ * receives no DHCPOFFER messages." */
+ VCALL(client->sock, set_read_deadline, client->time_ns_init+CONFIG_DHCP_SELECTING_NS);
+ switch ((r = dhcp_client_recv(client, &msg))) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_OFFER:
+ /* Accept the first offer. */
+ dhcp_client_take_lease(client, &msg, false);
+ dhcp_client_setstate(client, STATE_REQUESTING, DHCP_MSGTYP_REQUEST, NULL);
+ break;
+ default:
+ /* ignore */
+ }
+ break;
+ case -NET_ETIMEDOUT:
+ dhcp_client_setstate(client, STATE_INIT, 0, NULL);
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
+ break;
+ case STATE_REQUESTING:
+ /* TODO: "The client retransmits the
+ * DHCPREQUEST according to the retransmission
+ * algorithm in section 4.1." */
+ VCALL(client->sock, set_read_deadline, 0);
+ switch ((r = dhcp_client_recv(client, &msg))) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_NAK:
+ dhcp_client_setstate(client, STATE_INIT, 0, NULL);
+ break;
+ case DHCP_MSGTYP_ACK:
+ if (dhcp_check_conflict(client->sock, client->lease_client_addr)) {
+ dhcp_client_setstate(client, STATE_INIT, DHCP_MSGTYP_DECLINE, "IP is already in use");
+ } else {
+ dhcp_client_take_lease(client, &msg, true);
+ dhcp_client_setstate(client, STATE_BOUND, 0, NULL);
+ }
+ break;
+ default:
+ /* ignore */
+ }
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
+ break;
+ case STATE_BOUND:
+ VCALL(client->sock, set_read_deadline, client->lease_time_ns_t1);
+ switch ((r = dhcp_client_recv(client, &msg))) {
+ case 0:
+ /* discard */
+ break;
+ case -NET_ETIMEDOUT:
+ dhcp_client_setstate(client, STATE_RENEWING, DHCP_MSGTYP_REQUEST, NULL);
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
+ break;
+ case STATE_RENEWING:
+ client->xid = rand_uint63n(UINT32_MAX);
+ client->time_ns_init = VCALL(bootclock, get_time_ns);
+
+ VCALL(client->sock, set_read_deadline, dhcp_renew_rebind_deadline(client));
+ switch ((r = dhcp_client_recv(client, &msg))) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_NAK:
+ VCALL(client->iface, ifdown);
+ dhcp_client_setstate(client, STATE_INIT, 0, NULL);
+ break;
+ case DHCP_MSGTYP_ACK:
+ dhcp_client_take_lease(client, &msg, true);
+ dhcp_client_setstate(client, STATE_BOUND, 0, NULL);
+ break;
+ default:
+ /* ignore */
+ }
+ break;
+ case -NET_ETIMEDOUT:
+ if (VCALL(bootclock, get_time_ns) < client->lease_time_ns_t2)
+ break;
+ client->lease_server_id = net_ip4_addr_zero;
+ dhcp_client_setstate(client, STATE_REBINDING, DHCP_MSGTYP_REQUEST, NULL);
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
+ break;
+ case STATE_REBINDING:
+ VCALL(client->sock, set_read_deadline, dhcp_renew_rebind_deadline(client));
+ switch ((r = dhcp_client_recv(client, &msg))) {
+ case 0:
+ switch (msg.options[DHCP_OPT_DHCP_MSG_TYPE].dat[0]) {
+ case DHCP_MSGTYP_NAK:
+ VCALL(client->iface, ifdown);
+ dhcp_client_setstate(client, STATE_BOUND, 0, NULL);
+ break;
+ case DHCP_MSGTYP_ACK:
+ dhcp_client_take_lease(client, &msg, true);
+ dhcp_client_setstate(client, STATE_BOUND, 0, NULL);
+ break;
+ default:
+ /* ignore */
+ }
+ break;
+ case -NET_ETIMEDOUT:
+ if (VCALL(bootclock, get_time_ns) < client->lease_time_ns_end)
+ break;
+ VCALL(client->iface, ifdown);
+ dhcp_client_setstate(client, STATE_BOUND, 0, NULL);
+ break;
+ default:
+ assert(r < 0);
+ debugf("error: recvfrom: %d", r);
+ }
+ break;
+ case STATE_INIT_REBOOT:
+ case STATE_REBOOTING:
+ assert_notreached("initialization with known network address is not supported");
+ default:
+ assert_notreached("invalid client state");
+ }
+ }
+}
+
+__attribute__((noreturn)) void dhcp_client_main(implements_net_iface *iface,
+ char *self_hostname) {
+ assert(iface);
+
+ /* Even though a client ID is optional and not meaningful for
+ * us (the best we can do is to duplicate .chaddr), systemd's
+ * sd-dhcp-client.c asserts that some servers are broken and
+ * require it to be set. */
+ struct {uint8_t typ; struct net_eth_addr dat;} client_id = {
+ DHCP_HTYPE_ETHERNET,
+ VCALL(iface, hwaddr),
+ };
+
+ struct dhcp_client client = {
+ /* Static. */
+ .iface = iface,
+ .sock = VCALL(iface, udp_conn, DHCP_PORT_CLIENT),
+ .self_eth_addr = VCALL(iface, hwaddr),
+ .self_id_len = sizeof(client_id),
+ .self_id_dat = &client_id,
+
+ /* Mutable. */
+ .state = STATE_INIT,
+ };
+ if (self_hostname)
+ strncpy(client.self_hostname, self_hostname, sizeof(client.self_hostname));
+
+ assert(client.sock);
+
+ dhcp_client_run(&client);
+}
diff --git a/libdhcp/dhcp_common.h b/libdhcp/dhcp_common.h
new file mode 100644
index 0000000..0be3a27
--- /dev/null
+++ b/libdhcp/dhcp_common.h
@@ -0,0 +1,305 @@
+/* libdhcp/dhcp_common.h - Base definitions for the DHCP protocol
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ *
+ * -----------------------------------------------------------------------------
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/Internet/DHCP/dhcp.c
+ *
+ * Copyright (c) 2013, WIZnet Co., LTD.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of the <ORGANIZATION> nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *
+ * -----------------------------------------------------------------------------
+ * https://github.com/Wiznet/ioLibrary_Driver/blob/b981401e7f3d07015619adf44c13998e13e777f9/license.txt
+ *
+ * Copyright (c) 2014 WIZnet Co.,Ltd.
+ * Copyright (c) WIZnet ioLibrary Project.
+ * All rights reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * SPDX-License-Identifier: MIT
+ */
+
+#ifndef _LIBDHCP_DHCP_COMMON_H_
+#define _LIBDHCP_DHCP_COMMON_H_
+
+#include <libmisc/endian.h>
+
+/* Config *********************************************************************/
+
+#include "config.h"
+
+#ifndef CONFIG_DHCP_OPT_SIZE
+ #define CONFIG_DHCP_OPT_SIZE DHCP_MSG_MIN_MAX_OPT_SIZE
+#endif
+
+/* RFC 2131 definitions *******************************************************/
+
+/**
+ * "A DHCP client must be prepared to receive DHCP messages with an
+ * 'options' field of at least length 312 octets." --
+ * https://datatracker.ietf.org/doc/html/rfc2131#page-9
+ */
+#define DHCP_MSG_MIN_MAX_OPT_SIZE 312
+static_assert(CONFIG_DHCP_OPT_SIZE >= DHCP_MSG_MIN_MAX_OPT_SIZE);
+
+/** https://datatracker.ietf.org/doc/html/rfc2131#page-9 */
+struct dhcp_msg {
+ uint8_t op; /* DHCP_OP_{x} */
+ uint8_t htype; /* DHCP_HTYPE_{x} */
+ uint8_t hlen; /* length of ->chaddr (`sizeof(struct net_eth_addr)`) */
+ uint8_t hops;
+
+ uint32be_t xid; /* transaction ID */
+
+ uint16be_t secs;
+ uint16be_t flags; /* DHCP_FLAG_{x} */
+
+ struct net_ip4_addr ciaddr; /* Request IP to DHCP sever */
+ struct net_ip4_addr yiaddr; /* Offered IP from DHCP server */
+ struct net_ip4_addr siaddr; /* next-server IP address */
+ struct net_ip4_addr giaddr; /* relay-agent IP address */
+ uint8_t chaddr[16]; /* client hardware (MAC) address */
+ uint8_t sname[64]; /* server name (not used) */
+ uint8_t file[128]; /* boot file name (not used) */
+
+ uint8_t options[CONFIG_DHCP_OPT_SIZE];
+};
+static_assert(offsetof(struct dhcp_msg, options) == 236);
+#define DHCP_MSG_BASE_SIZE offsetof(struct dhcp_msg, options)
+
+/** https://datatracker.ietf.org/doc/html/rfc2131#page-10 */
+#define DHCP_OP_BOOTREQUEST 1
+#define DHCP_OP_BOOTREPLY 2
+
+/** https://datatracker.ietf.org/doc/html/rfc2131#page-11 */
+#define DHCP_FLAG_BROADCAST 0x8000 /* The BROADCAST flag (set by the client)
+ * indicates that the server should use
+ * broadcast-IP replies instead of
+ * unicast-IP because the client is not
+ * yet capable of receiving unicast-IP at
+ * this stage of configuration. */
+
+/** https://datatracker.ietf.org/doc/html/rfc2131#section-3 */
+static const uint8_t dhcp_magic_cookie[] = {99, 130, 83, 99};
+
+/** https://datatracker.ietf.org/doc/html/rfc2131#section-3.3 */
+#define DHCP_INFINITY 0xffffffff
+
+/* IANA assignments ***********************************************************/
+
+/**
+ * Port Numbers
+ * https://www.iana.org/assignments/service-names-port-numbers/service-names-port-numbers.xhtml
+ */
+#define DHCP_PORT_SERVER ((uint16_t)67)
+#define DHCP_PORT_CLIENT ((uint16_t)68)
+
+/**
+ * Hardware Types
+ * https://www.iana.org/assignments/arp-parameters/arp-parameters.xhtml#arp-parameters-2
+ */
+#define DHCP_HTYPE_ETHERNET ((uint8_t) 1)
+
+/**
+ * DHCP Options
+ * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#options
+ */
+#define DHCP_OPT_PAD ((uint8_t) 0) /* RFC2132: length: 0; meaning: None */
+#define DHCP_OPT_SUBNET_MASK ((uint8_t) 1) /* RFC2132: length: 4; meaning: Subnet Mask Value */
+#define DHCP_OPT_TIME_OFFSET ((uint8_t) 2) /* RFC2132: length: 4; meaning: Time Offset in Seconds from UTC (note: deprecated by 100 and 101 */
+#define DHCP_OPT_ROUTER ((uint8_t) 3) /* RFC2132: length: N; meaning: N/4 Router addresses */
+#define DHCP_OPT_TIME_SERVER ((uint8_t) 4) /* RFC2132: length: N; meaning: N/4 Timeserver addresses */
+#define DHCP_OPT_NAME_SERVER ((uint8_t) 5) /* RFC2132: length: N; meaning: N/4 IEN-116 Server addresses */
+#define DHCP_OPT_DOMAIN_SERVER ((uint8_t) 6) /* RFC2132: length: N; meaning: N/4 DNS Server addresses */
+#define DHCP_OPT_LOG_SERVER ((uint8_t) 7) /* RFC2132: length: N; meaning: N/4 Logging Server addresses */
+#define DHCP_OPT_QUOTES_SERVER ((uint8_t) 8) /* RFC2132: length: N; meaning: N/4 Quotes Server addresses */
+#define DHCP_OPT_LPR_SERVER ((uint8_t) 9) /* RFC2132: length: N; meaning: N/4 Printer Server addresses */
+#define DHCP_OPT_IMPRESS_SERVER ((uint8_t) 10) /* RFC2132: length: N; meaning: N/4 Impress Server addresses */
+#define DHCP_OPT_RLP_SERVER ((uint8_t) 11) /* RFC2132: length: N; meaning: N/4 RLP Server addresses */
+#define DHCP_OPT_HOSTNAME ((uint8_t) 12) /* RFC2132: length: N; meaning: Hostname string */
+#define DHCP_OPT_BOOT_FILE_SIZE ((uint8_t) 13) /* RFC2132: length: 2; meaning: Size of boot file in 512 byte chunks */
+#define DHCP_OPT_MERIT_DUMP_FILE ((uint8_t) 14) /* RFC2132: length: N; meaning: Client to dump and name the file to dump it to */
+#define DHCP_OPT_DOMAIN_NAME ((uint8_t) 15) /* RFC2132: length: N; meaning: The DNS domain name of the client */
+#define DHCP_OPT_SWAP_SERVER ((uint8_t) 16) /* RFC2132: length: N; meaning: Swap Server address */
+#define DHCP_OPT_ROOT_PATH ((uint8_t) 17) /* RFC2132: length: N; meaning: Path name for root disk */
+#define DHCP_OPT_EXTENSION_FILE ((uint8_t) 18) /* RFC2132: length: N; meaning: Path name for more BOOTP info */
+#define DHCP_OPT_FORWARD_ONOFF ((uint8_t) 19) /* RFC2132: length: 1; meaning: Enable/Disable IP Forwarding */
+#define DHCP_OPT_SRCRTE_ONOFF ((uint8_t) 20) /* RFC2132: length: 1; meaning: Enable/Disable Source Routing */
+#define DHCP_OPT_POLICY_FILTER ((uint8_t) 21) /* RFC2132: length: N; meaning: Routing Policy Filters */
+#define DHCP_OPT_MAX_DG_ASSEMBLY ((uint8_t) 22) /* RFC2132: length: 2; meaning: Max Datagram Reassembly Size */
+#define DHCP_OPT_DEFAULT_IP_TTL ((uint8_t) 23) /* RFC2132: length: 1; meaning: Default IP Time to Live */
+#define DHCP_OPT_MTU_TIMEOUT ((uint8_t) 24) /* RFC2132: length: 4; meaning: Path MTU Aging Timeout */
+#define DHCP_OPT_MTU_PLATEAU ((uint8_t) 25) /* RFC2132: length: N; meaning: Path MTU Plateau Table */
+#define DHCP_OPT_MTU_INTERFACE ((uint8_t) 26) /* RFC2132: length: 2; meaning: Interface MTU Size */
+#define DHCP_OPT_MTU_SUBNET ((uint8_t) 27) /* RFC2132: length: 1; meaning: All Subnets are Local */
+#define DHCP_OPT_BROADCAST_ADDRESS ((uint8_t) 28) /* RFC2132: length: 4; meaning: Broadcast Address */
+#define DHCP_OPT_MASK_DISCOVERY ((uint8_t) 29) /* RFC2132: length: 1; meaning: Perform Mask Discovery */
+#define DHCP_OPT_MASK_SUPPLIER ((uint8_t) 30) /* RFC2132: length: 1; meaning: Provide Mask to Others */
+#define DHCP_OPT_ROUTER_DISCOVERY ((uint8_t) 31) /* RFC2132: length: 1; meaning: Perform Router Discovery */
+#define DHCP_OPT_ROUTER_REQUEST ((uint8_t) 32) /* RFC2132: length: 4; meaning: Router Solicitation Address */
+#define DHCP_OPT_STATIC_ROUTE ((uint8_t) 33) /* RFC2132: length: N; meaning: Static Routing Table */
+#define DHCP_OPT_TRAILERS ((uint8_t) 34) /* RFC2132: length: 1; meaning: Trailer Encapsulation */
+#define DHCP_OPT_ARP_TIMEOUT ((uint8_t) 35) /* RFC2132: length: 4; meaning: ARP Cache Timeout */
+#define DHCP_OPT_ETHERNET ((uint8_t) 36) /* RFC2132: length: 1; meaning: Ethernet Encapsulation */
+#define DHCP_OPT_DEFAULT_TCP_TTL ((uint8_t) 37) /* RFC2132: length: 1; meaning: Default TCP Time to Live */
+#define DHCP_OPT_KEEPALIVE_TIME ((uint8_t) 38) /* RFC2132: length: 4; meaning: TCP Keepalive Interval */
+#define DHCP_OPT_KEEPALIVE_DATA ((uint8_t) 39) /* RFC2132: length: 1; meaning: TCP Keepalive Garbage */
+#define DHCP_OPT_NIS_DOMAIN ((uint8_t) 40) /* RFC2132: length: N; meaning: NIS Domain Name */
+#define DHCP_OPT_NIS_SERVERS ((uint8_t) 41) /* RFC2132: length: N; meaning: NIS Server Addresses */
+#define DHCP_OPT_NTP_SERVERS ((uint8_t) 42) /* RFC2132: length: N; meaning: NTP Server Addresses */
+#define DHCP_OPT_VENDOR_SPECIFIC ((uint8_t) 43) /* RFC2132: length: N; meaning: Vendor Specific Information */
+#define DHCP_OPT_NETBIOS_NAME_SRV ((uint8_t) 44) /* RFC2132: length: N; meaning: NETBIOS Name Servers */
+#define DHCP_OPT_NETBIOS_DIST_SRV ((uint8_t) 45) /* RFC2132: length: N; meaning: NETBIOS Datagram Distribution */
+#define DHCP_OPT_NETBIOS_NODE_TYPE ((uint8_t) 46) /* RFC2132: length: 1; meaning: NETBIOS Node Type */
+#define DHCP_OPT_NETBIOS_SCOPE ((uint8_t) 47) /* RFC2132: length: N; meaning: NETBIOS Scope */
+#define DHCP_OPT_X_WINDOW_FONT ((uint8_t) 48) /* RFC2132: length: N; meaning: X Window Font Server */
+#define DHCP_OPT_X_WINDOW_MANAGER ((uint8_t) 49) /* RFC2132: length: N; meaning: X Window Display Manager */
+#define DHCP_OPT_ADDRESS_REQUEST ((uint8_t) 50) /* RFC2132: length: 4; meaning: Requested IP Address */
+#define DHCP_OPT_ADDRESS_TIME ((uint8_t) 51) /* RFC2132: length: 4; meaning: IP Address Lease Time */
+#define DHCP_OPT_OVERLOAD ((uint8_t) 52) /* RFC2132: length: 1; meaning: Overload "sname" or "file */
+#define DHCP_OPT_DHCP_MSG_TYPE ((uint8_t) 53) /* RFC2132: length: 1; meaning: DHCP Message Type */
+#define DHCP_OPT_DHCP_SERVER_ID ((uint8_t) 54) /* RFC2132: length: 4; meaning: DHCP Server Identification */
+#define DHCP_OPT_PARAMETER_LIST ((uint8_t) 55) /* RFC2132: length: N; meaning: Parameter Request List */
+#define DHCP_OPT_DHCP_MESSAGE ((uint8_t) 56) /* RFC2132: length: N; meaning: DHCP Error Message */
+#define DHCP_OPT_DHCP_MAX_MSG_SIZE ((uint8_t) 57) /* RFC2132: length: 2; meaning: DHCP Maximum Message Size */ /* note: includes IP & UDP headers (20 and 8, respectively) */
+#define DHCP_OPT_RENEWAL_TIME ((uint8_t) 58) /* RFC2132: length: 4; meaning: DHCP Renewal (T1) Time */
+#define DHCP_OPT_REBINDING_TIME ((uint8_t) 59) /* RFC2132: length: 4; meaning: DHCP Rebinding (T2) Time */
+#define DHCP_OPT_CLASS_ID ((uint8_t) 60) /* RFC2132: length: N; meaning: Class Identifier */
+#define DHCP_OPT_CLIENT_ID ((uint8_t) 61) /* RFC2132: length: N; meaning: Client Identifier */
+ /* ... */
+#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;
+ }
+};
+
+/**
+ * DHCP Message Type 53 Values
+ * https://www.iana.org/assignments/bootp-dhcp-parameters/bootp-dhcp-parameters.xhtml#message-type-53
+ */
+#define DHCP_MSGTYP_DISCOVER ((uint8_t) 1) /* RFC2132, client->server */
+#define DHCP_MSGTYP_OFFER ((uint8_t) 2) /* RFC2132, server->client */
+#define DHCP_MSGTYP_REQUEST ((uint8_t) 3) /* RFC2132, client->server */
+#define DHCP_MSGTYP_DECLINE ((uint8_t) 4) /* RFC2132, client->server */
+#define DHCP_MSGTYP_ACK ((uint8_t) 5) /* RFC2132, server->client */
+#define DHCP_MSGTYP_NAK ((uint8_t) 6) /* RFC2132, server->client */
+#define DHCP_MSGTYP_RELEASE ((uint8_t) 7) /* RFC2132, client->server */
+#define DHCP_MSGTYP_INFORM ((uint8_t) 8) /* RFC2132, client->server */
+
+#endif /* _LIBDHCP_DHCP_COMMON_H_ */
diff --git a/libdhcp/f.md b/libdhcp/f.md
new file mode 100644
index 0000000..447ee5b
--- /dev/null
+++ b/libdhcp/f.md
@@ -0,0 +1,27 @@
+xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+| <c> | | <c> |
+| In the message from the | | ... then the server ... |
+| client the ... | | |
+| | | |
+| and and | | |
+
+| <c> | <c> | <c> | | <c> | <c> | <c> |
+| giaddr | ciaddr | bcast | | | | |
+| (relay) | (cur ip) | bit | | verbs | message types | to |
+| is | is | is | | | | |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| nonzero | * | * | | sends | (all) | 'giaddr' BOOTP relay agent |
+| | | | | | | |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| zero | nonzero | * | | unicasts | DHCPOFFER & DHCPACK | 'ciaddr' client |
+| | | | | broadcasts | DHCPNACK | 0xffffffff |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| zero | zero | 1 | | broadcasts | DHCPOFFER & DHCPACK | 0xffffffff |
+| | | | | broadcasts | DHCPNACK | 0xffffffff |
+|---------+----------+-------+---+------------+---------------------+-----------------------------|
+| zero | zero | 0 | | unicasts | DHCPOFFER & DHCPACK | client hw addr and 'yiaddr' |
+| | | | | broadcasts | DHCPNACK | 0xffffffff |
+
+In all cases, when 'giaddr' is zero, the server broadcasts any DHCPNAK
+messages to 0xffffffff.
diff --git a/libdhcp/include/libdhcp/dhcp.h b/libdhcp/include/libdhcp/client.h
index 0b4c06f..b0eaa52 100644
--- a/libdhcp/include/libdhcp/dhcp.h
+++ b/libdhcp/include/libdhcp/client.h
@@ -1,4 +1,4 @@
-/* libdhcp/dhcp.h - A DHCP client
+/* libdhcp/client.h - A DHCP client
*
* Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
* SPDX-License-Identifier: AGPL-3.0-or-later
@@ -68,91 +68,8 @@
#define _LIBDHCP_DHCP_H_
#include <libhw/generic/net.h>
-#include "hw/w5500.h"
-enum dhcp_event {
- DHCP_ASSIGN,
- DHCP_UPDATE,
- DHCP_CONFLICT,
-};
-
-typedef void (*dhcp_callback_t)(enum dhcp_event, struct dhcp_lease);
-
-/* Retry to processing DHCP */
-#define MAX_DHCP_RETRY 2 ///< Maximum retry count
-#define DHCP_WAIT_TIME 10 ///< Wait Time 10s
-
-/*
- * @brief return value of @ref DHCP_run()
- */
-enum {
- DHCP_RET_FAILED = 0, // Processing Fail
- DHCP_RET_RUNNING, // Processing DHCP protocol
- DHCP_RET_IP_ASSIGN, // First Occupy IP from DHPC server (if cbfunc == null, act as default default_ip_assign)
- DHCP_RET_IP_CHANGED, // Change IP address by new ip from DHCP (if cbfunc == null, act as default default_ip_update)
- DHCP_RET_IP_LEASED, // Stand by
- DHCP_RET_STOPPED // Stop processing DHCP protocol
-};
-
-/*
- * @brief DHCP client initialization (outside of the main loop)
- * @param buf - buffer for processing DHCP message
- */
-void DHCP_init(void *buf);
-
-/*
- * @brief DHCP 1s Tick Timer handler
- * @note SHOULD BE register to your system 1s Tick timer handler
- */
-void DHCP_time_handler(void);
-
-/*
- * @brief DHCP client in the main loop
- * @return The value is as the follow \n
- * @ref DHCP_FAILED \n
- * @ref DHCP_RUNNING \n
- * @ref DHCP_IP_ASSIGN \n
- * @ref DHCP_IP_CHANGED \n
- * @ref DHCP_IP_LEASED \n
- * @ref DHCP_STOPPED \n
- *
- * @note This function is always called by you main task.
- */
-uint8_t DHCP_run(struct w5500 *chip, uint8_t socknum, dhcp_callback_t cb);
-//uint8_t DHCP_run(implements_net_packet_conn *sock, dhcp_callback_t cb);
-
-/*
- * @brief Stop DHCP processing
- * @note If you want to restart. call DHCP_init() and DHCP_run()
- */
-void DHCP_stop(implements_net_packet_conn *sock);
-
-void xhandle(uint8_t opt_typ, uint8_t opt_len, uint8_t *opt_dat) {
- switch (opt_typ) {
- case DHCP_OPT_SUBNET_MASK:
- if (opt_len != 4)
- return;
- global_lease.subnet_mask.octets[0] = opt_dat[0];
- global_lease.subnet_mask.octets[1] = opt_dat[1];
- global_lease.subnet_mask.octets[2] = opt_dat[2];
- global_lease.subnet_mask.octets[3] = opt_dat[3];
- case DHCP_OPT_ROUTER :
- if (opt_len < 4)
- return;
- global_lease.gateway.octets[0] = opt_dat[0];
- global_lease.gateway.octets[1] = opt_dat[1];
- global_lease.gateway.octets[2] = opt_dat[2];
- global_lease.gateway.octets[3] = opt_dat[3];
- break;
- case DHCP_OPT_DOMAIN_SERVER :
- if (opt_len < 4)
- return;
- global_lease.dns.octets[0] = opt_dat[0];
- global_lease.dns.octets[1] = opt_dat[1];
- global_lease.dns.octets[2] = opt_dat[2];
- global_lease.dns.octets[3] = opt_dat[3];
- break;
- }
-}
+__attribute__((noreturn)) void dhcp_client_main(implements_net_iface *iface,
+ char *self_hostname);
#endif /* _LIBDHCP_DHCP_H_ */
diff --git a/libhw/common_include/libhw/generic/net.h b/libhw/common_include/libhw/generic/net.h
index 8dedaea..def533c 100644
--- a/libhw/common_include/libhw/generic/net.h
+++ b/libhw/common_include/libhw/generic/net.h
@@ -52,6 +52,14 @@ struct net_stream_listener_vtable {
* connection is still open.
*/
implements_net_stream_conn *(*accept)(implements_net_stream_listener *self);
+
+ /**
+ * The net_stream_conn returned from accept() may still be
+ * valid after the listener is closed.
+ *
+ * Return 0 on success, -errno on error.
+ */
+ int (*close)(implements_net_stream_listener *self);
};
struct net_stream_conn_vtable {
@@ -106,6 +114,11 @@ struct net_packet_conn_vtable {
ssize_t (*sendto )(implements_net_packet_conn *self,
void *buf, size_t len,
struct net_ip4_addr node, uint16_t port);
+ /**
+ * @return The full length of the message, which may be more
+ * than the given `len` (as if the Linux MSG_TRUNC flag were
+ * given).
+ */
ssize_t (*recvfrom)(implements_net_packet_conn *self,
void *buf, size_t len,
struct net_ip4_addr *ret_node, uint16_t *ret_port);
@@ -114,4 +127,28 @@ struct net_packet_conn_vtable {
int (*close )(implements_net_packet_conn *self);
};
+/* Interfaces *****************************************************************/
+
+struct net_iface_config {
+ struct net_ip4_addr addr;
+ struct net_ip4_addr gateway_addr;
+ struct net_ip4_addr subnet_mask;
+};
+
+struct net_iface_vtable;
+
+typedef struct {
+ struct net_iface_vtable *vtable;
+} implements_net_iface;
+
+struct net_iface_vtable {
+ struct net_eth_addr (*hwaddr )(implements_net_iface *);
+ void (*ifup )(implements_net_iface *, struct net_iface_config);
+ void (*ifdown )(implements_net_iface *);
+
+ implements_net_stream_listener *(*tcp_listen)(implements_net_iface *, uint16_t local_port);
+ implements_net_stream_conn *(*tcp_dial )(implements_net_iface *, struct net_ip4_addr remote_node, uint16_t remote_port);
+ implements_net_packet_conn *(*udp_conn )(implements_net_iface *, uint16_t local_port);
+};
+
#endif /* _LIBHW_GENERIC_NET_H_ */
diff --git a/libhw/host_alarmclock.c b/libhw/host_alarmclock.c
index 141927c..4f43fc7 100644
--- a/libhw/host_alarmclock.c
+++ b/libhw/host_alarmclock.c
@@ -4,13 +4,13 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <assert.h>
#include <errno.h>
#include <error.h>
#include <signal.h>
#include <time.h>
#include <libcr/coroutine.h>
+#include <libmisc/assert.h>
#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES
diff --git a/libhw/host_net.c b/libhw/host_net.c
index 27df2e2..db31522 100644
--- a/libhw/host_net.c
+++ b/libhw/host_net.c
@@ -6,7 +6,6 @@
#define _GNU_SOURCE /* for pthread_sigqueue(3gnu) */
/* misc */
-#include <assert.h> /* for assert() */
#include <errno.h> /* for errno, EAGAIN, EINVAL */
#include <error.h> /* for error(3gnu) */
#include <stdlib.h> /* for abs(), shutdown(), SHUT_RD, SHUT_WR, SHUT_RDWR */
@@ -20,6 +19,7 @@
#include <signal.h> /* for siginfo_t, struct sigaction, enum sigval, sigaction(), SA_SIGINFO */
#include <libcr/coroutine.h>
+#include <libmisc/assert.h>
#include <libmisc/vcall.h>
#include <libhw/generic/alarmclock.h>
@@ -92,14 +92,16 @@ static inline ssize_t hostnet_map_negerrno(ssize_t v) {
/* TCP init() ( AKA socket(3) + listen(3) )************************************/
-static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener);
+static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *);
+static int hostnet_tcplist_close(implements_net_stream_listener *);
static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *conn, uint64_t ts_ns);
static ssize_t hostnet_tcp_read(implements_net_stream_conn *conn, void *buf, size_t count);
static ssize_t hostnet_tcp_write(implements_net_stream_conn *conn, void *buf, size_t count);
static int hostnet_tcp_close(implements_net_stream_conn *conn, bool rd, bool wr);
static struct net_stream_listener_vtable hostnet_tcp_listener_vtable = {
- .accept = hostnet_tcp_accept,
+ .accept = hostnet_tcplist_accept,
+ .close = hostnet_tcplist_close,
};
static struct net_stream_conn_vtable hostnet_tcp_conn_vtable = {
@@ -136,7 +138,7 @@ void hostnet_tcp_listener_init(struct hostnet_tcp_listener *self, uint16_t port)
self->fd = listenerfd;
}
-/* TCP accept() ***************************************************************/
+/* TCP listener accept() ******************************************************/
struct hostnet_pthread_accept_args {
pthread_t cr_thread;
@@ -156,7 +158,7 @@ static void *hostnet_pthread_accept(void *_args) {
return NULL;
}
-static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_listener *_listener) {
+static implements_net_stream_conn *hostnet_tcplist_accept(implements_net_stream_listener *_listener) {
struct hostnet_tcp_listener *listener =
VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener);
assert(listener);
@@ -176,6 +178,16 @@ static implements_net_stream_conn *hostnet_tcp_accept(implements_net_stream_list
return &listener->active_conn;
}
+/* TCP listener close() *******************************************************/
+
+static int hostnet_tcplist_close(implements_net_stream_listener *_listener) {
+ struct hostnet_tcp_listener *listener =
+ VCALL_SELF(struct hostnet_tcp_listener, implements_net_stream_listener, _listener);
+ assert(listener);
+
+ return hostnet_map_negerrno(close(listener->fd) ? -errno : 0);
+}
+
/* TCP read() *****************************************************************/
static void hostnet_tcp_set_read_deadline(implements_net_stream_conn *_conn, uint64_t ts_ns) {
@@ -313,7 +325,7 @@ static int hostnet_tcp_close(implements_net_stream_conn *_conn, bool rd, bool wr
else if (!rd && wr)
how = SHUT_WR;
else
- assert(false);
+ assert_notreached("invalid arguments to stream_conn.close()");
return hostnet_map_negerrno(shutdown(conn->fd, how) ? -errno : 0);
}
@@ -457,7 +469,8 @@ static void *hostnet_pthread_recvfrom(void *_args) {
if (*(args->ret_size) < 0)
goto end;
- *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count, 0, &addr.gen, &addr_size);
+ *(args->ret_size) = recvfrom(args->connfd, args->buf, args->count,
+ MSG_TRUNC, &addr.gen, &addr_size);
if (*(args->ret_size) < 0)
goto end;
diff --git a/libhw/rp2040_hwspi.c b/libhw/rp2040_hwspi.c
index 4edcdf7..da7ee78 100644
--- a/libhw/rp2040_hwspi.c
+++ b/libhw/rp2040_hwspi.c
@@ -4,11 +4,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <assert.h>
-
#include <hardware/spi.h> /* pico-sdk:hardware_spi */
#include <hardware/gpio.h> /* pico-sdk:hardware_gpio */
+#include <libmisc/assert.h>
#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIBHW_RP2040_HWSPI_H YES
@@ -67,7 +66,7 @@ void _rp2040_hwspi_init(struct rp2040_hwspi *self,
assert(pin_mosi == 11 || pin_mosi == 15 || pin_mosi == 27);
break;
default:
- assert(false);
+ assert_notreached("invalid hwspi instance number");
}
spi_init(inst, baudrate_hz);
@@ -117,7 +116,7 @@ static void rp2040_hwspi_readwritev(implements_spi *_self, const struct bidi_iov
else if (iov[i].iov_read_dst)
spi_read_blocking(inst, 0, iov[i].iov_read_dst, iov[i].iov_len);
else
- assert(false);
+ assert_notreached("bidi_iovec is neither read nor write");
}
gpio_put(self->pin_cs, 1);
}
diff --git a/libhw/rp2040_hwtimer.c b/libhw/rp2040_hwtimer.c
index 54bdab3..69c2a50 100644
--- a/libhw/rp2040_hwtimer.c
+++ b/libhw/rp2040_hwtimer.c
@@ -4,12 +4,11 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
-#include <assert.h>
-
#include <hardware/irq.h> /* pico-sdk:hardware_irq */
#include <hardware/timer.h> /* pico-sdk:hardware_timer */
#include <libcr/coroutine.h>
+#include <libmisc/assert.h>
#include <libmisc/vcall.h>
#define IMPLEMENTATION_FOR_LIBHW_GENERIC_ALARMCLOCK_H YES
diff --git a/libhw/rp2040_include/libhw/w5500.h b/libhw/rp2040_include/libhw/w5500.h
index 16f88cc..7538bbc 100644
--- a/libhw/rp2040_include/libhw/w5500.h
+++ b/libhw/rp2040_include/libhw/w5500.h
@@ -25,16 +25,17 @@ struct _w5500_socket {
uint8_t socknum;
/* mutable */
+ struct _w5500_socket *next_free;
enum {
W5500_MODE_NONE = 0,
W5500_MODE_TCP,
W5500_MODE_UDP,
} mode;
- uint16_t port; /* MODE_{TCP,UDP} */
- uint64_t read_deadline_ns; /* MODE_{TCP,UDP} */
- cr_sema_t listen_sema; /* MODE_TCP */
- cr_sema_t read_sema; /* MODE_{TCP,UDP} */
- bool read_open, write_open; /* MODE_TCP */
+ uint16_t port; /* MODE_{TCP,UDP} */
+ uint64_t read_deadline_ns; /* MODE_{TCP,UDP} */
+ cr_sema_t listen_sema; /* MODE_TCP */
+ cr_sema_t read_sema; /* MODE_{TCP,UDP} */
+ bool list_open, read_open, write_open; /* MODE_TCP */
cr_mutex_t cmd_mu;
END_PRIVATE(LIBHW_W5500_H)
@@ -42,8 +43,9 @@ struct _w5500_socket {
struct w5500 {
/* const-after-init */
- implements_spi *spidev;
+ implements_net_iface;
BEGIN_PRIVATE(LIBHW_W5500_H)
+ implements_spi *spidev;
uint pin_intr;
uint pin_reset;
struct net_eth_addr hwaddr;
@@ -51,6 +53,7 @@ struct w5500 {
/* mutable */
uint16_t next_local_port;
struct _w5500_socket sockets[8];
+ struct _w5500_socket *free;
cr_sema_t intr;
END_PRIVATE(LIBHW_W5500_H)
};
@@ -88,24 +91,4 @@ void w5500_hard_reset(struct w5500 *self);
*/
void w5500_soft_reset(struct w5500 *self);
-struct w5500_netcfg {
- struct net_ip4_addr gateway_addr;
- struct net_ip4_addr subnet_mask;
- struct net_ip4_addr addr;
-};
-
-/**
- * TODO.
- */
-void w5500_netcfg(struct w5500 *self, struct w5500_netcfg cfg);
-
-implements_net_stream_listener *w5500_tcp_listen(struct w5500 *self, uint8_t socknum,
- uint16_t port);
-
-/**
- * TODO.
- */
-implements_net_packet_conn *w5500_udp_conn(struct w5500 *self, uint8_t socknum,
- uint16_t port);
-
#endif /* _LIBHW_W5500_H_ */
diff --git a/libhw/w5500.c b/libhw/w5500.c
index 1923a17..ae48a61 100644
--- a/libhw/w5500.c
+++ b/libhw/w5500.c
@@ -107,22 +107,44 @@
/* vtables ********************************************************************/
-static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *);
-
-static void w5500_tcp_set_read_deadline(implements_net_stream_conn *, uint64_t ns);
-static ssize_t w5500_tcp_read( implements_net_stream_conn *, void *, size_t);
-static ssize_t w5500_tcp_write( implements_net_stream_conn *, void *, size_t);
-static int w5500_tcp_close( implements_net_stream_conn *, bool rd, bool wr);
-
-static void w5500_udp_set_read_deadline(implements_net_packet_conn *, uint64_t ns);
-static ssize_t w5500_udp_recvfrom( implements_net_packet_conn *, void *, size_t,
- struct net_ip4_addr *, uint16_t *);
-static ssize_t w5500_udp_sendto( implements_net_packet_conn *, void *, size_t,
- struct net_ip4_addr, uint16_t);
-static int w5500_udp_close( implements_net_packet_conn *);
+/* iface */
+static struct net_eth_addr w5500_if_hwaddr (implements_net_iface *);
+static void w5500_if_up (implements_net_iface *, struct net_iface_config);
+static void w5500_if_down (implements_net_iface *);
+static implements_net_stream_listener *w5500_if_tcp_listen (implements_net_iface *, uint16_t local_port);
+static implements_net_stream_conn *w5500_if_tcp_dial (implements_net_iface *, struct net_ip4_addr, uint16_t remote_port);
+static implements_net_packet_conn *w5500_if_udp_conn (implements_net_iface *, uint16_t local_port);
+
+/* stream_listener */
+static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *);
+static int w5500_tcplist_close (implements_net_stream_listener *);
+
+/* stream_conn */
+static void w5500_tcp_set_read_deadline (implements_net_stream_conn *, uint64_t ns);
+static ssize_t w5500_tcp_read (implements_net_stream_conn *, void *, size_t);
+static ssize_t w5500_tcp_write (implements_net_stream_conn *, void *, size_t);
+static int w5500_tcp_close (implements_net_stream_conn *, bool rd, bool wr);
+
+/* packet_conn */
+static void w5500_udp_set_read_deadline (implements_net_packet_conn *, uint64_t ns);
+static ssize_t w5500_udp_recvfrom (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr *, uint16_t *);
+static ssize_t w5500_udp_sendto (implements_net_packet_conn *, void *, size_t, struct net_ip4_addr, uint16_t);
+static int w5500_udp_close (implements_net_packet_conn *);
+
+/* tables */
+
+static struct net_iface_vtable w5500_iface_vtable = {
+ .hwaddr = w5500_if_hwaddr,
+ .ifup = w5500_if_up,
+ .ifdown = w5500_if_down,
+ .tcp_listen = w5500_if_tcp_listen,
+ .tcp_dial = w5500_if_tcp_dial,
+ .udp_conn = w5500_if_udp_conn,
+};
static struct net_stream_listener_vtable w5500_tcp_listener_vtable = {
- .accept = w5500_tcp_accept,
+ .accept = w5500_tcplist_accept,
+ .close = w5500_tcplist_close,
};
static struct net_stream_conn_vtable w5500_tcp_conn_vtable = {
@@ -141,14 +163,40 @@ static struct net_packet_conn_vtable w5500_udp_conn_vtable = {
/* mid-level utilities ********************************************************/
-#if 0
-static uint16_t w5500_get_local_port(struct w5500 *self) {
+static uint16_t w5500_alloc_local_port(struct w5500 *self) {
+ assert(self);
uint16_t ret = self->next_local_port++;
if (self->next_local_port > CONFIG_W5500_LOCAL_PORT_MAX)
self->next_local_port = CONFIG_W5500_LOCAL_PORT_MIN;
return ret;
}
-#endif
+
+static struct _w5500_socket *w5500_alloc_socket(struct w5500 *self) {
+ assert(self);
+ struct _w5500_socket *sock = self->free;
+ if (!sock)
+ return NULL;
+ self->free = sock->next_free;
+ sock->next_free = NULL;
+ assert(sock->mode == W5500_MODE_NONE);
+ return sock;
+}
+
+static void w5500_free_socket(struct w5500 *self, struct _w5500_socket *sock) {
+ assert(self);
+ assert(sock);
+ sock->mode = W5500_MODE_NONE;
+ sock->next_free = self->free;
+ self->free = sock;
+}
+
+static void w5500_tcp_maybe_free(struct w5500 *chip, struct _w5500_socket *sock) {
+ assert(chip);
+ assert(sock);
+ assert(sock->mode == W5500_MODE_TCP);
+ if (!sock->list_open && !sock->read_open && !sock->write_open)
+ w5500_free_socket(chip, sock);
+}
static COROUTINE w5500_irq_cr(void *_chip) {
struct w5500 *chip = _chip;
@@ -263,6 +311,7 @@ void _w5500_init(struct w5500 *chip,
/* Initialize the data structures. */
*chip = (struct w5500){
/* const-after-init */
+ .implements_net_iface = { .vtable = &w5500_iface_vtable },
.spidev = spi,
.pin_intr = pin_intr,
.pin_reset = pin_reset,
@@ -270,6 +319,7 @@ void _w5500_init(struct w5500 *chip,
/* mutable */
.next_local_port = CONFIG_W5500_LOCAL_PORT_MIN,
};
+ chip->free = &chip->sockets[0];
for (uint8_t i = 0; i < 8; i++) {
chip->sockets[i] = (struct _w5500_socket){
/* const-after-init */
@@ -278,7 +328,9 @@ void _w5500_init(struct w5500 *chip,
.implements_net_packet_conn = { .vtable = &w5500_udp_conn_vtable },
.socknum = i,
/* mutable */
- /* these all get initialized to the zero values */
+ .next_free = (i + 1 < 8) ? &chip->sockets[i+1] : NULL,
+ /* the rest of the mutable members get
+ * initialized to the zero values */
};
}
@@ -359,46 +411,122 @@ void w5500_soft_reset(struct w5500 *chip) {
w5500_post_reset(chip);
}
-void w5500_netcfg(struct w5500 *chip, struct w5500_netcfg cfg) {
+static struct net_eth_addr w5500_if_hwaddr(implements_net_iface *_chip) {
+ struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+ assert(chip);
+
+ return chip->hwaddr;
+}
+
+static void w5500_if_up(implements_net_iface *_chip, struct net_iface_config cfg) {
+ struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+ assert(chip);
+
w5500ll_write_common_reg(chip->spidev, ip_gateway_addr, cfg.gateway_addr);
w5500ll_write_common_reg(chip->spidev, ip_subnet_mask, cfg.subnet_mask);
w5500ll_write_common_reg(chip->spidev, ip_addr, cfg.addr);
}
-implements_net_stream_listener *w5500_tcp_listen(struct w5500 *chip, uint8_t socknum,
- uint16_t port) {
+static void w5500_if_down(implements_net_iface *_chip) {
+ w5500_if_up(_chip, (struct net_iface_config){0});
+}
+
+static implements_net_stream_listener *w5500_if_tcp_listen(implements_net_iface *_chip, uint16_t local_port) {
+ struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
assert(chip);
- assert(socknum < 8);
- assert(port);
- assert(chip->sockets[socknum].mode == W5500_MODE_NONE);
- chip->sockets[socknum].mode = W5500_MODE_TCP;
- chip->sockets[socknum].port = port;
- chip->sockets[socknum].read_deadline_ns = 0;
+ struct _w5500_socket *sock = w5500_alloc_socket(chip);
+ if (!sock)
+ return NULL;
- return &chip->sockets[socknum].implements_net_stream_listener;
+ if (!local_port)
+ local_port = w5500_alloc_local_port(chip);
+
+ assert(sock->mode == W5500_MODE_NONE);
+ sock->mode = W5500_MODE_TCP;
+ sock->port = local_port;
+ sock->read_deadline_ns = 0;
+ sock->list_open = true;
+
+ return &sock->implements_net_stream_listener;
}
-implements_net_packet_conn *w5500_udp_conn(struct w5500 *chip, uint8_t socknum,
- uint16_t port) {
+static implements_net_stream_conn *w5500_if_tcp_dial(implements_net_iface *_chip,
+ struct net_ip4_addr node, uint16_t port) {
+ struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
assert(chip);
- assert(socknum < 8);
+ assert(memcmp(node.octets, net_ip4_addr_zero.octets, 4));
+ assert(memcmp(node.octets, net_ip4_addr_broadcast.octets, 4));
assert(port);
- assert(chip->sockets[socknum].mode == W5500_MODE_NONE);
- chip->sockets[socknum].mode = W5500_MODE_UDP;
- chip->sockets[socknum].port = port;
- chip->sockets[socknum].read_deadline_ns = 0;
+ struct _w5500_socket *socket = w5500_alloc_socket(chip);
+ if (!socket)
+ return NULL;
+ uint8_t socknum = socket->socknum;
+
+ uint16_t local_port = w5500_alloc_local_port(chip);
+
+ assert(socket->mode == W5500_MODE_NONE);
+ socket->mode = W5500_MODE_TCP;
+ socket->port = local_port;
+ socket->read_deadline_ns = 0;
+ socket->read_open = socket->write_open = true;
+
+ restart:
+ /* Mimics socket.c:socket(). */
+ w5500_socket_close(socket);
+ w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP);
+ w5500ll_write_sock_reg(chip->spidev, socknum, local_port, uint16be_marshal(socket->port));
+ w5500_socket_cmd(socket, CMD_OPEN);
+ while (w5500ll_read_sock_reg(chip->spidev, socknum, state) != STATE_TCP_INIT)
+ cr_yield();
+
+ /* Mimics socket.c:connect(). */
+ w5500ll_write_sock_reg(chip->spidev, socknum, remote_ip_addr, node);
+ w5500ll_write_sock_reg(chip->spidev, socknum, remote_port, uint16be_marshal(port));
+ w5500_socket_cmd(socket, CMD_CONNECT);
+ for (;;) {
+ uint8_t state = w5500ll_read_sock_reg(chip->spidev, socknum, state);
+ switch (state) {
+ case STATE_TCP_SYNSENT:
+ cr_yield();
+ break;
+ case STATE_TCP_ESTABLISHED:
+ return &socket->implements_net_stream_conn;
+ default:
+ goto restart;
+ }
+ }
+}
+
+implements_net_packet_conn *w5500_if_udp_conn(implements_net_iface *_chip, uint16_t local_port) {
+ struct w5500 *chip = VCALL_SELF(struct w5500, implements_net_iface, _chip);
+ assert(chip);
+
+ struct _w5500_socket *socket = w5500_alloc_socket(chip);
+ if (!socket)
+ return NULL;
+
+ if (!local_port)
+ local_port = w5500_alloc_local_port(chip);
+
+ assert(socket->mode == W5500_MODE_NONE);
+ socket->mode = W5500_MODE_UDP;
+ socket->port = local_port;
+ socket->read_deadline_ns = 0;
- return &chip->sockets[socknum].implements_net_packet_conn;
+ return &socket->implements_net_packet_conn;
}
/* tcp_listener methods *******************************************************/
-static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listener *_socket) {
+static implements_net_stream_conn *w5500_tcplist_accept(implements_net_stream_listener *_socket) {
ASSERT_SELF(stream_listener, TCP);
restart:
+ if (!socket->list_open)
+ return NULL;
+
/* Mimics socket.c:socket(). */
w5500_socket_close(socket);
w5500ll_write_sock_reg(chip->spidev, socknum, mode, SOCKMODE_TCP);
@@ -428,6 +556,14 @@ static implements_net_stream_conn *w5500_tcp_accept(implements_net_stream_listen
}
}
+static int w5500_tcplist_close(implements_net_stream_listener *_socket) {
+ ASSERT_SELF(stream_listener, TCP);
+
+ socket->list_open = false;
+ w5500_tcp_maybe_free(chip, socket);
+ return 0;
+}
+
/* tcp_conn methods ***********************************************************/
static ssize_t w5500_tcp_write(implements_net_stream_conn *_socket, void *buf, size_t count) {
@@ -577,6 +713,7 @@ static int w5500_tcp_close(implements_net_stream_conn *_socket, bool rd, bool wr
}
}
+ w5500_tcp_maybe_free(chip, socket);
return 0;
}
@@ -630,7 +767,7 @@ static void w5500_udp_alarm_handler(void *_arg) {
}
static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf, size_t count,
- struct net_ip4_addr *node, uint16_t *port) {
+ struct net_ip4_addr *ret_node, uint16_t *ret_port) {
ASSERT_SELF(packet_conn, UDP);
assert(buf);
assert(count);
@@ -669,11 +806,14 @@ static ssize_t w5500_udp_recvfrom(implements_net_packet_conn *_socket, void *buf
* this; this is based off of socket.c:recvfrom(). */
uint8_t hdr[8];
w5500ll_read(chip->spidev, ptr, CTL_BLOCK_SOCK(socknum, RX), hdr, sizeof(hdr));
- node->octets[0] = hdr[0];
- node->octets[1] = hdr[1];
- node->octets[2] = hdr[2];
- node->octets[3] = hdr[3];
- *port = uint16be_decode(&hdr[4]);
+ if (ret_node) {
+ ret_node->octets[0] = hdr[0];
+ ret_node->octets[1] = hdr[1];
+ ret_node->octets[2] = hdr[2];
+ ret_node->octets[3] = hdr[3];
+ }
+ if (ret_port)
+ *ret_port = uint16be_decode(&hdr[4]);
uint16_t len = uint16be_decode(&hdr[6]);
/* Now read the actual data. */
if (count > len)
@@ -691,6 +831,6 @@ static int w5500_udp_close(implements_net_packet_conn *_socket) {
ASSERT_SELF(packet_conn, UDP);
w5500_socket_close(socket);
-
+ w5500_free_socket(chip, socket);
return 0;
}
diff --git a/libhw/w5500_ll.h b/libhw/w5500_ll.h
index db66f01..1f578a9 100644
--- a/libhw/w5500_ll.h
+++ b/libhw/w5500_ll.h
@@ -10,12 +10,12 @@
#ifndef _LIBHW_W5500_LL_H_
#define _LIBHW_W5500_LL_H_
-#include <assert.h> /* for assert(), static_assert() */
#include <stdint.h> /* for uint{n}_t */
#include <string.h> /* for memcmp() */
-#include <libmisc/vcall.h> /* for VCALL() */
+#include <libmisc/assert.h> /* for assert(), static_assert() */
#include <libmisc/endian.h> /* for uint16be_t */
+#include <libmisc/vcall.h> /* for VCALL() */
#include <libhw/generic/net.h> /* for struct net_eth_addr, struct net_ip4_addr */
#include <libhw/generic/spi.h> /* for implements_spi */
diff --git a/libmisc/CMakeLists.txt b/libmisc/CMakeLists.txt
index 0c5c019..1f30906 100644
--- a/libmisc/CMakeLists.txt
+++ b/libmisc/CMakeLists.txt
@@ -5,4 +5,7 @@
add_library(libmisc INTERFACE)
target_include_directories(libmisc SYSTEM INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include)
+target_sources(libmisc INTERFACE
+ assert.c
+)
target_compile_options(libmisc INTERFACE "$<$<COMPILE_LANGUAGE:C>:-fplan9-extensions>")
diff --git a/libmisc/assert.c b/libmisc/assert.c
new file mode 100644
index 0000000..7817ba3
--- /dev/null
+++ b/libmisc/assert.c
@@ -0,0 +1,23 @@
+/* libmisc/assert.h - More assertions
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#include <stdio.h> /* for fprintf(), stderr */
+#include <stdlib.h> /* for abort() */
+
+#include <libmisc/assert.h>
+
+#ifndef NDEBUG
+__attribute__((__noreturn__)) void
+__assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg) {
+ fprintf(stderr, "error: %s:%u:%s(): assertion \"%s\" failed%s%s\n",
+ file, line, func,
+ expr,
+ msg ? ": " : "", msg);
+ abort();
+}
+#endif
diff --git a/libmisc/include/libmisc/assert.h b/libmisc/include/libmisc/assert.h
new file mode 100644
index 0000000..c2c4697
--- /dev/null
+++ b/libmisc/include/libmisc/assert.h
@@ -0,0 +1,24 @@
+/* libmisc/assert.h - More assertions
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#ifndef _LIBMISC_ASSERT_H_
+#define _LIBMISC_ASSERT_H_
+
+#ifdef NDEBUG
+# define assert_msg(expr, msg) ((void)0)
+#else
+# define assert_msg(expr, msg) do { if (!(expr)) __assert_msg_fail(#expr, __FILE__, __LINE__, __func__, msg); } while (0)
+__attribute__((__noreturn__)) void
+__assert_msg_fail(const char *expr,
+ const char *file, unsigned int line, const char *func,
+ const char *msg);
+#endif
+
+#define assert(expr) assert_msg(expr, NULL) /* C89, POSIX-2001 */
+#define assert_notreached(msg) do { assert_msg(0, msg); __builtin_unreachable(); } while (0)
+#define static_assert _Static_assert /* C11 */
+
+#endif /* _LIBMISC_ASSERT_H_ */
diff --git a/libmisc/include/libmisc/endian.h b/libmisc/include/libmisc/endian.h
index 24d7d42..b1bc55c 100644
--- a/libmisc/include/libmisc/endian.h
+++ b/libmisc/include/libmisc/endian.h
@@ -7,9 +7,10 @@
#ifndef _LIBMISC_ENDIAN_H_
#define _LIBMISC_ENDIAN_H_
-#include <assert.h>
#include <stdint.h> /* for uint{n}_t */
+#include <libmisc/assert.h>
+
/* Big endian *****************************************************************/
typedef struct {
diff --git a/libmisc/include/libmisc/rand.h b/libmisc/include/libmisc/rand.h
new file mode 100644
index 0000000..bdb0db9
--- /dev/null
+++ b/libmisc/include/libmisc/rand.h
@@ -0,0 +1,46 @@
+/* libmisc/rand.h - Non-crytpographic random-number utilities
+ *
+ * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
+ * SPDX-License-Identifier: AGPL-3.0-or-later
+ */
+
+#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>
+
+/**
+ * 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) {
+ 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 = (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 = (random() << 62) | (random() << 31) | random();
+ } while (rnd >= fair_cnt);
+ return rnd % cnt;
+ } else {
+ assert_notreached("cnt is out of bounds");
+ }
+}
+
+#endif /* _LIBMISC_RAND_H_ */
diff --git a/libmisc/include/libmisc/vcall.h b/libmisc/include/libmisc/vcall.h
index ea9402e..9b54c06 100644
--- a/libmisc/include/libmisc/vcall.h
+++ b/libmisc/include/libmisc/vcall.h
@@ -7,9 +7,10 @@
#ifndef _LIBMISC_VCALL_H_
#define _LIBMISC_VCALL_H_
-#include <assert.h> /* for assert() and static_assert() */
#include <stddef.h> /* for offsetof() */
+#include <libmisc/assert.h>
+
#define VCALL(o, m, ...) \
({ \
assert(o); \
diff --git a/libusb/CMakeLists.txt b/libusb/CMakeLists.txt
index f5d6ae2..c23f30e 100644
--- a/libusb/CMakeLists.txt
+++ b/libusb/CMakeLists.txt
@@ -14,4 +14,5 @@ target_link_libraries(libusb INTERFACE
tinyusb_board
libcr_ipc
+ libmisc
)
diff --git a/libusb/include/libusb/tusb_helpers.h b/libusb/include/libusb/tusb_helpers.h
index a250ca7..1b4e48e 100644
--- a/libusb/include/libusb/tusb_helpers.h
+++ b/libusb/include/libusb/tusb_helpers.h
@@ -1,4 +1,4 @@
-/* Generated by `./libusb/include/libusb/tusb_helpers.h.gen `. DO NOT EDIT! */
+/* Generated by `libusb/include/libusb/tusb_helpers.h.gen `. DO NOT EDIT! */
/* libusb/tusb_helpers.h - Preprocessor macros that I think should be included in TinyUSB
*
* Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com>
@@ -36,16 +36,16 @@
* dated 2000-03-29. There is no longer any mention of this on usb.org, but I found a copy at
* http://www.baiheee.com/Documents/090518/090518112619/USB_LANGIDs.pdf
*
- * So how does the USB-IF defined LANGIDs these days?
+ * So if they don't publish that anymore, how does the USB-IF define LANGIDs these days?
*
* https://www.usb.org/deprecated-links-and-tools says "To get the latest LANGID definitions go to
* https://docs.microsoft.com/en-us/windows/desktop/intl/language-identifier-constants-and-strings. This
* page will change as new LANGIDs are added." That page has no list of LANGIDs, but says "For the
* predefined primary language identifiers with their valid sublanguage identifiers, see
* [\[MS-LCID\]: Windows Language Code Identifier (LCID)
- * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-565326e84c37f)."
+ * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f)."
* That page at the time of this writing as a PDF marked as version 16.0, dated 2024-04-24:
- * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf
+ * https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf
* [MS-LCID] defines an LCID as a 32-bit value consisting of a 16-bit a "Language ID", a 4-bit "Sort
* ID", and 12 reserved bits.
*
diff --git a/libusb/include/libusb/tusb_helpers.h.gen b/libusb/include/libusb/tusb_helpers.h.gen
index 326ca22..5a5d1ac 100755
--- a/libusb/include/libusb/tusb_helpers.h.gen
+++ b/libusb/include/libusb/tusb_helpers.h.gen
@@ -42,16 +42,16 @@ cat <<'EOT'
* dated 2000-03-29. There is no longer any mention of this on usb.org, but I found a copy at
* http://www.baiheee.com/Documents/090518/090518112619/USB_LANGIDs.pdf
*
- * So how does the USB-IF defined LANGIDs these days?
+ * So if they don't publish that anymore, how does the USB-IF define LANGIDs these days?
*
* https://www.usb.org/deprecated-links-and-tools says "To get the latest LANGID definitions go to
* https://docs.microsoft.com/en-us/windows/desktop/intl/language-identifier-constants-and-strings. This
* page will change as new LANGIDs are added." That page has no list of LANGIDs, but says "For the
* predefined primary language identifiers with their valid sublanguage identifiers, see
* [\[MS-LCID\]: Windows Language Code Identifier (LCID)
- * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-565326e84c37f)."
+ * Reference](https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f)."
* That page at the time of this writing as a PDF marked as version 16.0, dated 2024-04-24:
- * https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf
+ * https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf
* [MS-LCID] defines an LCID as a 32-bit value consisting of a 16-bit a "Language ID", a 4-bit "Sort
* ID", and 12 reserved bits.
*
@@ -65,7 +65,7 @@ cat <<'EOT'
*/
EOT
if [[ ! -f 3rd-party/MS-LCID.pdf || 3rd-party/MS-LCID.pdf -ot $0 ]]; then
- wget --no-use-server-timestamps -O 3rd-party/MS-LCID.pdf 'https://winprotocoldoc.blob.core.windows.net/productionwindowsarchives/MS-LCID/%5bMS-LCID%5d.pdf'
+ wget --no-use-server-timestamps -O 3rd-party/MS-LCID.pdf 'https://winprotocoldocs-bhdugrdyduf5h2e4.b02.azurefd.net/MS-LCID/%5bMS-LCID%5d.pdf'
fi
if [[ ! -f 3rd-party/MS-LCID.txt || 3rd-party/MS-LCID.txt -ot 3rd-party/MS-LCID.pdf ]]; then
pdftotext -layout 3rd-party/MS-LCID.pdf
diff --git a/libusb/usb_common.c b/libusb/usb_common.c
index ceabad3..17dba0a 100644
--- a/libusb/usb_common.c
+++ b/libusb/usb_common.c
@@ -7,11 +7,12 @@
#include <stdint.h> /* for uint{n}_t types */
#include <stddef.h> /* for size_t */
#include <string.h> /* memcpy(newlib) */
-#include <assert.h> /* for assert(newlib) */
#include <stdlib.h> /* for malloc(pico_malloc), realloc(pico_malloc), reallocarray(pico_malloc) */
#include "bsp/board_api.h" /* for board_init(), board_init_after_usb(), board_usb_get_serial(TinyUSB) */
#include "tusb.h" /* for various tusb_*_t types */
+#include <libmisc/assert.h>
+
#include <libusb/tusb_helpers.h> /* for LANGID_*, TU_UTF16() */
#include <libusb/usb_common.h>