Skip to content

feat(core)!: a no_std leaf for microcontrollers, released as 0.2.0 - #6

Merged
Ttimmahlax merged 2 commits into
mainfrom
feat/no-std-leaf
Sep 10, 2026
Merged

feat(core)!: a no_std leaf for microcontrollers, released as 0.2.0#6
Ttimmahlax merged 2 commits into
mainfrom
feat/no-std-leaf

Conversation

@Ttimmahlax

Copy link
Copy Markdown
Contributor

Closes brick B2 of Kairos' build-me-bare.md: rusty_time-core carried the
no-std category on crates.io and failed to compile on a Cortex-M4F. A category
is not a claim; a target that compiles is.

What this does

The leaf is ntp — the NTPv4 packet codec, RFC 7822 extension iteration and the
RFC 5905 §8 offset/delay arithmetic. It builds with no std and no alloc,
reads no clock, opens no socket and owns no buffer, so an SNTP client on a
Cortex-M4F, RV32 or Xtensa part needs nothing else from us.

Everything above the wire — filter, select, discipline, client,
server, config, refclock, vclock — needs Vec/String and now sits
behind a default-on std feature. Default builds are unchanged
(cargo semver-checks vs 0.1.10 on the default arm: 196 checks, no semver
update required).

ParseError implements core::error::Error rather than having the impl gated
away. The plan asked for the gate; the two traits have been the same since Rust
1.81, so hosted callers see nothing and no_std callers keep the impl instead
of losing it — they can still use ? on a ParseError.

Why 0.2.0 and not the 0.1.11 the plan asked for

default-features = false now selects the leaf. Before this release the crate
had no [features] table at all, so that spelling was a silent no-op on a host
and did not build for any bare-metal target — the defect being fixed.

Under Cargo's 0.x rules 0.1.10 and 0.1.11 are compatible, so anyone on
rusty_time-core = "0.1" with default-features = false would have been handed
the narrower crate by the next cargo update, with no version change to notice.
A minor bump makes that one breaking arm an explicit choice by the consumer.

Gates

gate result
cargo check --no-default-features --target thumbv7em-none-eabihf exit 0 (new CI rung)
cargo check --no-default-features --target riscv32imac-unknown-none-elf exit 0 (new CI rung)
cargo test -p rusty_time-core --no-default-features 11 pass — the leaf's own suite on the no_std code path
cargo test --workspace 217 pass, no dependant moved
clippy, both feature arms, --all-targets clean
ESP32-S3 rev v0.2, no heap linked 30/30 checks, PASS

The gate is proven able to fail. Removing the cfg_attr reproduces the
plan's exact error[E0463]: can't find crate for std; restoring
std::error::Error gives E0433. Both restored to exit 0. A three-line
gate-* crate is exactly the shape that can be green for the wrong reason.

The --all-targets clippy rung earned its place immediately — it caught the
three benches reaching into now-gated modules. They are
required-features = ["std"].

The board row

bare-metal/esp32s3/ is hand-run (it needs Espressif's Rust fork, which no CI
runner has) and excluded from the workspace, so a normal cargo build never
sees it.

=== rusty_time-core leaf on ESP32-S3 (xtensa, no_std, NO alloc) ===
[1] client request                    5 checks   ok
[2] server response (literal image)  14 checks   ok
[3] offset / delay (RFC 5905 §8)      5 checks   ok
      offset 0.09999999997671694 s   delay 0.05000000004656613 s
      2 s across the 2036 era wrap measured as 2 s
[4] extension fields and rejections    6 checks   ok

checks passed 30 / 30
RESULT: PASS -- the rusty_time-core leaf ran on the board

Two things make it more than a smoke test. It parses a hand-written 48-byte
wire image
, not the codec's own to_bytes output — parsing what we wrote
would only prove the codec is self-consistent, whereas a literal image pins the
RFC 5905 field offsets and byte order independently of the writer. And Xtensa
LX7 has no f64 unit, so the offset arithmetic runs in soft-float, which is the
half a host cargo test cannot stand in for.

Two numbers there look wrong and are not: the 1 ns step reads 0.931 ns and root
delay reads 0.0149993…, both being the wire format's fixed-point quantization
showing through. The checks are toleranced at one NTP tick (2⁻³² s) accordingly.

Not in this PR

Moving Kairos' tools/house-gate pin to =0.2.0. That is a separate commit in
that repo once these crates are on crates.io — its strategy says the pin moves
in one commit with the verdict, and the verdict has to be a released pin.

🤖 Generated with Claude Code

tim-almond-house and others added 2 commits September 9, 2026 17:19
rusty_time-core's NTPv4 protocol layer now builds with no `std` and no
`alloc`, so an SNTP client on a Cortex-M4F, RV32 or Xtensa part can use the
house NTP engine directly instead of remaking coreSNTP (Kairos
build-me-bare.md, brick B2).

The leaf is `ntp`: the packet codec, RFC 7822 extension iteration and the
RFC 5905 section 8 offset/delay arithmetic. It reads no clock, opens no
socket and owns no buffer. Everything above the wire -- filter, select,
discipline, client, server, config, refclock, vclock -- needs Vec/String and
now sits behind a default-on `std` feature, so default builds are unchanged.

`ParseError` implements `core::error::Error` rather than having the impl
gated away: the two have been the same trait since Rust 1.81, so hosted
callers see no change and no_std callers keep the impl instead of losing it.

BREAKING CHANGE: `default-features = false` now selects the leaf. Before this
release the crate had no [features] table at all, so that spelling was a
silent no-op on a host and did not build for any bare-metal target -- the
defect this fixes. It is also why the release is 0.2.0 and not the 0.1.11 the
plan asked for: under Cargo's 0.x rules 0.1.x bumps are compatible, so a
caret requirement paired with `default-features = false` would have been
handed the narrower crate by the next `cargo update`, with no version change
to notice. A minor bump makes that an explicit choice by the consumer.

Gates:
- CI rungs on thumbv7em-none-eabihf and riscv32imac-unknown-none-elf with
  --no-default-features, both proven able to FAIL: removing the cfg_attr
  reproduces the plan's exact `E0463: can't find crate for std`.
- The leaf's own suite run against the same no_std code path on the host,
  plus clippy on that arm. The three benches are required-features = ["std"];
  the --all-targets clippy rung is what caught them.
- bare-metal/esp32s3: 30/30 checks on an ESP32-S3 rev v0.2 with no heap
  linked. It parses a hand-written 48-byte wire image rather than its own
  to_bytes output -- parsing what we wrote would only prove self-consistency
  -- and runs the arithmetic where f64 is soft-float, which a host test
  cannot stand in for.
- cargo semver-checks against 0.1.10 on the default arm: 196 checks, no
  semver update required.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Format-only, no behaviour change: 54 daemon tests unchanged, and the diff is
identical with and without --ignore-cr-at-eol, so no line endings were
rewritten.

These two files have been unformatted since 90786e8, which has had CI's `lint`
job (`cargo fmt --all --check`) failing on main since 2026-08-29 -- red at
d0977f7 and again at cf6a07c. Any branch cut from main inherits that red,
including this one, so it is fixed here rather than left to make every future
PR look broken.

Kept as its own commit, limited to the two files rustfmt objects to, so it can
be reviewed and reverted independently of the no_std leaf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@Ttimmahlax
Ttimmahlax merged commit 7c9bfd8 into main Sep 10, 2026
25 checks passed
@Ttimmahlax
Ttimmahlax deleted the feat/no-std-leaf branch September 10, 2026 00:39
@Ttimmahlax Ttimmahlax mentioned this pull request Sep 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants