Jelajahi Sumber

Add WASI support for esp-idf platform (#3348)

Add WASI support for esp-idf platform:

1. add Kconfig and cmake scripts
2. add API "openat" when using littlefs
3. add clock/rwlock/file/socket OS adapter
dongheng 1 tahun lalu
induk
melakukan
6aa7cb85f6

+ 88 - 42
build-scripts/esp-idf/wamr/CMakeLists.txt

@@ -2,56 +2,102 @@
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 # Set WAMR's build options
-if("${IDF_TARGET}" STREQUAL "esp32c3" OR "${IDF_TARGET}" STREQUAL "esp32c6")
-    set(WAMR_BUILD_TARGET "RISCV32")
-else()
-    set(WAMR_BUILD_TARGET "XTENSA")
-endif()
+if (NOT CMAKE_BUILD_EARLY_EXPANSION)
 
-set(WAMR_BUILD_PLATFORM "esp-idf")
+  if (CONFIG_IDF_TARGET_ARCH_RISCV)
+      set (WAMR_BUILD_TARGET "RISCV32")
+  elseif (CONFIG_IDF_TARGET_ARCH_XTENSA)
+      set (WAMR_BUILD_TARGET "XTENSA")
+  else ()
+      message (FATAL_ERROR "Arch ${CONFIG_IDF_TARGET_ARCH} is not supported")
+  endif ()
 
-if (NOT CMAKE_BUILD_TYPE)
-  set(CMAKE_BUILD_TYPE Release)
-endif ()
+  set (WAMR_BUILD_PLATFORM "esp-idf")
 
-if (NOT DEFINED WAMR_BUILD_INTERP)
-  set (WAMR_BUILD_INTERP 1)
-endif ()
+  if (CONFIG_WAMR_BUILD_DEBUG)
+    set (CMAKE_BUILD_TYPE Debug)
+  else ()
+    set (CMAKE_BUILD_TYPE Release)
+  endif ()
 
-if (NOT DEFINED WAMR_BUILD_FAST_INTERP)
-  set (WAMR_BUILD_FAST_INTERP 1)
-endif ()
+  if (CONFIG_WAMR_ENABLE_INTERP)
+    set (WAMR_BUILD_INTERP 1)
+  endif ()
 
-if (NOT DEFINED WAMR_BUILD_AOT)
-  set (WAMR_BUILD_AOT 1)
-endif ()
+  if (CONFIG_WAMR_INTERP_FAST)
+    set (WAMR_BUILD_FAST_INTERP 1)
+  endif ()
 
-if (NOT DEFINED WAMR_BUILD_LIBC_BUILTIN)
-  set (WAMR_BUILD_LIBC_BUILTIN 1)
-endif ()
+  if (CONFIG_WAMR_ENABLE_AOT)
+    set (WAMR_BUILD_AOT 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_LIBC_BUILTIN)
+    set (WAMR_BUILD_LIBC_BUILTIN 1)
+  endif ()
+
+  if (CONFIG_WAMR_INTERP_LOADER_MINI)
+    set (WAMR_BUILD_MINI_LOADER 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_MULTI_MODULE)
+      set (WAMR_BUILD_MULTI_MODULE 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_SHARED_MEMORY)
+      set (WAMR_BUILD_SHARED_MEMORY 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_MEMORY_PROFILING)
+      set (WAMR_BUILD_MEMORY_PROFILING 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_PERF_PROFILING)
+      set (WAMR_BUILD_PERF_PROFILING 1)
+  endif ()
 
-if (NOT DEFINED WAMR_BUILD_APP_FRAMEWORK)
-  set (WAMR_BUILD_APP_FRAMEWORK 0)
+  if (CONFIG_WAMR_ENABLE_REF_TYPES)
+      set (WAMR_BUILD_REF_TYPES 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_LIBC_WASI)
+      set (WAMR_BUILD_LIBC_WASI 1)
+  endif ()
+
+  if (CONFIG_WAMR_ENABLE_LIB_PTHREAD)
+      set (WAMR_BUILD_LIB_PTHREAD 1)
+  endif ()
+
+  set (WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..)
+  include (${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
+
+  list (APPEND srcs "${WAMR_RUNTIME_LIB_SOURCE}"
+                    "${PLATFORM_SHARED_SOURCE}")
+
+  set (include_dirs "${IWASM_DIR}/include"
+                    "${UTILS_SHARED_DIR}"
+                    "${PLATFORM_SHARED_DIR}"
+                    "${PLATFORM_SHARED_DIR}/../include"
+                    "${IWASM_COMMON_DIR}")
 endif ()
 
-if (NOT CMAKE_BUILD_EARLY_EXPANSION)
-    if (WAMR_BUILD_TARGET STREQUAL "XTENSA")
-      idf_build_set_property(COMPILE_DEFINITIONS "-DBUILD_TARGET_XTENSA=1" APPEND)
-    endif ()
-    if (WAMR_BUILD_INTERP)
-      idf_build_set_property(COMPILE_DEFINITIONS "-DWASM_ENABLE_INTERP=1" APPEND)
-    endif ()
-    if (WAMR_BUILD_AOT)
-      idf_build_set_property(COMPILE_DEFINITIONS "-DWASM_ENABLE_AOT=1" APPEND)
-    endif ()
-
-    set(WAMR_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/../../..)
-    include(${WAMR_ROOT_DIR}/build-scripts/runtime_lib.cmake)
-endif()
-
-idf_component_register(SRCS ${WAMR_RUNTIME_LIB_SOURCE} ${PLATFORM_SHARED_SOURCE}
-  INCLUDE_DIRS ${IWASM_DIR}/include ${UTILS_SHARED_DIR} ${PLATFORM_SHARED_DIR} ${PLATFORM_SHARED_DIR}/../include
-  REQUIRES pthread lwip esp_timer
-)
+idf_component_register(SRCS ${srcs}
+                       INCLUDE_DIRS ${include_dirs}
+                       REQUIRES pthread lwip esp_timer
+                       KCONFIG ${CMAKE_CURRENT_LIST_DIR}/Kconfig)
 
+target_compile_options(${COMPONENT_LIB} PRIVATE "-Wno-format")
 
+if (CONFIG_IDF_TARGET_ARCH_RISCV)
+  target_compile_definitions(${COMPONENT_LIB} PUBLIC -DBUILD_TARGET_RISCV32_ILP32=1)
+elseif (CONFIG_IDF_TARGET_ARCH_XTENSA)
+  target_compile_definitions(${COMPONENT_LIB} PUBLIC -DBUILD_TARGET_XTENSA=1)
+endif ()
+
+if (CONFIG_WAMR_ENABLE_AOT)
+  target_compile_definitions(${COMPONENT_LIB} PUBLIC -DWASM_ENABLE_AOT=1)
+endif ()
+
+if (CONFIG_WAMR_ENABLE_INTERP)
+  target_compile_definitions(${COMPONENT_LIB} PUBLIC -DWASM_ENABLE_INTERP=1)
+endif ()

+ 77 - 0
build-scripts/esp-idf/wamr/Kconfig

@@ -0,0 +1,77 @@
+menu "WASM Micro Runtime"
+    choice WAMR_BUILD_TYPE
+        prompt "Build type"
+        default WAMR_BUILD_RELEASE
+
+        config WAMR_BUILD_RELEASE
+            bool "Release"
+
+        config WAMR_BUILD_DEBUG
+            bool "Debug"
+    endchoice
+
+    config WAMR_ENABLE_AOT
+        bool "AOT"
+        default y
+
+    menuconfig WAMR_ENABLE_INTERP
+        bool "Interpreter"
+        default y
+
+    if WAMR_ENABLE_INTERP
+
+       choice WAMR_INTERP_MODE
+            prompt "Interpreter mode"
+            default WAMR_INTERP_FAST
+
+            config WAMR_INTERP_CLASSIC
+                bool "Classic"
+
+            config WAMR_INTERP_FAST
+                bool "Fast"
+        endchoice
+
+        choice WAMR_INTERP_LOADER_MODE
+            prompt "Loader mode"
+            default WAMR_INTERP_LOADER_NORMAL
+
+            config WAMR_INTERP_LOADER_NORMAL
+                bool "Normal"
+
+            config WAMR_INTERP_LOADER_MINI
+                bool "Mini"
+        endchoice
+    endif
+
+    config WAMR_ENABLE_LIB_PTHREAD
+        bool "Lib pthread"
+        default y
+
+    config WAMR_ENABLE_LIBC_BUILTIN
+        bool "Libc builtin"
+        default y
+
+    config WAMR_ENABLE_LIBC_WASI
+        bool "Libc WASI"
+        default y
+
+    config WAMR_ENABLE_MEMORY_PROFILING
+        bool "Memory profiling"
+        default n
+
+    config WAMR_ENABLE_MULTI_MODULE
+        bool "Multi module"
+        default n
+
+    config WAMR_ENABLE_PERF_PROFILING
+        bool "Performance profiling"
+        default n
+
+    config WAMR_ENABLE_REF_TYPES
+        bool "Reference types"
+        default n
+
+    config WAMR_ENABLE_SHARED_MEMORY
+        bool "Shared memory"
+        default n
+endmenu

+ 88 - 0
core/shared/platform/esp-idf/espidf_clock.c

@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 Amazon Inc.  All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+ */
+
+#include "libc_errno.h"
+#include "platform_api_extension.h"
+
+#define NANOSECONDS_PER_SECOND 1000000000ULL
+
+static __wasi_errno_t
+wasi_clockid_to_clockid(__wasi_clockid_t in, clockid_t *out)
+{
+    switch (in) {
+        case __WASI_CLOCK_MONOTONIC:
+            *out = CLOCK_MONOTONIC;
+            return __WASI_ESUCCESS;
+        case __WASI_CLOCK_REALTIME:
+            *out = CLOCK_REALTIME;
+            return __WASI_ESUCCESS;
+        case __WASI_CLOCK_PROCESS_CPUTIME_ID:
+#if defined(CLOCK_PROCESS_CPUTIME_ID)
+            *out = CLOCK_PROCESS_CPUTIME_ID;
+            return __WASI_ESUCCESS;
+#else
+            return __WASI_ENOTSUP;
+#endif
+        case __WASI_CLOCK_THREAD_CPUTIME_ID:
+#if defined(CLOCK_THREAD_CPUTIME_ID)
+            *out = CLOCK_THREAD_CPUTIME_ID;
+            return __WASI_ESUCCESS;
+#else
+            return __WASI_ENOTSUP;
+#endif
+        default:
+            return __WASI_EINVAL;
+    }
+}
+
+static __wasi_timestamp_t
+timespec_to_nanoseconds(const struct timespec *ts)
+{
+    if (ts->tv_sec < 0)
+        return 0;
+    if ((__wasi_timestamp_t)ts->tv_sec >= UINT64_MAX / NANOSECONDS_PER_SECOND)
+        return UINT64_MAX;
+    return (__wasi_timestamp_t)ts->tv_sec * NANOSECONDS_PER_SECOND
+           + (__wasi_timestamp_t)ts->tv_nsec;
+}
+
+__wasi_errno_t
+os_clock_res_get(__wasi_clockid_t clock_id, __wasi_timestamp_t *resolution)
+{
+    clockid_t nclock_id;
+    __wasi_errno_t error = wasi_clockid_to_clockid(clock_id, &nclock_id);
+
+    if (error != __WASI_ESUCCESS)
+        return error;
+
+    struct timespec ts;
+    if (clock_getres(nclock_id, &ts) < 0)
+        return convert_errno(errno);
+
+    *resolution = timespec_to_nanoseconds(&ts);
+
+    return error;
+}
+
+__wasi_errno_t
+os_clock_time_get(__wasi_clockid_t clock_id, __wasi_timestamp_t precision,
+                  __wasi_timestamp_t *time)
+{
+    clockid_t nclock_id;
+    __wasi_errno_t error = wasi_clockid_to_clockid(clock_id, &nclock_id);
+
+    (void)precision;
+
+    if (error != __WASI_ESUCCESS)
+        return error;
+
+    struct timespec ts;
+    if (clock_gettime(nclock_id, &ts) < 0)
+        return convert_errno(errno);
+
+    *time = timespec_to_nanoseconds(&ts);
+
+    return error;
+}

+ 1014 - 0
core/shared/platform/esp-idf/espidf_file.c

@@ -0,0 +1,1014 @@
+/*
+ * Copyright (C) 2023 Intel Corporation. All rights reserved.
+ * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+ */
+
+#include "platform_api_extension.h"
+#include "libc_errno.h"
+#include <unistd.h>
+
+#if !defined(__APPLE__) && !defined(ESP_PLATFORM)
+#define CONFIG_HAS_PWRITEV 1
+#define CONFIG_HAS_PREADV 1
+#else
+#define CONFIG_HAS_PWRITEV 0
+#define CONFIG_HAS_PREADV 0
+#endif
+
+#if !defined(__APPLE__) && !defined(__FreeBSD__) && !defined(ESP_PLATFORM)
+#define CONFIG_HAS_FDATASYNC 1
+#else
+#define CONFIG_HAS_FDATASYNC 0
+#endif
+
+/*
+ * For NuttX, CONFIG_HAS_ISATTY is provided by its platform header.
+ * (platform_internal.h)
+ */
+#if !defined(CONFIG_HAS_D_INO)
+#if !defined(__NuttX__)
+#define CONFIG_HAS_D_INO 1
+#define CONFIG_HAS_ISATTY 1
+#else
+#define CONFIG_HAS_D_INO 0
+#endif
+#endif
+
+#if !defined(__APPLE__) && !defined(ESP_PLATFORM) && !defined(__COSMOPOLITAN__)
+#define CONFIG_HAS_POSIX_FALLOCATE 1
+#else
+#define CONFIG_HAS_POSIX_FALLOCATE 0
+#endif
+
+#if defined(O_DSYNC)
+#define CONFIG_HAS_O_DSYNC
+#endif
+
+// POSIX requires O_RSYNC to be defined, but Linux explicitly doesn't support
+// it.
+#if defined(O_RSYNC) && !defined(__linux__)
+#define CONFIG_HAS_O_RSYNC
+#endif
+
+#if defined(O_SYNC)
+#define CONFIG_HAS_O_SYNC
+#endif
+
+// Converts a POSIX timespec to a WASI timestamp.
+static __wasi_timestamp_t
+convert_timespec(const struct timespec *ts)
+{
+    if (ts->tv_sec < 0)
+        return 0;
+    if ((__wasi_timestamp_t)ts->tv_sec >= UINT64_MAX / 1000000000)
+        return UINT64_MAX;
+    return (__wasi_timestamp_t)ts->tv_sec * 1000000000
+           + (__wasi_timestamp_t)ts->tv_nsec;
+}
+
+// Converts a POSIX stat structure to a WASI filestat structure
+static void
+convert_stat(os_file_handle handle, const struct stat *in,
+             __wasi_filestat_t *out)
+{
+    out->st_dev = in->st_dev;
+    out->st_ino = in->st_ino;
+    out->st_nlink = (__wasi_linkcount_t)in->st_nlink;
+    out->st_size = (__wasi_filesize_t)in->st_size;
+#ifdef __APPLE__
+    out->st_atim = convert_timespec(&in->st_atimespec);
+    out->st_mtim = convert_timespec(&in->st_mtimespec);
+    out->st_ctim = convert_timespec(&in->st_ctimespec);
+#else
+    out->st_atim = convert_timespec(&in->st_atim);
+    out->st_mtim = convert_timespec(&in->st_mtim);
+    out->st_ctim = convert_timespec(&in->st_ctim);
+#endif
+
+    // Convert the file type. In the case of sockets there is no way we
+    // can easily determine the exact socket type.
+    if (S_ISBLK(in->st_mode)) {
+        out->st_filetype = __WASI_FILETYPE_BLOCK_DEVICE;
+    }
+    else if (S_ISCHR(in->st_mode)) {
+        out->st_filetype = __WASI_FILETYPE_CHARACTER_DEVICE;
+    }
+    else if (S_ISDIR(in->st_mode)) {
+        out->st_filetype = __WASI_FILETYPE_DIRECTORY;
+    }
+    else if (S_ISFIFO(in->st_mode)) {
+        out->st_filetype = __WASI_FILETYPE_SOCKET_STREAM;
+    }
+    else if (S_ISLNK(in->st_mode)) {
+        out->st_filetype = __WASI_FILETYPE_SYMBOLIC_LINK;
+    }
+    else if (S_ISREG(in->st_mode)) {
+        out->st_filetype = __WASI_FILETYPE_REGULAR_FILE;
+    }
+    else if (S_ISSOCK(in->st_mode)) {
+        int socktype;
+        socklen_t socktypelen = sizeof(socktype);
+
+        if (getsockopt(handle, SOL_SOCKET, SO_TYPE, &socktype, &socktypelen)
+            < 0) {
+            out->st_filetype = __WASI_FILETYPE_UNKNOWN;
+            return;
+        }
+
+        switch (socktype) {
+            case SOCK_DGRAM:
+                out->st_filetype = __WASI_FILETYPE_SOCKET_DGRAM;
+                break;
+            case SOCK_STREAM:
+                out->st_filetype = __WASI_FILETYPE_SOCKET_STREAM;
+                break;
+            default:
+                out->st_filetype = __WASI_FILETYPE_UNKNOWN;
+                return;
+        }
+    }
+    else {
+        out->st_filetype = __WASI_FILETYPE_UNKNOWN;
+    }
+}
+
+static void
+convert_timestamp(__wasi_timestamp_t in, struct timespec *out)
+{
+    // Store sub-second remainder.
+#if defined(__SYSCALL_SLONG_TYPE)
+    out->tv_nsec = (__SYSCALL_SLONG_TYPE)(in % 1000000000);
+#else
+    out->tv_nsec = (long)(in % 1000000000);
+#endif
+    in /= 1000000000;
+
+    // Clamp to the maximum in case it would overflow our system's time_t.
+    out->tv_sec = (time_t)in < BH_TIME_T_MAX ? (time_t)in : BH_TIME_T_MAX;
+}
+
+// Converts the provided timestamps and flags to a set of arguments for
+// futimens() and utimensat().
+static void
+convert_utimens_arguments(__wasi_timestamp_t st_atim,
+                          __wasi_timestamp_t st_mtim,
+                          __wasi_fstflags_t fstflags, struct timespec *ts)
+{
+    if ((fstflags & __WASI_FILESTAT_SET_ATIM_NOW) != 0) {
+        ts[0].tv_nsec = UTIME_NOW;
+    }
+    else if ((fstflags & __WASI_FILESTAT_SET_ATIM) != 0) {
+        convert_timestamp(st_atim, &ts[0]);
+    }
+    else {
+        ts[0].tv_nsec = UTIME_OMIT;
+    }
+
+    if ((fstflags & __WASI_FILESTAT_SET_MTIM_NOW) != 0) {
+        ts[1].tv_nsec = UTIME_NOW;
+    }
+    else if ((fstflags & __WASI_FILESTAT_SET_MTIM) != 0) {
+        convert_timestamp(st_mtim, &ts[1]);
+    }
+    else {
+        ts[1].tv_nsec = UTIME_OMIT;
+    }
+}
+
+__wasi_errno_t
+os_fstat(os_file_handle handle, struct __wasi_filestat_t *buf)
+{
+    struct stat stat_buf;
+    int ret = fstat(handle, &stat_buf);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    convert_stat(handle, &stat_buf, buf);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_fstatat(os_file_handle handle, const char *path,
+           struct __wasi_filestat_t *buf, __wasi_lookupflags_t lookup_flags)
+{
+    struct stat stat_buf;
+    int ret = fstatat(handle, path, &stat_buf,
+                      (lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW)
+                          ? AT_SYMLINK_FOLLOW
+                          : AT_SYMLINK_NOFOLLOW);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    convert_stat(handle, &stat_buf, buf);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_file_get_fdflags(os_file_handle handle, __wasi_fdflags_t *flags)
+{
+    int ret = fcntl(handle, F_GETFL);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    *flags = 0;
+
+    if ((ret & O_APPEND) != 0)
+        *flags |= __WASI_FDFLAG_APPEND;
+#ifdef CONFIG_HAS_O_DSYNC
+    if ((ret & O_DSYNC) != 0)
+        *flags |= __WASI_FDFLAG_DSYNC;
+#endif
+    if ((ret & O_NONBLOCK) != 0)
+        *flags |= __WASI_FDFLAG_NONBLOCK;
+#ifdef CONFIG_HAS_O_RSYNC
+    if ((ret & O_RSYNC) != 0)
+        *flags |= __WASI_FDFLAG_RSYNC;
+#endif
+#ifdef CONFIG_HAS_O_SYNC
+    if ((ret & O_SYNC) != 0)
+        *flags |= __WASI_FDFLAG_SYNC;
+#endif
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_file_set_fdflags(os_file_handle handle, __wasi_fdflags_t flags)
+{
+    int fcntl_flags = 0;
+
+    if ((flags & __WASI_FDFLAG_APPEND) != 0)
+        fcntl_flags |= O_APPEND;
+    if ((flags & __WASI_FDFLAG_DSYNC) != 0)
+#ifdef CONFIG_HAS_O_DSYNC
+        fcntl_flags |= O_DSYNC;
+#else
+        return __WASI_ENOTSUP;
+#endif
+    if ((flags & __WASI_FDFLAG_NONBLOCK) != 0)
+        fcntl_flags |= O_NONBLOCK;
+    if ((flags & __WASI_FDFLAG_RSYNC) != 0)
+#ifdef CONFIG_HAS_O_RSYNC
+        fcntl_flags |= O_RSYNC;
+#else
+        return __WASI_ENOTSUP;
+#endif
+    if ((flags & __WASI_FDFLAG_SYNC) != 0)
+#ifdef CONFIG_HAS_O_SYNC
+        fcntl_flags |= O_SYNC;
+#else
+        return __WASI_ENOTSUP;
+#endif
+
+    int ret = fcntl(handle, F_SETFL, fcntl_flags);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_fdatasync(os_file_handle handle)
+{
+#if CONFIG_HAS_FDATASYNC
+    int ret = fdatasync(handle);
+#else
+    int ret = fsync(handle);
+#endif
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_fsync(os_file_handle handle)
+{
+    int ret = fsync(handle);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_open_preopendir(const char *path, os_file_handle *out)
+{
+
+    int fd = open(path, O_RDONLY | O_DIRECTORY, 0);
+
+    if (fd < 0)
+        return convert_errno(errno);
+
+    *out = fd;
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_openat(os_file_handle handle, const char *path, __wasi_oflags_t oflags,
+          __wasi_fdflags_t fs_flags, __wasi_lookupflags_t lookup_flags,
+          wasi_libc_file_access_mode read_write_mode, os_file_handle *out)
+{
+    int open_flags = 0;
+
+    // Convert open flags.
+    if ((oflags & __WASI_O_CREAT) != 0) {
+        open_flags |= O_CREAT;
+    }
+    if ((oflags & __WASI_O_DIRECTORY) != 0)
+        open_flags |= O_DIRECTORY;
+    if ((oflags & __WASI_O_EXCL) != 0)
+        open_flags |= O_EXCL;
+    if ((oflags & __WASI_O_TRUNC) != 0) {
+        open_flags |= O_TRUNC;
+    }
+
+    // Convert file descriptor flags.
+    if ((fs_flags & __WASI_FDFLAG_APPEND) != 0)
+        open_flags |= O_APPEND;
+    if ((fs_flags & __WASI_FDFLAG_DSYNC) != 0) {
+#ifdef CONFIG_HAS_O_DSYNC
+        open_flags |= O_DSYNC;
+#else
+        return __WASI_ENOTSUP;
+#endif
+    }
+    if ((fs_flags & __WASI_FDFLAG_NONBLOCK) != 0)
+        open_flags |= O_NONBLOCK;
+    if ((fs_flags & __WASI_FDFLAG_RSYNC) != 0) {
+#ifdef CONFIG_HAS_O_RSYNC
+        open_flags |= O_RSYNC;
+#else
+        return __WASI_ENOTSUP;
+#endif
+    }
+    if ((fs_flags & __WASI_FDFLAG_SYNC) != 0) {
+#ifdef CONFIG_HAS_O_SYNC
+        open_flags |= O_SYNC;
+#else
+        return __WASI_ENOTSUP;
+#endif
+    }
+
+    if ((lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW) == 0) {
+        open_flags |= O_NOFOLLOW;
+    }
+
+    switch (read_write_mode) {
+        case WASI_LIBC_ACCESS_MODE_READ_WRITE:
+            open_flags |= O_RDWR;
+            break;
+        case WASI_LIBC_ACCESS_MODE_READ_ONLY:
+            open_flags |= O_RDONLY;
+            break;
+        case WASI_LIBC_ACCESS_MODE_WRITE_ONLY:
+            open_flags |= O_WRONLY;
+            break;
+        default:
+            return __WASI_EINVAL;
+    }
+
+    int fd = openat(handle, path, open_flags, 0666);
+
+    if (fd < 0) {
+        int openat_errno = errno;
+        // Linux returns ENXIO instead of EOPNOTSUPP when opening a socket.
+        if (openat_errno == ENXIO) {
+            struct stat sb;
+            int ret = fstatat(handle, path, &sb,
+                              (lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW)
+                                  ? 0
+                                  : AT_SYMLINK_NOFOLLOW);
+            return ret == 0 && S_ISSOCK(sb.st_mode) ? __WASI_ENOTSUP
+                                                    : __WASI_ENXIO;
+        }
+        // Linux returns ENOTDIR instead of ELOOP when using
+        // O_NOFOLLOW|O_DIRECTORY on a symlink.
+        if (openat_errno == ENOTDIR
+            && (open_flags & (O_NOFOLLOW | O_DIRECTORY)) != 0) {
+            struct stat sb;
+            int ret = fstatat(handle, path, &sb, AT_SYMLINK_NOFOLLOW);
+            if (S_ISLNK(sb.st_mode)) {
+                return __WASI_ELOOP;
+            }
+            (void)ret;
+        }
+        // FreeBSD returns EMLINK instead of ELOOP when using O_NOFOLLOW on
+        // a symlink.
+        if ((lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW) == 0
+            && openat_errno == EMLINK)
+            return __WASI_ELOOP;
+
+        return convert_errno(openat_errno);
+    }
+
+    *out = fd;
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_file_get_access_mode(os_file_handle handle,
+                        wasi_libc_file_access_mode *access_mode)
+{
+    int ret = fcntl(handle, F_GETFL, 0);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    switch (ret & O_ACCMODE) {
+        case O_RDONLY:
+            *access_mode = WASI_LIBC_ACCESS_MODE_READ_ONLY;
+            break;
+        case O_WRONLY:
+            *access_mode = WASI_LIBC_ACCESS_MODE_WRITE_ONLY;
+            break;
+        case O_RDWR:
+            *access_mode = WASI_LIBC_ACCESS_MODE_READ_WRITE;
+            break;
+        default:
+            return __WASI_EINVAL;
+    }
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_close(os_file_handle handle, bool is_stdio)
+{
+    if (is_stdio)
+        return __WASI_ESUCCESS;
+
+    int ret = close(handle);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_preadv(os_file_handle handle, const struct __wasi_iovec_t *iov, int iovcnt,
+          __wasi_filesize_t offset, size_t *nread)
+{
+#if CONFIG_HAS_PREADV
+    ssize_t len =
+        preadv(handle, (const struct iovec *)iov, (int)iovcnt, (off_t)offset);
+    if (len < 0)
+        return convert_errno(errno);
+
+    *nread = (size_t)len;
+    return __WASI_ESUCCESS;
+#else
+    if (iovcnt == 1) {
+        ssize_t len = pread(handle, iov->buf, iov->buf_len, offset);
+
+        if (len < 0)
+            return convert_errno(errno);
+
+        *nread = len;
+        return __WASI_ESUCCESS;
+    }
+
+    // Allocate a single buffer to fit all data.
+    size_t totalsize = 0;
+    for (int i = 0; i < iovcnt; ++i)
+        totalsize += iov[i].buf_len;
+
+    char *buf = BH_MALLOC(totalsize);
+
+    if (buf == NULL) {
+        return __WASI_ENOMEM;
+    }
+
+    // Perform a single read operation.
+    ssize_t len = pread(handle, buf, totalsize, offset);
+
+    if (len < 0) {
+        BH_FREE(buf);
+        return convert_errno(errno);
+    }
+
+    // Copy data back to vectors.
+    size_t bufoff = 0;
+    for (int i = 0; i < iovcnt; ++i) {
+        if (bufoff + iov[i].buf_len < (size_t)len) {
+            memcpy(iov[i].buf, buf + bufoff, iov[i].buf_len);
+            bufoff += iov[i].buf_len;
+        }
+        else {
+            memcpy(iov[i].buf, buf + bufoff, len - bufoff);
+            break;
+        }
+    }
+    BH_FREE(buf);
+    *nread = len;
+
+    return __WASI_ESUCCESS;
+#endif
+}
+
+__wasi_errno_t
+os_pwritev(os_file_handle handle, const struct __wasi_ciovec_t *iov, int iovcnt,
+           __wasi_filesize_t offset, size_t *nwritten)
+{
+    if (iovcnt == 0)
+        return __WASI_EINVAL;
+
+    ssize_t len = 0;
+#if CONFIG_HAS_PWRITEV
+    len =
+        pwritev(handle, (const struct iovec *)iov, (int)iovcnt, (off_t)offset);
+#else
+    if (iovcnt == 1) {
+        len = pwrite(handle, iov->buf, iov->buf_len, offset);
+    }
+    else {
+        // Allocate a single buffer to fit all data.
+        size_t totalsize = 0;
+        for (int i = 0; i < iovcnt; ++i)
+            totalsize += iov[i].buf_len;
+        char *buf = BH_MALLOC(totalsize);
+        if (buf == NULL) {
+            return __WASI_ENOMEM;
+        }
+        size_t bufoff = 0;
+        for (int i = 0; i < iovcnt; ++i) {
+            memcpy(buf + bufoff, iov[i].buf, iov[i].buf_len);
+            bufoff += iov[i].buf_len;
+        }
+
+        // Perform a single write operation.
+        len = pwrite(handle, buf, totalsize, offset);
+        BH_FREE(buf);
+    }
+#endif
+    if (len < 0)
+        return convert_errno(errno);
+
+    *nwritten = (size_t)len;
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_readv(os_file_handle handle, const struct __wasi_iovec_t *iov, int iovcnt,
+         size_t *nread)
+{
+    ssize_t len = readv(handle, (const struct iovec *)iov, (int)iovcnt);
+
+    if (len < 0)
+        return convert_errno(errno);
+
+    *nread = (size_t)len;
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_writev(os_file_handle handle, const struct __wasi_ciovec_t *iov, int iovcnt,
+          size_t *nwritten)
+{
+    ssize_t len = writev(handle, (const struct iovec *)iov, (int)iovcnt);
+
+    if (len < 0)
+        return convert_errno(errno);
+
+    *nwritten = (size_t)len;
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_fallocate(os_file_handle handle, __wasi_filesize_t offset,
+             __wasi_filesize_t length)
+{
+#if CONFIG_HAS_POSIX_FALLOCATE
+    int ret = posix_fallocate(handle, (off_t)offset, (off_t)length);
+#else
+    // At least ensure that the file is grown to the right size.
+    // TODO(ed): See if this can somehow be implemented without any race
+    // conditions. We may end up shrinking the file right now.
+    struct stat sb;
+    int ret = fstat(handle, &sb);
+    off_t newsize = (off_t)(offset + length);
+
+    if (ret == 0 && sb.st_size < newsize)
+        ret = ftruncate(handle, newsize);
+#endif
+
+    if (ret != 0)
+        return convert_errno(ret);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_ftruncate(os_file_handle handle, __wasi_filesize_t size)
+{
+    int ret = ftruncate(handle, (off_t)size);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_futimens(os_file_handle handle, __wasi_timestamp_t access_time,
+            __wasi_timestamp_t modification_time, __wasi_fstflags_t fstflags)
+{
+    struct timespec ts[2];
+    convert_utimens_arguments(access_time, modification_time, fstflags, ts);
+
+    int ret = futimens(handle, ts);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_utimensat(os_file_handle handle, const char *path,
+             __wasi_timestamp_t access_time,
+             __wasi_timestamp_t modification_time, __wasi_fstflags_t fstflags,
+             __wasi_lookupflags_t lookup_flags)
+{
+    struct timespec ts[2];
+    convert_utimens_arguments(access_time, modification_time, fstflags, ts);
+
+    int ret = utimensat(handle, path, ts,
+                        (lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW)
+                            ? 0
+                            : AT_SYMLINK_NOFOLLOW);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_readlinkat(os_file_handle handle, const char *path, char *buf,
+              size_t bufsize, size_t *nread)
+{
+    // Linux requires that the buffer size is positive. whereas POSIX does
+    // not. Use a fake buffer to store the results if the size is zero.
+    char fakebuf[1];
+    ssize_t len = readlinkat(handle, path, bufsize == 0 ? fakebuf : buf,
+                             bufsize == 0 ? sizeof(fakebuf) : bufsize);
+
+    if (len < 0)
+        return convert_errno(errno);
+
+    *nread = (size_t)len < bufsize ? (size_t)len : bufsize;
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_linkat(os_file_handle from_handle, const char *from_path,
+          os_file_handle to_handle, const char *to_path,
+          __wasi_lookupflags_t lookup_flags)
+{
+    int ret = linkat(
+        from_handle, from_path, to_handle, to_path,
+        (lookup_flags & __WASI_LOOKUP_SYMLINK_FOLLOW) ? AT_SYMLINK_FOLLOW : 0);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_symlinkat(const char *old_path, os_file_handle handle, const char *new_path)
+{
+    int ret = symlinkat(old_path, handle, new_path);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_mkdirat(os_file_handle handle, const char *path)
+{
+    int ret = mkdirat(handle, path, 0777);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_renameat(os_file_handle old_handle, const char *old_path,
+            os_file_handle new_handle, const char *new_path)
+{
+
+    int ret = renameat(old_handle, old_path, new_handle, new_path);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_unlinkat(os_file_handle handle, const char *path, bool is_dir)
+{
+    int ret = unlinkat(handle, path, is_dir ? AT_REMOVEDIR : 0);
+
+#ifndef __linux__
+    if (ret < 0) {
+        // Non-Linux implementations may return EPERM when attempting to remove
+        // a directory without REMOVEDIR. While that's what POSIX specifies,
+        // it's less useful. Adjust this to EISDIR. It doesn't matter that this
+        // is not atomic with the unlinkat, because if the file is removed and a
+        // directory is created before fstatat sees it, we're racing with that
+        // change anyway and unlinkat could have legitimately seen the directory
+        // if the race had turned out differently.
+        if (errno == EPERM) {
+            struct stat statbuf;
+            if (fstatat(handle, path, &statbuf, AT_SYMLINK_NOFOLLOW) == 0
+                && S_ISDIR(statbuf.st_mode)) {
+                errno = EISDIR;
+            }
+        }
+        // POSIX permits either EEXIST or ENOTEMPTY when the directory is not
+        // empty. Map it to ENOTEMPTY.
+        else if (errno == EEXIST) {
+            errno = ENOTEMPTY;
+        }
+
+        return convert_errno(errno);
+    }
+#endif
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_lseek(os_file_handle handle, __wasi_filedelta_t offset,
+         __wasi_whence_t whence, __wasi_filesize_t *new_offset)
+{
+    int nwhence;
+
+    switch (whence) {
+        case __WASI_WHENCE_CUR:
+            nwhence = SEEK_CUR;
+            break;
+        case __WASI_WHENCE_END:
+            nwhence = SEEK_END;
+            break;
+        case __WASI_WHENCE_SET:
+            nwhence = SEEK_SET;
+            break;
+        default:
+            return __WASI_EINVAL;
+    }
+
+    off_t ret = lseek(handle, offset, nwhence);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    *new_offset = (__wasi_filesize_t)ret;
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_fadvise(os_file_handle handle, __wasi_filesize_t offset,
+           __wasi_filesize_t length, __wasi_advice_t advice)
+{
+#ifdef POSIX_FADV_NORMAL
+    int nadvice;
+    switch (advice) {
+        case __WASI_ADVICE_DONTNEED:
+            nadvice = POSIX_FADV_DONTNEED;
+            break;
+        case __WASI_ADVICE_NOREUSE:
+            nadvice = POSIX_FADV_NOREUSE;
+            break;
+        case __WASI_ADVICE_NORMAL:
+            nadvice = POSIX_FADV_NORMAL;
+            break;
+        case __WASI_ADVICE_RANDOM:
+            nadvice = POSIX_FADV_RANDOM;
+            break;
+        case __WASI_ADVICE_SEQUENTIAL:
+            nadvice = POSIX_FADV_SEQUENTIAL;
+            break;
+        case __WASI_ADVICE_WILLNEED:
+            nadvice = POSIX_FADV_WILLNEED;
+            break;
+        default:
+            return __WASI_EINVAL;
+    }
+
+    int ret = posix_fadvise(handle, (off_t)offset, (off_t)length, nadvice);
+
+    if (ret != 0)
+        return convert_errno(ret);
+
+    return __WASI_ESUCCESS;
+#else
+    // Advisory information can be safely ignored if not supported
+    switch (advice) {
+        case __WASI_ADVICE_DONTNEED:
+        case __WASI_ADVICE_NOREUSE:
+        case __WASI_ADVICE_NORMAL:
+        case __WASI_ADVICE_RANDOM:
+        case __WASI_ADVICE_SEQUENTIAL:
+        case __WASI_ADVICE_WILLNEED:
+            return __WASI_ESUCCESS;
+        default:
+            return __WASI_EINVAL;
+    }
+#endif
+}
+
+__wasi_errno_t
+os_isatty(os_file_handle handle)
+{
+#if CONFIG_HAS_ISATTY
+    int ret = isatty(handle);
+
+    if (ret == 1)
+        return __WASI_ESUCCESS;
+
+    return __WASI_ENOTTY;
+#else
+    return __WASI_ENOTSUP;
+#endif
+}
+
+os_file_handle
+os_convert_stdin_handle(os_raw_file_handle raw_stdin)
+{
+#ifndef STDIN_FILENO
+#define STDIN_FILENO 0
+#endif
+    return raw_stdin >= 0 ? raw_stdin : STDIN_FILENO;
+}
+
+os_file_handle
+os_convert_stdout_handle(os_raw_file_handle raw_stdout)
+{
+#ifndef STDOUT_FILENO
+#define STDOUT_FILENO 1
+#endif
+    return raw_stdout >= 0 ? raw_stdout : STDOUT_FILENO;
+}
+
+os_file_handle
+os_convert_stderr_handle(os_raw_file_handle raw_stderr)
+{
+#ifndef STDERR_FILENO
+#define STDERR_FILENO 2
+#endif
+    return raw_stderr >= 0 ? raw_stderr : STDERR_FILENO;
+}
+
+__wasi_errno_t
+os_fdopendir(os_file_handle handle, os_dir_stream *dir_stream)
+{
+    *dir_stream = fdopendir(handle);
+
+    if (*dir_stream == NULL)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_rewinddir(os_dir_stream dir_stream)
+{
+    rewinddir(dir_stream);
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_seekdir(os_dir_stream dir_stream, __wasi_dircookie_t position)
+{
+    seekdir(dir_stream, (long)position);
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_readdir(os_dir_stream dir_stream, __wasi_dirent_t *entry,
+           const char **d_name)
+{
+    errno = 0;
+
+    struct dirent *dent = readdir(dir_stream);
+
+    if (dent == NULL) {
+        *d_name = NULL;
+        if (errno != 0) {
+            return convert_errno(errno);
+        }
+        else {
+            return 0;
+        }
+    }
+
+    long offset = (__wasi_dircookie_t)telldir(dir_stream);
+
+    size_t namlen = strlen(dent->d_name);
+
+    *d_name = dent->d_name;
+    entry->d_next = offset;
+    entry->d_namlen = (__wasi_dirnamlen_t)namlen;
+#if CONFIG_HAS_D_INO
+    entry->d_ino = dent->d_ino;
+#else
+    entry->d_ino = 0;
+#endif
+
+    switch (dent->d_type) {
+        case DT_BLK:
+            entry->d_type = __WASI_FILETYPE_BLOCK_DEVICE;
+            break;
+        case DT_CHR:
+            entry->d_type = __WASI_FILETYPE_CHARACTER_DEVICE;
+            break;
+        case DT_DIR:
+            entry->d_type = __WASI_FILETYPE_DIRECTORY;
+            break;
+        case DT_FIFO:
+            entry->d_type = __WASI_FILETYPE_SOCKET_STREAM;
+            break;
+        case DT_LNK:
+            entry->d_type = __WASI_FILETYPE_SYMBOLIC_LINK;
+            break;
+        case DT_REG:
+            entry->d_type = __WASI_FILETYPE_REGULAR_FILE;
+            break;
+#ifdef DT_SOCK
+        case DT_SOCK:
+            // Technically not correct, but good enough.
+            entry->d_type = __WASI_FILETYPE_SOCKET_STREAM;
+            break;
+#endif
+        default:
+            entry->d_type = __WASI_FILETYPE_UNKNOWN;
+            break;
+    }
+
+    return __WASI_ESUCCESS;
+}
+
+__wasi_errno_t
+os_closedir(os_dir_stream dir_stream)
+{
+    int ret = closedir(dir_stream);
+
+    if (ret < 0)
+        return convert_errno(errno);
+
+    return __WASI_ESUCCESS;
+}
+
+os_dir_stream
+os_get_invalid_dir_stream()
+{
+    return NULL;
+}
+
+bool
+os_is_dir_stream_valid(os_dir_stream *dir_stream)
+{
+    assert(dir_stream != NULL);
+
+    return *dir_stream != NULL;
+}
+
+bool
+os_is_handle_valid(os_file_handle *handle)
+{
+    assert(handle != NULL);
+
+    return *handle > -1;
+}
+
+char *
+os_realpath(const char *path, char *resolved_path)
+{
+    return realpath(path, resolved_path);
+}

+ 38 - 0
core/shared/platform/esp-idf/espidf_platform.c

@@ -3,6 +3,7 @@
  * SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  */
 
+#include "sdkconfig.h"
 #include "platform_api_vmcore.h"
 #include "platform_api_extension.h"
 
@@ -12,6 +13,15 @@
 #define FUTIMENS_TIMESPEC_POINTER 1
 #endif
 
+#if CONFIG_LITTLEFS_OPEN_DIR && CONFIG_LITTLEFS_FCNTL_GET_PATH
+#define OPENAT_SUPPORT 1
+
+#undef F_GETPATH
+#define F_GETPATH CONFIG_LITTLEFS_FCNTL_F_GETPATH_VALUE
+
+#define DIR_PATH_LEN (CONFIG_LITTLEFS_OBJ_NAME_LEN + 1)
+#endif
+
 int
 bh_platform_init()
 {
@@ -183,12 +193,40 @@ writev(int fildes, const struct iovec *iov, int iovcnt)
     return ntotal;
 }
 
+#if OPENAT_SUPPORT
+int
+openat(int fd, const char *pathname, int flags, ...)
+{
+    int new_fd;
+    int ret;
+    char dir_path[DIR_PATH_LEN];
+    char *full_path;
+
+    ret = fcntl(fd, F_GETPATH, dir_path);
+    if (ret != 0) {
+        errno = -EINVAL;
+        return -1;
+    }
+
+    ret = asprintf(&full_path, "%s/%s", dir_path, pathname);
+    if (ret < 0) {
+        errno = ENOMEM;
+        return -1;
+    }
+
+    new_fd = open(full_path, flags);
+    free(full_path);
+
+    return new_fd;
+}
+#else
 int
 openat(int fd, const char *path, int oflags, ...)
 {
     errno = ENOSYS;
     return -1;
 }
+#endif
 
 int
 fstatat(int fd, const char *path, struct stat *buf, int flag)

+ 831 - 35
core/shared/platform/esp-idf/espidf_socket.c

@@ -8,19 +8,44 @@
 #include "libc_errno.h"
 
 #include <arpa/inet.h>
+#include <netdb.h>
+#include <netinet/tcp.h>
+#include <netinet/in.h>
 
-static void
-textual_addr_to_sockaddr(const char *textual, int port, struct sockaddr_in *out)
+static bool
+textual_addr_to_sockaddr(const char *textual, int port, struct sockaddr *out,
+                         socklen_t *out_len)
 {
+    struct sockaddr_in *v4;
+#ifdef IPPROTO_IPV6
+    struct sockaddr_in6 *v6;
+#endif
+
     assert(textual);
 
-    out->sin_family = AF_INET;
-    out->sin_port = htons(port);
-    out->sin_addr.s_addr = inet_addr(textual);
+    v4 = (struct sockaddr_in *)out;
+    if (inet_pton(AF_INET, textual, &v4->sin_addr.s_addr) == 1) {
+        v4->sin_family = AF_INET;
+        v4->sin_port = htons(port);
+        *out_len = sizeof(struct sockaddr_in);
+        return true;
+    }
+
+#ifdef IPPROTO_IPV6
+    v6 = (struct sockaddr_in6 *)out;
+    if (inet_pton(AF_INET6, textual, &v6->sin6_addr.s6_addr) == 1) {
+        v6->sin6_family = AF_INET6;
+        v6->sin6_port = htons(port);
+        *out_len = sizeof(struct sockaddr_in6);
+        return true;
+    }
+#endif
+
+    return false;
 }
 
 static int
-sockaddr_to_bh_sockaddr(const struct sockaddr *sockaddr, socklen_t socklen,
+sockaddr_to_bh_sockaddr(const struct sockaddr *sockaddr,
                         bh_sockaddr_t *bh_sockaddr)
 {
     switch (sockaddr->sa_family) {
@@ -28,31 +53,82 @@ sockaddr_to_bh_sockaddr(const struct sockaddr *sockaddr, socklen_t socklen,
         {
             struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr;
 
-            assert(socklen >= sizeof(struct sockaddr_in));
-
             bh_sockaddr->port = ntohs(addr->sin_port);
             bh_sockaddr->addr_buffer.ipv4 = ntohl(addr->sin_addr.s_addr);
             bh_sockaddr->is_ipv4 = true;
             return BHT_OK;
         }
+#ifdef IPPROTO_IPV6
+        case AF_INET6:
+        {
+            struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr;
+            size_t i;
+
+            bh_sockaddr->port = ntohs(addr->sin6_port);
+
+            for (i = 0; i < sizeof(bh_sockaddr->addr_buffer.ipv6)
+                                / sizeof(bh_sockaddr->addr_buffer.ipv6[0]);
+                 i++) {
+                uint16 part_addr = addr->sin6_addr.s6_addr[i * 2]
+                                   | (addr->sin6_addr.s6_addr[i * 2 + 1] << 8);
+                bh_sockaddr->addr_buffer.ipv6[i] = ntohs(part_addr);
+            }
+
+            bh_sockaddr->is_ipv4 = false;
+            return BHT_OK;
+        }
+#endif
         default:
             errno = EAFNOSUPPORT;
             return BHT_ERROR;
     }
 }
 
+static void
+bh_sockaddr_to_sockaddr(const bh_sockaddr_t *bh_sockaddr,
+                        struct sockaddr_storage *sockaddr, socklen_t *socklen)
+{
+    if (bh_sockaddr->is_ipv4) {
+        struct sockaddr_in *addr = (struct sockaddr_in *)sockaddr;
+        addr->sin_port = htons(bh_sockaddr->port);
+        addr->sin_family = AF_INET;
+        addr->sin_addr.s_addr = htonl(bh_sockaddr->addr_buffer.ipv4);
+        *socklen = sizeof(*addr);
+    }
+#ifdef IPPROTO_IPV6
+    else {
+        struct sockaddr_in6 *addr = (struct sockaddr_in6 *)sockaddr;
+        size_t i;
+        addr->sin6_port = htons(bh_sockaddr->port);
+        addr->sin6_family = AF_INET6;
+
+        for (i = 0; i < sizeof(bh_sockaddr->addr_buffer.ipv6)
+                            / sizeof(bh_sockaddr->addr_buffer.ipv6[0]);
+             i++) {
+            uint16 part_addr = htons(bh_sockaddr->addr_buffer.ipv6[i]);
+            addr->sin6_addr.s6_addr[i * 2] = 0xff & part_addr;
+            addr->sin6_addr.s6_addr[i * 2 + 1] = (0xff00 & part_addr) >> 8;
+        }
+
+        *socklen = sizeof(*addr);
+    }
+#endif
+}
+
 int
 os_socket_create(bh_socket_t *sock, bool is_ipv4, bool is_tcp)
 {
+    int af = is_ipv4 ? AF_INET : AF_INET6;
+
     if (!sock) {
         return BHT_ERROR;
     }
 
     if (is_tcp) {
-        *sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
+        *sock = socket(af, SOCK_STREAM, IPPROTO_TCP);
     }
     else {
-        *sock = socket(AF_INET, SOCK_DGRAM, 0);
+        *sock = socket(af, SOCK_DGRAM, 0);
     }
 
     return (*sock == -1) ? BHT_ERROR : BHT_OK;
@@ -61,28 +137,47 @@ os_socket_create(bh_socket_t *sock, bool is_ipv4, bool is_tcp)
 int
 os_socket_bind(bh_socket_t socket, const char *host, int *port)
 {
-    struct sockaddr_in addr;
+    struct sockaddr_storage addr = { 0 };
+    struct linger ling;
     socklen_t socklen;
     int ret;
 
     assert(host);
     assert(port);
 
-    addr.sin_addr.s_addr = inet_addr(host);
-    addr.sin_port = htons(*port);
-    addr.sin_family = AF_INET;
+    ling.l_onoff = 1;
+    ling.l_linger = 0;
+
+    if (!textual_addr_to_sockaddr(host, *port, (struct sockaddr *)&addr,
+                                  &socklen)) {
+        goto fail;
+    }
+
+    ret = setsockopt(socket, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));
+    if (ret < 0) {
+        goto fail;
+    }
 
-    ret = bind(socket, (struct sockaddr *)&addr, sizeof(addr));
+    ret = bind(socket, (struct sockaddr *)&addr, socklen);
     if (ret < 0) {
         goto fail;
     }
 
     socklen = sizeof(addr);
-    if (getsockname(socket, (struct sockaddr *)&addr, &socklen) == -1) {
+    if (getsockname(socket, (void *)&addr, &socklen) == -1) {
         goto fail;
     }
 
-    *port = ntohs(addr.sin_port);
+    if (addr.ss_family == AF_INET) {
+        *port = ntohs(((struct sockaddr_in *)&addr)->sin_port);
+    }
+    else {
+#ifdef IPPROTO_IPV6
+        *port = ntohs(((struct sockaddr_in6 *)&addr)->sin6_port);
+#else
+        goto fail;
+#endif
+    }
 
     return BHT_OK;
 
@@ -120,10 +215,7 @@ int
 os_socket_accept(bh_socket_t server_sock, bh_socket_t *sock, void *addr,
                  unsigned int *addrlen)
 {
-    struct sockaddr addr_tmp;
-    socklen_t len = sizeof(struct sockaddr);
-
-    *sock = accept(server_sock, (struct sockaddr *)&addr_tmp, &len);
+    *sock = accept(server_sock, addr, (socklen_t *)addrlen);
 
     if (*sock < 0) {
         return BHT_ERROR;
@@ -135,11 +227,14 @@ os_socket_accept(bh_socket_t server_sock, bh_socket_t *sock, void *addr,
 int
 os_socket_connect(bh_socket_t socket, const char *addr, int port)
 {
-    struct sockaddr_in addr_in = { 0 };
-    socklen_t addr_len = sizeof(struct sockaddr_in);
+    struct sockaddr_storage addr_in = { 0 };
+    socklen_t addr_len;
     int ret = 0;
 
-    textual_addr_to_sockaddr(addr, port, &addr_in);
+    if (!textual_addr_to_sockaddr(addr, port, (struct sockaddr *)&addr_in,
+                                  &addr_len)) {
+        return BHT_ERROR;
+    }
 
     ret = connect(socket, (struct sockaddr *)&addr_in, addr_len);
     if (ret == -1) {
@@ -155,12 +250,53 @@ os_socket_recv(bh_socket_t socket, void *buf, unsigned int len)
     return recv(socket, buf, len, 0);
 }
 
+int
+os_socket_recv_from(bh_socket_t socket, void *buf, unsigned int len, int flags,
+                    bh_sockaddr_t *src_addr)
+{
+    struct sockaddr_storage sock_addr = { 0 };
+    socklen_t socklen = sizeof(sock_addr);
+    int ret;
+
+    ret = recvfrom(socket, buf, len, flags, (struct sockaddr *)&sock_addr,
+                   &socklen);
+
+    if (ret < 0) {
+        return ret;
+    }
+
+    if (src_addr && socklen > 0) {
+        if (sockaddr_to_bh_sockaddr((struct sockaddr *)&sock_addr, src_addr)
+            == BHT_ERROR) {
+            return -1;
+        }
+    }
+    else if (src_addr) {
+        memset(src_addr, 0, sizeof(*src_addr));
+    }
+
+    return ret;
+}
+
 int
 os_socket_send(bh_socket_t socket, const void *buf, unsigned int len)
 {
     return send(socket, buf, len, 0);
 }
 
+int
+os_socket_send_to(bh_socket_t socket, const void *buf, unsigned int len,
+                  int flags, const bh_sockaddr_t *dest_addr)
+{
+    struct sockaddr_storage sock_addr = { 0 };
+    socklen_t socklen = 0;
+
+    bh_sockaddr_to_sockaddr(dest_addr, &sock_addr, &socklen);
+
+    return sendto(socket, buf, len, flags, (const struct sockaddr *)&sock_addr,
+                  socklen);
+}
+
 int
 os_socket_close(bh_socket_t socket)
 {
@@ -191,41 +327,701 @@ os_socket_inet_network(bool is_ipv4, const char *cp, bh_ip_addr_buffer_t *out)
         out->ipv4 = ntohl(out->ipv4);
     }
     else {
+#ifdef IPPROTO_IPV6
         if (inet_pton(AF_INET6, cp, out->ipv6) != 1) {
             return BHT_ERROR;
         }
         for (int i = 0; i < 8; i++) {
             out->ipv6[i] = ntohs(out->ipv6[i]);
         }
+#else
+        errno = EAFNOSUPPORT;
+        return BHT_ERROR;
+#endif
     }
 
     return BHT_OK;
 }
 
+static int
+getaddrinfo_error_to_errno(int error)
+{
+    switch (error) {
+        case EAI_AGAIN:
+            return EAGAIN;
+        case EAI_FAIL:
+            return EFAULT;
+        case EAI_MEMORY:
+            return ENOMEM;
+        default:
+            return EINVAL;
+    }
+}
+
+static int
+is_addrinfo_supported(struct addrinfo *info)
+{
+    return
+        // Allow only IPv4 and IPv6
+        (info->ai_family == AF_INET || info->ai_family == AF_INET6)
+        // Allow only UDP and TCP
+        && (info->ai_socktype == SOCK_DGRAM || info->ai_socktype == SOCK_STREAM)
+        && (info->ai_protocol == IPPROTO_TCP
+            || info->ai_protocol == IPPROTO_UDP);
+}
+
 int
-os_socket_addr_remote(bh_socket_t socket, bh_sockaddr_t *sockaddr)
+os_socket_addr_resolve(const char *host, const char *service,
+                       uint8_t *hint_is_tcp, uint8_t *hint_is_ipv4,
+                       bh_addr_info_t *addr_info, size_t addr_info_size,
+                       size_t *max_info_size)
+{
+    struct addrinfo hints = { 0 }, *res, *result;
+    int hints_enabled = hint_is_tcp || hint_is_ipv4;
+    int ret;
+    size_t pos = 0;
+
+    if (hints_enabled) {
+        if (hint_is_ipv4) {
+            hints.ai_family = *hint_is_ipv4 ? AF_INET : AF_INET6;
+        }
+        if (hint_is_tcp) {
+            hints.ai_socktype = *hint_is_tcp ? SOCK_STREAM : SOCK_DGRAM;
+        }
+    }
+
+    ret = getaddrinfo(host, strlen(service) == 0 ? NULL : service,
+                      hints_enabled ? &hints : NULL, &result);
+    if (ret != BHT_OK) {
+        errno = getaddrinfo_error_to_errno(ret);
+        return BHT_ERROR;
+    }
+
+    res = result;
+    while (res) {
+        if (addr_info_size > pos) {
+            if (!is_addrinfo_supported(res)) {
+                res = res->ai_next;
+                continue;
+            }
+
+            ret =
+                sockaddr_to_bh_sockaddr(res->ai_addr, &addr_info[pos].sockaddr);
+
+            if (ret == BHT_ERROR) {
+                freeaddrinfo(result);
+                return BHT_ERROR;
+            }
+
+            addr_info[pos].is_tcp = res->ai_socktype == SOCK_STREAM;
+        }
+
+        pos++;
+        res = res->ai_next;
+    }
+
+    *max_info_size = pos;
+    freeaddrinfo(result);
+
+    return BHT_OK;
+}
+
+static int
+os_socket_setbooloption(bh_socket_t socket, int level, int optname,
+                        bool is_enabled)
+{
+    int option = (int)is_enabled;
+    if (setsockopt(socket, level, optname, &option, sizeof(option)) != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+}
+
+static int
+os_socket_getbooloption(bh_socket_t socket, int level, int optname,
+                        bool *is_enabled)
+{
+    assert(is_enabled);
+
+    int optval;
+    socklen_t optval_size = sizeof(optval);
+    if (getsockopt(socket, level, optname, &optval, &optval_size) != 0) {
+        return BHT_ERROR;
+    }
+    *is_enabled = (bool)optval;
+    return BHT_OK;
+}
+
+int
+os_socket_set_send_buf_size(bh_socket_t socket, size_t bufsiz)
+{
+    int buf_size_int = (int)bufsiz;
+    if (setsockopt(socket, SOL_SOCKET, SO_SNDBUF, &buf_size_int,
+                   sizeof(buf_size_int))
+        != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_get_send_buf_size(bh_socket_t socket, size_t *bufsiz)
+{
+    assert(bufsiz);
+
+    int buf_size_int;
+    socklen_t bufsiz_len = sizeof(buf_size_int);
+    if (getsockopt(socket, SOL_SOCKET, SO_SNDBUF, &buf_size_int, &bufsiz_len)
+        != 0) {
+        return BHT_ERROR;
+    }
+    *bufsiz = (size_t)buf_size_int;
+
+    return BHT_OK;
+}
+
+int
+os_socket_set_recv_buf_size(bh_socket_t socket, size_t bufsiz)
+{
+    int buf_size_int = (int)bufsiz;
+    if (setsockopt(socket, SOL_SOCKET, SO_RCVBUF, &buf_size_int,
+                   sizeof(buf_size_int))
+        != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_get_recv_buf_size(bh_socket_t socket, size_t *bufsiz)
+{
+    assert(bufsiz);
+
+    int buf_size_int;
+    socklen_t bufsiz_len = sizeof(buf_size_int);
+    if (getsockopt(socket, SOL_SOCKET, SO_RCVBUF, &buf_size_int, &bufsiz_len)
+        != 0) {
+        return BHT_ERROR;
+    }
+    *bufsiz = (size_t)buf_size_int;
+
+    return BHT_OK;
+}
+
+int
+os_socket_set_keep_alive(bh_socket_t socket, bool is_enabled)
+{
+    return os_socket_setbooloption(socket, SOL_SOCKET, SO_KEEPALIVE,
+                                   is_enabled);
+}
+
+int
+os_socket_get_keep_alive(bh_socket_t socket, bool *is_enabled)
+{
+    return os_socket_getbooloption(socket, SOL_SOCKET, SO_KEEPALIVE,
+                                   is_enabled);
+}
+
+int
+os_socket_set_reuse_addr(bh_socket_t socket, bool is_enabled)
+{
+    return os_socket_setbooloption(socket, SOL_SOCKET, SO_REUSEADDR,
+                                   is_enabled);
+}
+
+int
+os_socket_get_reuse_addr(bh_socket_t socket, bool *is_enabled)
+{
+    return os_socket_getbooloption(socket, SOL_SOCKET, SO_REUSEADDR,
+                                   is_enabled);
+}
+
+int
+os_socket_set_reuse_port(bh_socket_t socket, bool is_enabled)
+{
+#if defined(SO_REUSEPORT) /* NuttX doesn't have SO_REUSEPORT */
+    return os_socket_setbooloption(socket, SOL_SOCKET, SO_REUSEPORT,
+                                   is_enabled);
+#else
+    errno = ENOTSUP;
+    return BHT_ERROR;
+#endif /* defined(SO_REUSEPORT) */
+}
+
+int
+os_socket_get_reuse_port(bh_socket_t socket, bool *is_enabled)
+{
+#if defined(SO_REUSEPORT) /* NuttX doesn't have SO_REUSEPORT */
+    return os_socket_getbooloption(socket, SOL_SOCKET, SO_REUSEPORT,
+                                   is_enabled);
+#else
+    errno = ENOTSUP;
+    return BHT_ERROR;
+#endif /* defined(SO_REUSEPORT) */
+}
+
+int
+os_socket_set_linger(bh_socket_t socket, bool is_enabled, int linger_s)
+{
+    struct linger linger_opts = { .l_onoff = (int)is_enabled,
+                                  .l_linger = linger_s };
+    if (setsockopt(socket, SOL_SOCKET, SO_LINGER, &linger_opts,
+                   sizeof(linger_opts))
+        != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_get_linger(bh_socket_t socket, bool *is_enabled, int *linger_s)
+{
+    assert(is_enabled);
+    assert(linger_s);
+
+    struct linger linger_opts;
+    socklen_t linger_opts_len = sizeof(linger_opts);
+    if (getsockopt(socket, SOL_SOCKET, SO_LINGER, &linger_opts,
+                   &linger_opts_len)
+        != 0) {
+        return BHT_ERROR;
+    }
+    *linger_s = linger_opts.l_linger;
+    *is_enabled = (bool)linger_opts.l_onoff;
+    return BHT_OK;
+}
+
+int
+os_socket_set_tcp_no_delay(bh_socket_t socket, bool is_enabled)
+{
+    return os_socket_setbooloption(socket, IPPROTO_TCP, TCP_NODELAY,
+                                   is_enabled);
+}
+
+int
+os_socket_get_tcp_no_delay(bh_socket_t socket, bool *is_enabled)
+{
+    return os_socket_getbooloption(socket, IPPROTO_TCP, TCP_NODELAY,
+                                   is_enabled);
+}
+
+int
+os_socket_set_tcp_quick_ack(bh_socket_t socket, bool is_enabled)
+{
+#ifdef TCP_QUICKACK
+    return os_socket_setbooloption(socket, IPPROTO_TCP, TCP_QUICKACK,
+                                   is_enabled);
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_get_tcp_quick_ack(bh_socket_t socket, bool *is_enabled)
+{
+#ifdef TCP_QUICKACK
+    return os_socket_getbooloption(socket, IPPROTO_TCP, TCP_QUICKACK,
+                                   is_enabled);
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_set_tcp_keep_idle(bh_socket_t socket, uint32 time_s)
+{
+    int time_s_int = (int)time_s;
+#ifdef TCP_KEEPIDLE
+    if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &time_s_int,
+                   sizeof(time_s_int))
+        != 0) {
+        return BHT_ERROR;
+    }
+    return BHT_OK;
+#elif defined(TCP_KEEPALIVE)
+    if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPALIVE, &time_s_int,
+                   sizeof(time_s_int))
+        != 0) {
+        return BHT_ERROR;
+    }
+    return BHT_OK;
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_get_tcp_keep_idle(bh_socket_t socket, uint32 *time_s)
+{
+    assert(time_s);
+    int time_s_int;
+    socklen_t time_s_len = sizeof(time_s_int);
+#ifdef TCP_KEEPIDLE
+    if (getsockopt(socket, IPPROTO_TCP, TCP_KEEPIDLE, &time_s_int, &time_s_len)
+        != 0) {
+        return BHT_ERROR;
+    }
+    *time_s = (uint32)time_s_int;
+    return BHT_OK;
+#elif defined(TCP_KEEPALIVE)
+    if (getsockopt(socket, IPPROTO_TCP, TCP_KEEPALIVE, &time_s_int, &time_s_len)
+        != 0) {
+        return BHT_ERROR;
+    }
+    *time_s = (uint32)time_s_int;
+    return BHT_OK;
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_set_tcp_keep_intvl(bh_socket_t socket, uint32 time_s)
+{
+    int time_s_int = (int)time_s;
+#ifdef TCP_KEEPINTVL
+    if (setsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &time_s_int,
+                   sizeof(time_s_int))
+        != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_get_tcp_keep_intvl(bh_socket_t socket, uint32 *time_s)
+{
+#ifdef TCP_KEEPINTVL
+    assert(time_s);
+    int time_s_int;
+    socklen_t time_s_len = sizeof(time_s_int);
+    if (getsockopt(socket, IPPROTO_TCP, TCP_KEEPINTVL, &time_s_int, &time_s_len)
+        != 0) {
+        return BHT_ERROR;
+    }
+    *time_s = (uint32)time_s_int;
+    return BHT_OK;
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_set_tcp_fastopen_connect(bh_socket_t socket, bool is_enabled)
+{
+#ifdef TCP_FASTOPEN_CONNECT
+    return os_socket_setbooloption(socket, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
+                                   is_enabled);
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_get_tcp_fastopen_connect(bh_socket_t socket, bool *is_enabled)
+{
+#ifdef TCP_FASTOPEN_CONNECT
+    return os_socket_getbooloption(socket, IPPROTO_TCP, TCP_FASTOPEN_CONNECT,
+                                   is_enabled);
+#else
+    errno = ENOSYS;
+
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_set_ip_multicast_loop(bh_socket_t socket, bool ipv6, bool is_enabled)
+{
+    if (ipv6) {
+#ifdef IPPROTO_IPV6
+        return os_socket_setbooloption(socket, IPPROTO_IPV6,
+                                       IPV6_MULTICAST_LOOP, is_enabled);
+#else
+        errno = EAFNOSUPPORT;
+        return BHT_ERROR;
+#endif
+    }
+    else {
+        return os_socket_setbooloption(socket, IPPROTO_IP, IP_MULTICAST_LOOP,
+                                       is_enabled);
+    }
+}
+
+int
+os_socket_get_ip_multicast_loop(bh_socket_t socket, bool ipv6, bool *is_enabled)
+{
+    if (ipv6) {
+#ifdef IPPROTO_IPV6
+        return os_socket_getbooloption(socket, IPPROTO_IPV6,
+                                       IPV6_MULTICAST_LOOP, is_enabled);
+#else
+        errno = EAFNOSUPPORT;
+        return BHT_ERROR;
+#endif
+    }
+    else {
+        return os_socket_getbooloption(socket, IPPROTO_IP, IP_MULTICAST_LOOP,
+                                       is_enabled);
+    }
+}
+
+int
+os_socket_set_ip_add_membership(bh_socket_t socket,
+                                bh_ip_addr_buffer_t *imr_multiaddr,
+                                uint32_t imr_interface, bool is_ipv6)
+{
+    assert(imr_multiaddr);
+    if (is_ipv6) {
+#if defined(IPPROTO_IPV6) && !defined(BH_PLATFORM_COSMOPOLITAN)
+        struct ipv6_mreq mreq;
+        for (int i = 0; i < 8; i++) {
+            ((uint16_t *)mreq.ipv6mr_multiaddr.s6_addr)[i] =
+                imr_multiaddr->ipv6[i];
+        }
+        mreq.ipv6mr_interface = imr_interface;
+        if (setsockopt(socket, IPPROTO_IPV6, IPV6_JOIN_GROUP, &mreq,
+                       sizeof(mreq))
+            != 0) {
+            return BHT_ERROR;
+        }
+#else
+        errno = EAFNOSUPPORT;
+        return BHT_ERROR;
+#endif
+    }
+    else {
+        struct ip_mreq mreq;
+        mreq.imr_multiaddr.s_addr = imr_multiaddr->ipv4;
+        mreq.imr_interface.s_addr = imr_interface;
+        if (setsockopt(socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq,
+                       sizeof(mreq))
+            != 0) {
+            return BHT_ERROR;
+        }
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_set_ip_drop_membership(bh_socket_t socket,
+                                 bh_ip_addr_buffer_t *imr_multiaddr,
+                                 uint32_t imr_interface, bool is_ipv6)
+{
+    assert(imr_multiaddr);
+    if (is_ipv6) {
+#if defined(IPPROTO_IPV6) && !defined(BH_PLATFORM_COSMOPOLITAN)
+        struct ipv6_mreq mreq;
+        for (int i = 0; i < 8; i++) {
+            ((uint16_t *)mreq.ipv6mr_multiaddr.s6_addr)[i] =
+                imr_multiaddr->ipv6[i];
+        }
+        mreq.ipv6mr_interface = imr_interface;
+        if (setsockopt(socket, IPPROTO_IPV6, IPV6_LEAVE_GROUP, &mreq,
+                       sizeof(mreq))
+            != 0) {
+            return BHT_ERROR;
+        }
+#else
+        errno = EAFNOSUPPORT;
+        return BHT_ERROR;
+#endif
+    }
+    else {
+        struct ip_mreq mreq;
+        mreq.imr_multiaddr.s_addr = imr_multiaddr->ipv4;
+        mreq.imr_interface.s_addr = imr_interface;
+        if (setsockopt(socket, IPPROTO_IP, IP_DROP_MEMBERSHIP, &mreq,
+                       sizeof(mreq))
+            != 0) {
+            return BHT_ERROR;
+        }
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_set_ip_ttl(bh_socket_t socket, uint8_t ttl_s)
+{
+    if (setsockopt(socket, IPPROTO_IP, IP_TTL, &ttl_s, sizeof(ttl_s)) != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_get_ip_ttl(bh_socket_t socket, uint8_t *ttl_s)
+{
+    socklen_t opt_len = sizeof(*ttl_s);
+    if (getsockopt(socket, IPPROTO_IP, IP_TTL, ttl_s, &opt_len) != 0) {
+        return BHT_ERROR;
+    }
+
+    return BHT_OK;
+}
+
+int
+os_socket_set_ip_multicast_ttl(bh_socket_t socket, uint8_t ttl_s)
 {
-    struct sockaddr_in addr;
-    socklen_t addr_len = sizeof(addr);
+    if (setsockopt(socket, IPPROTO_IP, IP_MULTICAST_TTL, &ttl_s, sizeof(ttl_s))
+        != 0) {
+        return BHT_ERROR;
+    }
 
-    if (getpeername(socket, (struct sockaddr *)&addr, &addr_len) == -1) {
+    return BHT_OK;
+}
+
+int
+os_socket_get_ip_multicast_ttl(bh_socket_t socket, uint8_t *ttl_s)
+{
+    socklen_t opt_len = sizeof(*ttl_s);
+    if (getsockopt(socket, IPPROTO_IP, IP_MULTICAST_TTL, ttl_s, &opt_len)
+        != 0) {
         return BHT_ERROR;
     }
 
-    return sockaddr_to_bh_sockaddr((struct sockaddr *)&addr, addr_len,
-                                   sockaddr);
+    return BHT_OK;
+}
+
+int
+os_socket_set_ipv6_only(bh_socket_t socket, bool is_enabled)
+{
+#ifdef IPPROTO_IPV6
+    return os_socket_setbooloption(socket, IPPROTO_IPV6, IPV6_V6ONLY,
+                                   is_enabled);
+#else
+    errno = EAFNOSUPPORT;
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_get_ipv6_only(bh_socket_t socket, bool *is_enabled)
+{
+#ifdef IPPROTO_IPV6
+    return os_socket_getbooloption(socket, IPPROTO_IPV6, IPV6_V6ONLY,
+                                   is_enabled);
+#else
+    errno = EAFNOSUPPORT;
+    return BHT_ERROR;
+#endif
+}
+
+int
+os_socket_set_broadcast(bh_socket_t socket, bool is_enabled)
+{
+    return os_socket_setbooloption(socket, SOL_SOCKET, SO_BROADCAST,
+                                   is_enabled);
+}
+
+int
+os_socket_get_broadcast(bh_socket_t socket, bool *is_enabled)
+{
+    return os_socket_getbooloption(socket, SOL_SOCKET, SO_BROADCAST,
+                                   is_enabled);
+}
+
+int
+os_socket_set_send_timeout(bh_socket_t socket, uint64 timeout_us)
+{
+    struct timeval tv;
+    tv.tv_sec = timeout_us / 1000000UL;
+    tv.tv_usec = timeout_us % 1000000UL;
+    if (setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)) != 0) {
+        return BHT_ERROR;
+    }
+    return BHT_OK;
+}
+
+int
+os_socket_get_send_timeout(bh_socket_t socket, uint64 *timeout_us)
+{
+    struct timeval tv;
+    socklen_t tv_len = sizeof(tv);
+    if (getsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &tv, &tv_len) != 0) {
+        return BHT_ERROR;
+    }
+    *timeout_us = (tv.tv_sec * 1000000UL) + tv.tv_usec;
+    return BHT_OK;
+}
+
+int
+os_socket_set_recv_timeout(bh_socket_t socket, uint64 timeout_us)
+{
+    struct timeval tv;
+    tv.tv_sec = timeout_us / 1000000UL;
+    tv.tv_usec = timeout_us % 1000000UL;
+    if (setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) != 0) {
+        return BHT_ERROR;
+    }
+    return BHT_OK;
+}
+
+int
+os_socket_get_recv_timeout(bh_socket_t socket, uint64 *timeout_us)
+{
+    struct timeval tv;
+    socklen_t tv_len = sizeof(tv);
+    if (getsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &tv, &tv_len) != 0) {
+        return BHT_ERROR;
+    }
+    *timeout_us = (tv.tv_sec * 1000000UL) + tv.tv_usec;
+    return BHT_OK;
 }
 
 int
 os_socket_addr_local(bh_socket_t socket, bh_sockaddr_t *sockaddr)
 {
-    struct sockaddr_in addr;
-    socklen_t addr_len = sizeof(addr);
+    struct sockaddr_storage addr_storage = { 0 };
+    socklen_t addr_len = sizeof(addr_storage);
+    int ret;
+
+    ret = getsockname(socket, (struct sockaddr *)&addr_storage, &addr_len);
+
+    if (ret != BHT_OK) {
+        return BHT_ERROR;
+    }
+
+    return sockaddr_to_bh_sockaddr((struct sockaddr *)&addr_storage, sockaddr);
+}
+
+int
+os_socket_addr_remote(bh_socket_t socket, bh_sockaddr_t *sockaddr)
+{
+    struct sockaddr_storage addr_storage = { 0 };
+    socklen_t addr_len = sizeof(addr_storage);
+    int ret;
+
+    ret = getpeername(socket, (struct sockaddr *)&addr_storage, &addr_len);
 
-    if (getsockname(socket, (struct sockaddr *)&addr, &addr_len) == -1) {
+    if (ret != BHT_OK) {
         return BHT_ERROR;
     }
 
-    return sockaddr_to_bh_sockaddr((struct sockaddr *)&addr, addr_len,
-                                   sockaddr);
+    return sockaddr_to_bh_sockaddr((struct sockaddr *)&addr_storage, sockaddr);
 }

+ 56 - 1
core/shared/platform/esp-idf/espidf_thread.c

@@ -230,4 +230,59 @@ int
 os_cond_broadcast(korp_cond *cond)
 {
     return pthread_cond_broadcast(cond);
-}
+}
+
+int
+os_rwlock_init(korp_rwlock *lock)
+{
+    assert(lock);
+
+    if (pthread_rwlock_init(lock, NULL) != BHT_OK)
+        return BHT_ERROR;
+
+    return BHT_OK;
+}
+
+int
+os_rwlock_rdlock(korp_rwlock *lock)
+{
+    assert(lock);
+
+    if (pthread_rwlock_rdlock(lock) != BHT_OK)
+        return BHT_ERROR;
+
+    return BHT_OK;
+}
+
+int
+os_rwlock_wrlock(korp_rwlock *lock)
+{
+    assert(lock);
+
+    if (pthread_rwlock_wrlock(lock) != BHT_OK)
+        return BHT_ERROR;
+
+    return BHT_OK;
+}
+
+int
+os_rwlock_unlock(korp_rwlock *lock)
+{
+    assert(lock);
+
+    if (pthread_rwlock_unlock(lock) != BHT_OK)
+        return BHT_ERROR;
+
+    return BHT_OK;
+}
+
+int
+os_rwlock_destroy(korp_rwlock *lock)
+{
+    assert(lock);
+
+    if (pthread_rwlock_destroy(lock) != BHT_OK)
+        return BHT_ERROR;
+
+    return BHT_OK;
+}