Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
build/
3rdparty/
!3rdparty/FreeRTOS
!3rdparty/libyuarel
!3rdparty/llhttp
!3rdparty/quickjs
.isere/
*.so
55 changes: 0 additions & 55 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,12 @@ on:
branches:
- main
pull_request:
branches:
- main

permissions:
contents: read

env:
ZEPHYR_SDK_VERSION: 1.0.0
ZEPHYR_SDK_INSTALL_DIR: /opt/zephyr-sdk-1.0.0

jobs:
test:
name: Unit Tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: true

- uses: dtolnay/rust-toolchain@stable

- name: Run tests
run: cargo test

bytecode:
name: Compile Bytecode
runs-on: ubuntu-latest
Expand Down Expand Up @@ -72,16 +54,11 @@ jobs:
path: app

- name: Download bytecode artifact
if: matrix.arch == 'arm'
uses: actions/download-artifact@v4
with:
name: handler-bytecode
path: app/js/

- name: Remove placeholder bytecode for native_sim
if: matrix.arch == 'x86'
run: rm -f app/js/handler.bin

- name: Install system dependencies
run: |
sudo apt-get update
Expand Down Expand Up @@ -142,35 +119,3 @@ jobs:
west build -b ${{ matrix.board }} -p always app/
env:
ZEPHYR_SDK_INSTALL_DIR: ${{ env.ZEPHYR_SDK_INSTALL_DIR }}

- name: Upload native_sim binary
if: matrix.arch == 'x86'
uses: actions/upload-artifact@v4
with:
name: native-sim-binary
path: build/zephyr/zephyr.exe

- name: Upload RP2350 UF2
if: matrix.arch == 'arm'
uses: actions/upload-artifact@v4
with:
name: rpi-pico2-uf2
path: build/zephyr/zephyr.uf2

integration-test:
name: Integration Tests
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4

- name: Download native_sim binary
uses: actions/download-artifact@v4
with:
name: native-sim-binary
path: bin/

- name: Run integration tests
timeout-minutes: 3
run: |
scripts/integration_test.sh bin/zephyr.exe
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,3 @@ isere
Makefile
CMakeCache.txt
cmake_install.cmake
/target/
9 changes: 4 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,16 @@ The project was migrated from a C codebase (FreeRTOS + lwIP + TinyUSB) to **Zeph
## Tech stack

- **Zephyr RTOS** — kernel, USB CDC-ECM ethernet, networking, DHCP server
- **Rust** (`#![no_std]`, staticlib) — application logic: HTTP server (httparse for zero-copy parsing), event loop, JS runtime wrapper, platform abstraction
- **Rust** (`#![no_std]`, staticlib) — application logic: HTTP server, event loop, JS runtime wrapper, platform abstraction
- **QuickJS** (C, git submodule at `c_libs/quickjs/`) — JavaScript engine, accessed through raw FFI bindings
- **Target hardware** — Raspberry Pi Pico 2 (RP2350, Cortex-M33, 520KB SRAM)
- **Build system** — west + CMake (Zephyr) invoking Cargo (Rust)

## Build commands

```sh
west build -b rpi_pico2/rp2350a/m33 # hardware build
west build -b native_sim # development build (no hardware)
west build -b rpi_pico2/rp2350a/m33 # hardware build (Linux host required for -m32 bytecode)
west build -b native_sim/native/64 # development build in Docker (macOS arm64)
west flash # flash to device
```

Expand All @@ -42,7 +42,7 @@ scripts/
compile_bytecode.sh — Builds the compiler for 32-bit and runs it
src/
lib.rs — Entry point (rust_main), server socket setup, connection state machine, event loop
httpd.rs — HTTP/1.1 server: zero-copy request parsing via httparse, response builder
httpd.rs — HTTP/1.1 request parser (zero-copy, no_std) and response builder
http_handler.rs — Bridges HTTP request → JsContext → HTTP response
event_loop.rs — Poll-based I/O (replaces libuv subset from C version)
js/
Expand Down Expand Up @@ -77,6 +77,5 @@ src/
- The `zephyr` crate dependency in Cargo.toml comes from the `zephyr-lang-rust` module (declared in `west.yml`)
- QuickJS bytecode is NOT portable across pointer sizes — always compile with `-m32` for the 32-bit RP2350
- The HTTP server uses Connection: close (no keep-alive) — each request is a full TCP connection
- `HttpRequest<'a>` is fully zero-copy: path, query, headers, and body are references into the connection's receive buffer (parsed on-demand via `httparse`, not stored in `Connection`)
- Max 12 simultaneous connections, 8 setTimeout timers, 2KB request buffer, 4KB response body
- Handler JS format: `export const handler = async function(event, context, done) { return { statusCode, headers, body } }`
91 changes: 44 additions & 47 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,66 +7,63 @@ add_subdirectory(c_libs)

# --- Compile handler.js to QuickJS bytecode at build time ---
#
# QuickJS bytecode is NOT portable across pointer sizes. The bytecode
# compiler must be built to match the target:
# - 32-bit targets (RP2350): compile with -m32
# - 64-bit targets (native_sim/native/64): compile native (no -m32)
# handler.bin must be pre-compiled for 32-bit (to match RP2350's pointer size)
# using scripts/compile_bytecode.sh before the firmware build.
# In CI, the bytecode job does this and passes handler.bin as an artifact.
#
# In CI, a pre-compiled handler.bin can be provided as an artifact,
# but it MUST match the target's pointer size.
# For local native_sim builds where handler.bin is absent, we fall back to
# building it here using the host's gcc (never CMAKE_C_COMPILER, which is
# the cross-compiler when targeting ARM).

set(QUICKJS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/c_libs/quickjs)
set(HANDLER_JS ${CMAKE_CURRENT_SOURCE_DIR}/js/handler.js)
set(HANDLER_BIN ${CMAKE_CURRENT_SOURCE_DIR}/js/handler.bin)
set(COMPILE_BYTECODE ${CMAKE_CURRENT_BINARY_DIR}/compile_bytecode)

# Detect target pointer size to choose correct compiler flags
if(CONFIG_64BIT)
set(BYTECODE_M_FLAG "")
set(BYTECODE_COMMENT "Building host bytecode compiler (64-bit)")
# native_sim runs on the host (64-bit on modern systems) — bytecode must match.
# RP2350 (Cortex-M33) is 32-bit — requires -m32 on the host compiler.
# macOS arm64 does not support -m32 at all, so native_sim is the only usable
# build target on Apple Silicon without a Linux cross-build environment.
if(BOARD MATCHES "native_sim")
set(BYTECODE_ARCH_FLAGS "")
else()
set(BYTECODE_M_FLAG "-m32")
set(BYTECODE_COMMENT "Building host bytecode compiler (32-bit)")
set(BYTECODE_ARCH_FLAGS "-m32")
endif()

if(EXISTS ${HANDLER_BIN})
# handler.bin is already present (pre-built by CI or developer); use it.
add_custom_target(handler_bytecode ALL)
else()
# Not present: build using the host's native gcc, not CMAKE_C_COMPILER
# (which would be arm-zephyr-eabi-gcc when cross-compiling).
find_program(HOST_CC gcc REQUIRED)
find_program(HOST_CC gcc HINTS /usr/bin /usr/local/bin)
if(NOT HOST_CC)
find_program(HOST_CC cc REQUIRED)
endif()

add_custom_command(
OUTPUT ${COMPILE_BYTECODE}
COMMAND ${HOST_CC} ${BYTECODE_M_FLAG} -O2
-o ${COMPILE_BYTECODE}
${CMAKE_CURRENT_SOURCE_DIR}/scripts/compile_bytecode.c
${QUICKJS_DIR}/quickjs.c
${QUICKJS_DIR}/libregexp.c
${QUICKJS_DIR}/libunicode.c
${QUICKJS_DIR}/cutils.c
${QUICKJS_DIR}/dtoa.c
-I${QUICKJS_DIR}
-DCONFIG_VERSION='"2025-09-13"'
-D_GNU_SOURCE
-lm -lpthread
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/scripts/compile_bytecode.c
${QUICKJS_DIR}/quickjs.c
${QUICKJS_DIR}/quickjs.h
COMMENT "${BYTECODE_COMMENT}"
)
add_custom_command(
OUTPUT ${COMPILE_BYTECODE}
COMMAND ${HOST_CC} ${BYTECODE_ARCH_FLAGS} -O2
-o ${COMPILE_BYTECODE}
${CMAKE_CURRENT_SOURCE_DIR}/scripts/compile_bytecode.c
${QUICKJS_DIR}/quickjs.c
${QUICKJS_DIR}/libregexp.c
${QUICKJS_DIR}/libunicode.c
${QUICKJS_DIR}/cutils.c
${QUICKJS_DIR}/dtoa.c
-I${QUICKJS_DIR}
-DCONFIG_VERSION='"2025-09-13"'
-D_GNU_SOURCE
-lm -lpthread
DEPENDS
${CMAKE_CURRENT_SOURCE_DIR}/scripts/compile_bytecode.c
${QUICKJS_DIR}/quickjs.c
${QUICKJS_DIR}/quickjs.h
COMMENT "Building host bytecode compiler (${BOARD})"
)

add_custom_command(
OUTPUT ${HANDLER_BIN}
COMMAND ${COMPILE_BYTECODE} ${HANDLER_JS} ${HANDLER_BIN}
DEPENDS ${COMPILE_BYTECODE} ${HANDLER_JS}
COMMENT "Compiling handler.js to QuickJS bytecode"
)
add_custom_command(
OUTPUT ${HANDLER_BIN}
COMMAND ${COMPILE_BYTECODE} ${HANDLER_JS} ${HANDLER_BIN}
DEPENDS ${COMPILE_BYTECODE} ${HANDLER_JS}
COMMENT "Compiling handler.js to QuickJS bytecode"
)

add_custom_target(handler_bytecode ALL DEPENDS ${HANDLER_BIN})
endif()
add_custom_target(handler_bytecode ALL DEPENDS ${HANDLER_BIN})


# Zephyr API shims for Rust FFI (k_uptime_get and similar __syscall functions
Expand Down
Loading
Loading