FREE RAG Converter Online -- RAGconverter.com
A no_std JSON validator and in-place query engine, the Kairos remake of
coreJSON. It agrees with the C on all 318 files of JSONTestSuite, and passes
the suite outright.
- Proven, the validator:
JSON_Validate, strict ECMA-404. It agrees with coreJSON v3.3.1 on all 318 files of JSONTestSuite, and passes the suite outright — 95/95 accepted, 188/188 rejected. - Proven, the query engine:
JSON_SearchConstandJSON_Iterate, over 2,124 queries and 348 iterations compared against the C as a 3,089-line trace — status, offset, length and type, every time. - Zero allocation, on purpose. The depth stack is a fixed 32-byte array; there is no heap on any path, so the crate builds and runs the same on a Cortex-M with no allocator at all.
- A no-panic gate, because this one is fed by strangers. Every other crate here is driven by our own kernel. A validator is driven by bytes off a socket, so "cannot panic on any input" is not tidiness, it is the security property.
Known gaps. coreJSON has no serialiser and neither does this. JSON_SearchT
is absent on purpose: it is a cast of JSON_SearchConst that exists only to
let C callers pass a mutable buffer, and Rust needs no such twin.
- This package's plan: docs/plans/rusty_rtos_json.md
- Every number: docs/LEDGER.md
- The family plan: Kairos
docs/plans/rtos-mission.md
Claims discipline: this README makes no performance or capability claim that is not backed by a test, a benchmark ledger entry, or a kill test recorded in the plan. "Scaffold" means scaffold. "Sim only" means the sim port; "builds, not flashed" means no chip has run it.
318 of 318 files agree with core_json.c, compiled verbatim from the
pinned checkout (v3.3.1 at cffa492), and the suite is passed outright.
cargo test -p rusty_rtos_json-coreThe C arm's verdicts are checked in and the corpus is vendored, so this diffs
with no C toolchain and no network. Fetch the oracle itself with kairos oracle fetch --lib coreJSON; the corpus is Nicolas Seriot's JSONTestSuite, pinned at
1ef36fa in ORACLES.md.
Two tests, because they are two claims. One compares our verdict against
coreJSON's, file by file; the other compares against the suite's — every
y_ accepted, every n_ rejected. They happen to be the same target here, and
that was measured before either was adopted: coreJSON itself scores 100 % on
the suite. Had it failed anywhere, "agree with the C" and "pass the suite"
would have pulled apart and one would have had to give. Keeping both means the
day that changes is visible rather than silently resolved.
The 35 i_ files are where the standard leaves the answer to the
implementation, so the suite has no opinion and only the differential does.
coreJSON accepts 10 and rejects 25 — being a JSON parser does not determine
those, being coreJSON does.
The corpus guard. For a validator the rejections are the hard half:
accepting valid JSON is what any half-written scanner does, and every n_ file
is a specific way to be wrong. A standing test fails if the corpus stops being
mostly rejections. That is the fourth shape of that guard here, after
heap_4's (too few refusals), heap_1's (never exhausted) and backoff's (an
unvisited branch), and all four say the same thing: a differential whose
workload cannot fail is a differential about nothing.
Poison-proven on four behaviours, each caught by a different set of files:
- over-long UTF-8 — accepting a non-shortest encoding fails 2 files;
- lone surrogates — accepting an unpaired high surrogate escape fails 3;
- trailing commas — allowing one fails 2;
- leading zeros — allowing
01fails 3.
2,124 queries and 348 iterations agree with core_json.c, compared as a
3,089-line trace, line for line.
cargo test -p rusty_rtos_json-core --test querysearch takes a dotted, bracketed path and hands back a sub-slice of the
buffer you already have — no tree, no copy, no allocation. a.b[2].c is the
key c of the third element of the array at a.b.
use rusty_rtos_json::{search, pairs, Kind};
let doc = br#"{"a":{"b":[10,20,{"c":"hi"}]}}"#;
assert_eq!(search(doc, b"a.b[1]").unwrap().value, b"20");
let found = search(doc, b"a.b[2].c").unwrap();
assert_eq!(found.kind, Kind::String); // quotes stripped, as the C does
assert_eq!(found.value, b"hi");
// Walk a collection instead. An array yields values with no keys.
for pair in pairs(br#"{"x":1,"y":2}"#) {
let _ = (pair.key, pair.value, pair.kind);
}Two refusal types, not one. The C has a single JSONStatus_t and each
function documents the subset it can return. Here search can only answer
Missing or BadQuery, and iterate adds NotACollection — so they have
different types and the impossible variant is not there to be matched on. The
differential maps both back to the C's names, which is what makes the arms
comparable at all.
Neither entry point validates first, and neither does the C. A query walks whatever bytes it is handed. That is why the differential runs both of them over all 318 corpus files including the 188 that are malformed on purpose: walking a broken document is exactly where a reimplementation reads off the end. 70 of those malformed files still answer a query successfully, and every one of those answers had to match.
Poison-proven on five behaviours, each a real place a reimplementation drifts:
- a string's quotes are stripped from the value, and the length shortened by two;
- a trailing separator (
a.) is aBadQuery, which is one- 1in the C; - the first duplicate key wins, not the last;
- a huge array index latches to -1 rather than wrapping, so
[99999999999999999999]is refused instead of reading element zero; - an array element reports no key, which the C signals with a NULL pointer.
A sixth poison did not fire, and that is recorded rather than dropped: swapping the order in which a value is tried as a scalar and as a collection changes nothing anywhere in the 3,089 lines. The two scanners are disjoint on their first byte and neither moves the cursor when it fails, so the order is free — and a unit test now pins both halves of that, because an accidental property nobody checks is one edit away from being false.
The crate forbids unsafe and denies unwrap, expect and panic, so a
panic could only come from arithmetic that overflows, an index out of range, or
a slice shorter than something assumed. The lints catch the shapes; these go
after the reachability:
| test | what it feeds in |
|---|---|
| arbitrary bytes | every length 0..256 of unstructured noise |
| JSON-shaped noise | 20,000 documents drawn from JSON's own alphabet, which reaches far deeper than uniform noise |
| every truncation | six valid documents cut at every offset, 202 slices — the shape a packet boundary actually makes |
| every single-byte corruption | one valid document, all 46 positions × 35 interesting bytes |
| nesting past the limit | 32, 33, 64, 1,000 and 10,000 brackets, both kinds and mixed |
| the stress files | including the corpus's 100,000 opening brackets |
| the query surface | 20,000 random documents x random queries; 20,000 iterations driven to exhaustion; every truncation and corruption of a nested document through 8 queries; all 318 corpus files through both entry points |
73,005 documents in all — 22,085 through the validator and 50,920 through the query engine. Every one of them is deterministic: the pseudo-random arms use a written-out LCG rather than a system source, so a failure is reproducible from the seed alone on any machine.
Broken on purpose before it was believed. A no-panic gate that has never
failed is indistinguishable from one that cannot fail, so three panics were
introduced into the validator deliberately. Two were caught at once — an unchecked slice in the
literal scanner (found by the truncation of true) and an unchecked index into
the depth stack (found by the nesting test and the stress file).
The third was not caught, and that is the interesting result. Making the
universal byte reader panic on any read past the end leaves all eleven tests
passing — so no scanner reads out of bounds in the first place, and the
Option it returns is defence in depth rather than the thing keeping this
safe. The two bounds that are load-bearing are the two above, and both are
proven to be. That measurement is recorded next to the function, because it
holds only while every caller is right and a later edit would lose it silently.
The query half needed a different kind of test, and it found a different kind
of bug. iterate carries a cursor the caller owns, so a version that failed
to advance it would not panic and would not answer wrongly — it would hang, and
a hang is the one failure a test runner reports as "still running" rather than
as a bug. A standing test asserts the cursor strictly advances on every success,
over 20,000 random documents. Stopping the cursor on purpose turns an infinite
loop into a named assertion with the offending document printed.
Two more poisons there did not fire, and they say the same thing the reader
poison did: taking multiSearch's narrowed sub-slice unchecked, and taking the
returned slice unchecked, both leave every test passing. The narrowing
arithmetic only ever shrinks, so those bounds cannot fail — they stay as
get rather than indexing because that is what keeps "cannot panic" a property
of the code rather than of the current call graph.
The validator; Querying has the other half.
use rusty_rtos_json::{is_valid, validate, Validity};
assert!(is_valid(br#"{"a":[1,2,{"b":"x"}],"c":true}"#));
// Refusals are distinguished, not collapsed to a bool: a caller fixes a
// truncated document and an illegal one in different ways.
assert_eq!(validate(b"{"), Validity::Partial);
assert_eq!(validate(b"{]"), Validity::Illegal);
assert_eq!(validate(b""), Validity::BadParameter);A bare scalar at the top level is a valid document — that is ECMA-404 rather than RFC 4627, it is coreJSON's default, and it is the single most likely place for a reader to think the parser is too lax.
Nesting deeper than MAX_DEPTH (32, the C's) answers
Validity::MaxDepthExceeded. The bound exists because the stack is explicit:
recursion here would meet a 100,000-bracket document with a stack overflow,
which is not a panic and cannot be caught.
No speed row and no size row: nothing here has been timed, and nothing has run on a chip. What it has is a deterministic instruction count under callgrind, which is a count rather than a clock and so belongs here on the same terms as the corpus counts.
search-ir 42,748,360 -> 21,361,060 -50.0%
object_search matches a key by comparing raw bytes — it never unescapes
one — and the slice it compares always lies between a pair of quotes. So a
matching key means the bytes "key" appear contiguously in the buffer, and the
converse is the lever: if they do not appear, no key can match, and the
walk can only answer Missing.
That is a proof, not a prediction. It cannot change an answer, only reach the
same one sooner — which is what lets it be on by default in a crate whose whole
claim is byte-identity with coreJSON. Every work-parity anchor is unmoved, and
the query differential and the 318-file suite both pass. Poison-proved: make
the proof claim absence wrongly and
our_query_engine_matches_the_c_call_for_call fails.
search-ir is a 10x20 cross product, so 76% of its pairs are provably
absent — a property of the benchmark, not of any caller. A hit-only workload
built from the same documents and the 16 pairs that actually hit says:
| workload | vs no gate |
|---|---|
| 76% absent (the instrument) | −50.0% |
| 0% absent (every query hits) | +13.2% |
So the break-even is around a 16% absence rate: above it the proof pays, below it the scan is overhead. Probing for optional fields — the common reason to query JSON on a device — sits well above that. A caller that only ever asks for keys it knows are present would be better off without it, and now has the number to decide.
A first-byte prefilter was landed and then reverted on exactly this evidence: it won 0.9% on the cross product and cost 4.1% on hits, paying only above ~62% absence. Two further ideas were sized offline and pruned unbuilt — an array-index bound (worth one pair in two hundred) and a rarest-byte anchor (47% fewer candidates, but choosing the byte needs the histogram it would save).
The ledger carries this crate's counts too — the 318 files, the 2,124 queries, the 73,005 documents — with their methods.
Builds no_std with no default features on thumbv7em-none-eabihf and
riscv32imac-unknown-none-elf (both verified), and CI holds it to
thumbv8m.main-none-eabihf and riscv32imafc-unknown-none-elf as well. A
build claim, not a behaviour claim: no chip has run this yet.
crates/rusty_rtos_json facade: re-exports + prelude; the crate you depend on
crates/rusty_rtos_json-core no_std (+ alloc); forbid(unsafe); types,
traits, algorithms
firmware/ per-chip example projects, excluded from the workspace
docs/plans/ this package's plan and its hardening audit
docs/LEDGER.md every number, with its method line
cargo test --workspace # host: the tests
cargo check -p rusty_rtos_json-core --no-default-features \
--target thumbv7em-none-eabihf # Cortex-M4F class, no alloc
cargo check -p rusty_rtos_json-core --no-default-features --features alloc \
--target riscv32imac-unknown-none-elf # ESP32-C6 class, with allocCI holds the core to thumbv7em-none-eabihf, thumbv8m.main-none-eabihf,
riscv32imac-unknown-none-elf and riscv32imafc-unknown-none-elf, with and
without alloc, plus cargo deny check. Firmware examples (Xtensa needs the
esp toolchain; Cortex-M and RISC-V work on stable) are built from their own
directories under firmware/.
This crate is part of Kairos —
FreeRTOS remade in memory-safe Rust, as independent packages that expose the API
a FreeRTOS developer already knows and prove every scheduling decision against
the C kernel's own trace. rusty_rtos_json is one of the K7 libraries, and both halves are done:
the validator passes all 318 files of JSONTestSuite, and the query engine
agrees with the C over 2,124 queries and 348 iterations.
Where this sits for Mata. Kairos is the real-time layer on the device
itself, and rusty_rtos_mqtt is the way out of it.
Paired with the MATA distributed cloud, robotics and sensor data has two
routes — read it on the machine, or reach it through the cloud — with the same
memory-safe crates at both ends.
The family:
rusty_rtos_core (the shared vocabulary),
rusty_rtos_kernel (the scheduler),
rusty_rtos_port (the architecture seam),
rusty_rtos_heap (the allocators),
rusty_rtos_json (coreJSON),
rusty_rtos_sntp (coreSNTP),
rusty_rtos_mqtt (coreMQTT),
rusty_rtos_backoff (backoffAlgorithm),
rusty_rtos-capi (the C ABI) and
rusty_rtos_demo (the conformance corpus).
All ten are on crates.io. Also check out
the rest of github.com/remade-with-rust.
Mata Network builds sovereign, self-hostable privacy infrastructure — "stop sacrificing your privacy for convenience": wallet & identity, a password manager, a contact manager, and a browser extension that stops your information leaking as you browse.
Remade With Rust is our open-source home for the permissively-licensed building blocks that work depends on — including remade_ffmpeg_rs (the FFmpeg alternative) and FFAI (the AI media toolkit).
MIT OR Apache-2.0, at your option. FreeRTOS is MIT-licensed by Amazon.com, Inc. or its affiliates; this crate remakes its API and behaviour from the published sources and links no FreeRTOS code.
The threat model is written for a parser, which has exactly one adversary: whoever controls the bytes. They choose every byte, the length, the nesting depth, and where the input is cut off.
Two things it says plainly:
- A panic here is a halt. In
no_stdthere is nothing to catch one, so robustness against crafted input is a security property, not a quality one. The evidence is ten property tests over every truncation and every single-byte corruption of a real corpus — not a handful of examples. - There is no continuous fuzzing yet. The suite is bounded and reproducible, which a fuzzer is not, and it is a floor rather than a substitute. That is written down as a residual risk with the condition that closes it.
Tier critical-path · Audited 2026-09-21 (survey) · v1.0.0 gates 10/16 · Full checklist
███████░░░░░░░░░░░░░ 39% · 14 Completed · 0 Scheduled · 22 Incomplete · 19 N/A
| Phase | ✅ Completed | 🗓 Scheduled | ⬜ Incomplete | · N/A |
|---|---|---|---|---|
| 0 — Threat modeling | 1 | 0 | 1 | 0 |
| 1 — Toolchain | 2 | 0 | 2 | 0 |
| 2 — Supply chain | 5 | 0 | 3 | 0 |
| 3 — Code level | 4 | 0 | 3 | 0 |
| 4 — Static analysis | 0 | 0 | 1 | 0 |
| 5 — Dynamic analysis | 0 | 0 | 3 | 0 |
| 6 — Fuzzing and properties | 0 | 0 | 4 | 0 |
| 7 — Formal verification | 0 | 0 | 1 | 0 |
| 8 — Build and binary | 0 | 0 | 1 | 1 |
| 9 — Runtime privilege | 0 | 0 | 0 | 1 |
| 10 — Cryptography | 0 | 0 | 0 | 3 |
| 11 — CI/CD, release, and operations | 2 | 0 | 3 | 0 |
| 12 — Compliance controls | 0 | 0 | 0 | 14 |
| Total | 14 | 0 | 22 | 19 |
Architect — Tim Almond — accountable for this unit's security design; rendered