feat(core)!: a no_std leaf for microcontrollers, released as 0.2.0 - #6
Merged
Conversation
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>
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes brick B2 of Kairos'
build-me-bare.md:rusty_time-corecarried theno-stdcategory on crates.io and failed to compile on a Cortex-M4F. A categoryis not a claim; a target that compiles is.
What this does
The leaf is
ntp— the NTPv4 packet codec, RFC 7822 extension iteration and theRFC 5905 §8 offset/delay arithmetic. It builds with no
stdand noalloc,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— needsVec/Stringand now sitsbehind a default-on
stdfeature. Default builds are unchanged(
cargo semver-checksvs 0.1.10 on the default arm: 196 checks, no semverupdate required).
ParseErrorimplementscore::error::Errorrather than having the impl gatedaway. The plan asked for the gate; the two traits have been the same since Rust
1.81, so hosted callers see nothing and
no_stdcallers keep the impl insteadof losing it — they can still use
?on aParseError.Why 0.2.0 and not the 0.1.11 the plan asked for
default-features = falsenow selects the leaf. Before this release the cratehad no
[features]table at all, so that spelling was a silent no-op on a hostand did not build for any bare-metal target — the defect being fixed.
Under Cargo's 0.x rules
0.1.10and0.1.11are compatible, so anyone onrusty_time-core = "0.1"withdefault-features = falsewould have been handedthe 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
cargo check --no-default-features --target thumbv7em-none-eabihfcargo check --no-default-features --target riscv32imac-unknown-none-elfcargo test -p rusty_time-core --no-default-featuresno_stdcode pathcargo test --workspace--all-targetsThe gate is proven able to fail. Removing the
cfg_attrreproduces theplan's exact
error[E0463]: can't find crate for std; restoringstd::error::ErrorgivesE0433. Both restored to exit 0. A three-linegate-*crate is exactly the shape that can be green for the wrong reason.The
--all-targetsclippy rung earned its place immediately — it caught thethree 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 CIrunner has) and excluded from the workspace, so a normal
cargo buildneversees it.
Two things make it more than a smoke test. It parses a hand-written 48-byte
wire image, not the codec's own
to_bytesoutput — parsing what we wrotewould 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
f64unit, so the offset arithmetic runs in soft-float, which is thehalf a host
cargo testcannot 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-gatepin to=0.2.0. That is a separate commit inthat 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