Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
200d7d3835 | ||
|
|
3c6fc29271 | ||
|
|
1365272146 | ||
|
|
05b25465aa | ||
|
|
e3c6f6c9f8 | ||
|
|
57285d23bd |
No files matched your search
@@ -165,9 +165,9 @@ just spawned implementation agents.
|
||||
`Identity`, `IdentityProvider`, `AccessControl`,
|
||||
`OwnershipProvider`, `HandlerError`, `StreamError` come from
|
||||
`alkcall::core`. Do not vendor copies into this crate. alkcall is
|
||||
v0.7.x — breaking changes are expected at this major-zero stage;
|
||||
v0.10.x — breaking changes are expected at this major-zero stage;
|
||||
this is an early consumer, so we find and fix issues upstream
|
||||
rather than working around them. Pin `alkcall = "0.7.0"` and bump
|
||||
rather than working around them. Pin `alkcall = "0.10.0"` and bump
|
||||
deliberately. The 0.6.0 establishment surface (ADR-049 + amendment
|
||||
2) is load-bearing for this crate: tunnel opens use
|
||||
`register_openable_with_establisher` so a refused target dial is
|
||||
@@ -264,7 +264,7 @@ non-backend module changes.
|
||||
(§POC Target 3: tunnel handler). Validates the demux→Connection→
|
||||
handler→mux path and the two-pump tunnel shape over a stand-in
|
||||
transport. POC code at `/workspace/alknet-channels-poc/`.
|
||||
- **alkcall** — `/workspace/@alkdev/alkcall` (v0.7.x, crates.io).
|
||||
- **alkcall** — `/workspace/@alkdev/alkcall` (v0.10.x, crates.io).
|
||||
The substrate: call protocol + channels multiplexing. The tunnel
|
||||
crate consumes `alkcall::core` types and the channels
|
||||
`ChannelCore`/`ChannelClient`/`register_openable` surface.
|
||||
|
||||
+115
-1
@@ -6,6 +6,117 @@ this crate adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.4.0] - 2026-10-19
|
||||
|
||||
The dependency-major release: the alkcall substrate bumped 0.9.0 →
|
||||
0.10.0 (the gateway ownership-seam release). No alktunnels API
|
||||
change, no wire change; zero source changes — the only file-level
|
||||
contacts with the bump are `Cargo.toml`/`Cargo.lock`. Minor rather
|
||||
than patch for the 0.3.0 reason: the semver-incompatible dependency
|
||||
bump plus this crate's public re-exports of alkcall types
|
||||
(`ChannelOpenError`; `Establishment`/`EstablishmentError` shapes in
|
||||
the producer API) make it semver-visible to downstream consumers.
|
||||
|
||||
### Changed
|
||||
|
||||
- **alkcall 0.9.0 → 0.10.0** (crates.io). The gateway
|
||||
ownership-seam release (review 012): `GatewayDispatch` gains
|
||||
`with_ownership_provider` / `with_static_ownership_fallback`, and
|
||||
the previously-silent static fallback becomes loud — an unwired
|
||||
spine refuses (an error envelope, `FORBIDDEN`, pre-dispatch) an op
|
||||
whose spec carries `access_control.resource_type`. None of it is on
|
||||
this crate's paths: alktunnels does not use the `gateway` feature
|
||||
or `GatewayDispatch` — tunnel opens serve via
|
||||
`ChannelCore::register_openable`/`register_openable_with_establisher`
|
||||
registry dispatch on the native wire, which 0.10.0 leaves
|
||||
untouched. All of this crate's alkcall contact points
|
||||
(`register_openable`/`register_openable_with_establisher`,
|
||||
`Establishment::new`, `OpenHandler`,
|
||||
`ChannelClient::open_channel_with_reply`,
|
||||
`ManagerError::ChannelExists`, `ChannelOpenError`) are unchanged
|
||||
between 0.9.0 and 0.10.0. 136 tests pass; wasm-clean verified
|
||||
(check + clippy on `wasm32-unknown-unknown`).
|
||||
|
||||
## [0.3.0] - 2026-10-14
|
||||
|
||||
The dependency-major release: the alkcall substrate bumped 0.8.1 →
|
||||
0.9.0 (the stub-sweep release). No alktunnels API change, no wire
|
||||
change; the only code contact with the 0.9.0 breaking surface was
|
||||
test-side (`Establishment` no longer derives `Debug` — three
|
||||
`expect_err`/`panic!` sites restructured), which is why this is a
|
||||
minor bump rather than a patch: the semver-incompatible dependency
|
||||
bump plus this crate's public re-exports of alkcall types make it
|
||||
semver-visible to downstream consumers (matching the 0.7.1 → 0.8.0
|
||||
ride on 0.2.0).
|
||||
|
||||
### Changed
|
||||
|
||||
- **alkcall 0.8.1 → 0.9.0** (crates.io). The stub-sweep release: the
|
||||
marked-pub open contract (S-01), `channel/control` (S-02),
|
||||
`channel/resources/subscribe` (S-03), and `op/register`
|
||||
forward-through (S-04) replace the four always-failing stubs.
|
||||
None of the four stubs were on this crate's paths (no marked-pub
|
||||
registration, no control-handle subscription, no resource
|
||||
enumerator use, no generic `op/register` announcements), so no
|
||||
behavior change here. Of the 0.9.0 breaking list,
|
||||
`Establishment` dropping its `Debug` derive was the only surface
|
||||
this crate touched — two `bindfirst_establisher` unit tests'
|
||||
`expect_err` calls and one integration test's untyped
|
||||
`panic!("{other:?}")` fall-through restructured (the error type
|
||||
itself remains `Debug`; the `Ok` arm now formats via `panic!("got
|
||||
Ok")`). `ManagerError::InvalidAdopt` (additive),
|
||||
`RelayError::PubTypedOpen` removal, and the
|
||||
`register_imported`/`register_imported_all` return change are
|
||||
untouched by this crate's API use. 136 tests pass; wasm-clean
|
||||
verified.
|
||||
|
||||
## [0.2.1] - 2026-09-28
|
||||
|
||||
The hardening release: fuzzing adopted from the alkcall pattern
|
||||
(`fuzz/` workspace, excluded from the published package) and the
|
||||
alkcall substrate bumped 0.8.0 → 0.8.1. No API break, no wire change.
|
||||
|
||||
### Added
|
||||
|
||||
- **Fuzzing (`fuzz/` workspace, `docs/research/fuzzing.md`)** — three
|
||||
cargo-fuzz targets over this crate's own attacker-reachable surfaces
|
||||
(the envelope/chunk/manager layer is upstream and fuzzed there per
|
||||
alkcall 0.8.1): `datagram_codec` (the ADR-003 codec: no-panic,
|
||||
infallible decode, size bound, F-2 sentinel layering, re-frame
|
||||
round-trip, retained-bytes bound), `params_parse` (the serde
|
||||
params/reply parsers: no-panic on raw bytes and bounded structured
|
||||
values, clean typed failure, round-trips), and `session_pumps` (the
|
||||
wave-2 stateful target: op sequences over a live channels stack per
|
||||
exec, the `TunnelSession` data planes mirrored, no-wedge +
|
||||
shutdown-on-completion + lossless p2c + EOF-sentinel teardown + the
|
||||
`pending` backlog bound asserted). Invariant logic lives in
|
||||
`fuzz/shared/` — a stable-toolchain crate replayed as plain
|
||||
`cargo test`, so the committed corpora (96 + 8 seeds, plus the
|
||||
campaign-promoted `seed-900`) stay executable without nightly.
|
||||
`fuzz/` is excluded from the workspace and from the published
|
||||
package. Campaigns (detached, fork mode): ~25M+228k execs across
|
||||
waves 1–2, zero crashes/hangs/OOMs; all findings were
|
||||
harness-model defects, no crate bugs.
|
||||
|
||||
- **`TunnelSession::datagram_backlog()`** — the UDP `pending` count
|
||||
(the datagrams decoded from a chunk beyond the one returned), the
|
||||
wave-2 harness's asserted backlog bound made caller-visible;
|
||||
`None` for stream sessions or after the halves were taken.
|
||||
|
||||
- **`DatagramReader::retained_bytes()`** — the decoder-state byte
|
||||
count (leftover buffer + the in-flight partial datagram); post-feed
|
||||
bounded by one frame (≤ `MAX_DATAGRAM_LEN` + 2), pinned by the
|
||||
`datagram_codec` target.
|
||||
|
||||
### Changed
|
||||
|
||||
- **alkcall 0.8.0 → 0.8.1** (crates.io). The bug-fix release: a
|
||||
duplicate `adopt_channel`/`open_channel` no longer destroys the
|
||||
live channel it collided with (found by alkcall's own
|
||||
`manager_routing` fuzz target) — directly relevant to this crate's
|
||||
reverse-path adopt flow. No alktunnels API or wire change in this
|
||||
bump; the full suite passes unchanged.
|
||||
|
||||
## [0.2.0] - 2026-09-20
|
||||
|
||||
The graduation release: the direct op + the bind-first/forwarded
|
||||
@@ -221,6 +332,9 @@ complete (Phase 2, review 001 findings remediated).
|
||||
matrix). Wasm-clean default crate verified (`wasm32-unknown-unknown`
|
||||
check + clippy).
|
||||
|
||||
[Unreleased]: https://git.alk.dev/alkdev/alktunnels/compare/v0.2.0...HEAD
|
||||
[Unreleased]: https://git.alk.dev/alkdev/alktunnels/compare/v0.4.0...HEAD
|
||||
[0.4.0]: https://git.alk.dev/alkdev/alktunnels/compare/v0.3.0...v0.4.0
|
||||
[0.3.0]: https://git.alk.dev/alkdev/alktunnels/compare/v0.2.1...v0.3.0
|
||||
[0.2.1]: https://git.alk.dev/alkdev/alktunnels/compare/v0.2.0...v0.2.1
|
||||
[0.2.0]: https://git.alk.dev/alkdev/alktunnels/compare/v0.1.0...v0.2.0
|
||||
[0.1.0]: https://git.alk.dev/alkdev/alktunnels/releases/tag/v0.1.0
|
||||
Generated
+3
-3
@@ -27,9 +27,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alkcall"
|
||||
version = "0.8.0"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8695d47cee5b5a5fe68471f04a505bc257a7e7ed9e64d827de33ff83adf4db12"
|
||||
checksum = "6b8ef0591beff0e3fe64b7277a67d8e7caeccf71c27538b4b7aced028d22ca44"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
@@ -48,7 +48,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "alktunnels"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"alkcall",
|
||||
"bytes",
|
||||
|
||||
+7
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "alktunnels"
|
||||
version = "0.2.0"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.88"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -9,7 +9,11 @@ repository = "https://git.alk.dev/alkdev/alktunnels"
|
||||
readme = "README.md"
|
||||
keywords = ["tunnel", "proxy", "channels", "alkcall", "network"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/research/", "docs/plans/", "tasks/", "docs/sdd_process.md"]
|
||||
exclude = [".opencode/", "AGENTS.md", "docs/reviews/", "docs/research/", "docs/plans/", "tasks/", "docs/sdd_process.md", "fuzz/"]
|
||||
|
||||
[workspace]
|
||||
members = ["."]
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[lib]
|
||||
name = "alktunnels"
|
||||
@@ -19,7 +23,7 @@ default = []
|
||||
local = ["tokio/net"]
|
||||
|
||||
[dependencies]
|
||||
alkcall = "0.8.0"
|
||||
alkcall = "0.10.0"
|
||||
tokio = { version = "1", default-features = false, features = ["rt", "sync", "io-util", "macros", "time"] }
|
||||
bytes = "1"
|
||||
futures = "0.3"
|
||||
|
||||
@@ -300,6 +300,29 @@ enabling it on wasm is a compile error.
|
||||
flavor-form op ids in discovery (hub relay wraps them as channels);
|
||||
bonus: in-tree `ChannelRelay`/`HubLegTemplate` (alkcall ADR-051).
|
||||
|
||||
0.2.1 is the hardening release: the `fuzz/` workspace (three
|
||||
cargo-fuzz targets over the datagram codec, the params parsers, and a
|
||||
stateful session-pump target; corpora replay as plain `cargo test`) —
|
||||
`docs/research/fuzzing.md` — and the alkcall bump 0.8.0 → 0.8.1
|
||||
(duplicate adopt/open no longer destroys the live channel — relevant
|
||||
to the reverse-path adopt flow). Two additive diagnostics:
|
||||
`TunnelSession::datagram_backlog()` and
|
||||
`DatagramReader::retained_bytes()`. No API break, no wire change.
|
||||
|
||||
0.3.0 rides the alkcall bump 0.8.1 → 0.9.0 (the stub-sweep release —
|
||||
the four registered-but-always-failing stubs became real
|
||||
implementations; none were on this crate's paths). No alktunnels API
|
||||
change, no wire change; minor rather than patch because the
|
||||
semver-incompatible dependency bump + this crate's alkcall type
|
||||
re-exports make it semver-visible downstream.
|
||||
|
||||
0.4.0 rides the alkcall bump 0.9.0 → 0.10.0 (the gateway
|
||||
ownership-seam release — the loud ownership-wiring default on the
|
||||
`GatewayDispatch` spine, behind the opt-in `gateway` feature; none of
|
||||
it is on this crate's paths, which serve via
|
||||
`ChannelCore::register_openable*` dispatch). No alktunnels API
|
||||
change, no wire change; same minor-not-patch reasoning as 0.3.0.
|
||||
|
||||
## Features
|
||||
|
||||
| Feature | Contents | wasm |
|
||||
|
||||
@@ -109,7 +109,7 @@ establisher + the forwarded op (ADR-008).
|
||||
|
||||
```
|
||||
alktunnels (default — wasm-clean)
|
||||
├── alkcall 0.8.0 (core types, channels: ChannelCore/ChannelClient/manager,
|
||||
├── alkcall 0.10.0 (core types, channels: ChannelCore/ChannelClient/manager,
|
||||
│ pump_bidi, ServingConfig identity seam — CF-005/006,
|
||||
│ establisher reply projection + flavor-form discovery
|
||||
│ derivation + ChannelRelay/HubLegTemplate — review 008/ADR-051)
|
||||
|
||||
@@ -0,0 +1,587 @@
|
||||
# Fuzzing alktunnels: Proposal and Plan
|
||||
|
||||
**Status:** adopted — wave-1 complete (`datagram_codec` +
|
||||
`params_parse` implemented, seeds committed, two 10-min campaigns
|
||||
clean, two harness-model findings fixed — see §7); wave-2
|
||||
(`session_pumps`) implemented and campaigning — see §3.3/§7.
|
||||
Campaigns run via the detached runner (§6) — **never as a foreground
|
||||
child of an agent session** (§5).
|
||||
**Date:** 2026-09-28
|
||||
**Upstream precedent:** `/workspace/@alkdev/alkcall/docs/research/fuzzing.md`
|
||||
(0.8.0 — 0.8.1 campaign; one real finding: duplicate adopt/open destroying
|
||||
the live channel, fixed in 0.8.1). Tooling, corpus policy, runner, and the
|
||||
no-hosted-CI posture are inherited from that doc wholesale — this doc does
|
||||
not re-litigate them; it scopes them to this crate.
|
||||
|
||||
---
|
||||
|
||||
## 1. Should this crate be fuzzed?
|
||||
|
||||
**Yes — but scoped smaller than alkcall's.** The decision follows from
|
||||
three lines, all pointing the same way:
|
||||
|
||||
1. **alkcall's fuzz doc already assigned this to us.** Its §5
|
||||
"non-surfaces (deliberately)" note ends: *the channels layer carries
|
||||
opaque `Bytes` and the handler owns sub-stream framing (ADR-035) —
|
||||
those downstream parsers are downstream crates' fuzz targets.* Our
|
||||
`wire.rs` datagram codec IS that named downstream target. Fuzzing it
|
||||
completes alkcall's fuzz scope rather than duplicating it.
|
||||
|
||||
2. **We own one hand-rolled parse surface of exactly the covered class.**
|
||||
`DatagramReader::feed` (`src/wire.rs:100`) is an incremental
|
||||
length-prefix state machine reassembling `[len: u16 BE][payload]`
|
||||
frames across arbitrary chunk boundaries — length arithmetic and
|
||||
boundary handling, the dominant rust-fuzz trophy-case classes. It is
|
||||
sync, pure, allocation-light, and infallible **by contract** (the
|
||||
decode direction cannot fail; oversize is rejected at frame time) —
|
||||
a fuzz target is how that contract is *demonstrated* rather than
|
||||
believed.
|
||||
|
||||
3. **The stateful DoS class applies to us directly.** One pre-fuzz
|
||||
code-review candidate, the analog of alkcall §6.2-1 (the
|
||||
early-arrival key-space): `DatagramHalves.pending`
|
||||
(`src/consumer.rs:78`) queues every datagram decoded from a chunk
|
||||
beyond the one returned to the caller. A single 16 MiB chunk of
|
||||
`[00 00]` pairs = ~8.4 M empty datagrams → ~8.4 M `VecDeque`
|
||||
entries (≈ 400+ MiB) from ONE peer chunk — a ~25–50× memory
|
||||
amplification riding a bounded channel buffer. Example-based tests
|
||||
structurally cannot find this; a byte-sequence target with a
|
||||
pending-bytes assertion will.
|
||||
|
||||
**The honest caveat** (mirroring alkcall §1): the highest-value alkcall
|
||||
targets — envelope framing, chunk header, demux, manager — are upstream
|
||||
and now fuzzed (0.8.1). Our params parsing is serde + registry-side
|
||||
jsonschema (upstream's `spec_parse` target covers attacker-shaped
|
||||
schemas compiling at registration). Expected yield here: datagram-codec
|
||||
pathologies, the pending-queue bound (§1.3), serde/untagged-enum edge
|
||||
cases in params, and lifecycle invariants in the two-pump shape. That
|
||||
is worthwhile, not five-targets-worth of new machinery on day one —
|
||||
hence the two-wave scope in §4. If the first campaigns find nothing,
|
||||
that is the successful-negative-result posture alkcall's §1 codified:
|
||||
"we think the codec is robust" becomes a demonstrated property, and the
|
||||
targets become regression protection for a wire-stable format
|
||||
(ADR-003).
|
||||
|
||||
## 2. Toolchain (inherited, not re-decided)
|
||||
|
||||
Identical to alkcall §4: **cargo-fuzz 0.13.2 + `arbitrary` 1.4.x**,
|
||||
nightly confined to the `fuzz/` subtree via `fuzz/rust-toolchain.toml`,
|
||||
the crate itself stays stable at MSRV 1.88. AFL optional/deferred;
|
||||
bolero deferred (same async-friction argument); libafl/honggfuzz/OSS-Fuzz
|
||||
not adopted. Sanitizer: `--sanitizer=address` with debug assertions
|
||||
(cargo-fuzz defaults) — no MSAN (no `unsafe` in this crate).
|
||||
|
||||
**Layout (the alkcall pattern, load-bearing):**
|
||||
|
||||
```
|
||||
fuzz/
|
||||
├── Cargo.toml alktunnels-fuzz (nightly-only bins; own [workspace])
|
||||
├── rust-toolchain.toml pins nightly for this subtree only
|
||||
├── fuzz_targets/ thin fuzz_target! wrappers (3-line each)
|
||||
│ ├── datagram_codec.rs
|
||||
│ ├── params_parse.rs
|
||||
│ └── session_pumps.rs
|
||||
├── shared/ alktunnels-fuzz-shared — STABLE-toolchain library
|
||||
│ └── src/… invariant logic + corpus replay (§3)
|
||||
├── corpus/<target>/ committed hand-made seeds (§3)
|
||||
├── gen_fuzz_seeds.py deterministic seed generator (quiche pattern)
|
||||
├── run-detached.sh §6 detached runner (CWD-independent)
|
||||
└── README.md
|
||||
```
|
||||
|
||||
- **Invariant logic lives in `fuzz/shared/`**, not inline in the target
|
||||
binaries — the stable-toolchain corpus replay
|
||||
(`cargo test --manifest-path fuzz/shared/Cargo.toml`) runs the SAME
|
||||
invariant functions the fuzzer runs, on stable, in the release
|
||||
verification checklist. This is CI tier-3's deliverable (alkcall
|
||||
§7.2) minus automation; per alkcall §7.9 this repo has no hosted CI
|
||||
and will not get one (same gitea posture) — corpus replay as plain
|
||||
`cargo test` is the fuzz gate.
|
||||
- **Root `Cargo.toml` needs the explicit `[workspace]` table**
|
||||
(`members = ["."]`, `exclude = ["fuzz"]`) and `"fuzz/"` on the
|
||||
publish exclude list — without it cargo auto-discovers `fuzz/shared/`
|
||||
into the main workspace and the MSRV toolchain tries to build the
|
||||
nightly-consumed dev-deps (the exact failure alkcall hit).
|
||||
- **Corpus policy:** commit hand-made seeds, gitignore grown corpora
|
||||
(quinn/h2 model). Seeds per target: valid frames, truncation at every
|
||||
short prefix length, `len = 0` / `len = MAX` / `len = MAX+1`, empty
|
||||
datagrams, batching (two frames per chunk), splitting (one frame per
|
||||
many chunks), and for params: every substrate value, unknown
|
||||
substrate, unknown/missing fields, `listen: 0`, malformed reply
|
||||
shapes, untagged cross-shape targets, deep/invalid JSON.
|
||||
|
||||
## 3. Invariants by target
|
||||
|
||||
### 3.1 `datagram_codec` (wave 1)
|
||||
|
||||
Input: raw `&[u8]` (and in the shared logic, chunk-slice parameter
|
||||
arbitrary). Drives `DatagramReader::feed` + `frame_datagram`
|
||||
(`src/wire.rs`). Invariants:
|
||||
|
||||
- **no-panic on any byte sequence** (the baseline; the one
|
||||
`unreachable!` in `feed` is a structural invariant of the state
|
||||
machine — the target is how it stays true).
|
||||
- **infallibility**: `feed` never errors (contract — oversize is
|
||||
rejected at frame time; the decode direction is infallible by
|
||||
design, `src/wire.rs` docs).
|
||||
- **datagram-size bound**: every yielded datagram is
|
||||
`<= MAX_DATAGRAM_LEN` (u16 frame field makes this structural — the
|
||||
target pins it against future refactors).
|
||||
- **no partial yields**: a datagram completes only when exactly
|
||||
`declared` payload bytes are buffered (`is_mid_datagram` observable
|
||||
consistency); `feed(&[])` never yields anything and never clears
|
||||
mid-datagram state.
|
||||
- **round-trip**: `feed(frame_datagram(x)) == [x]` for any x; the
|
||||
reverse direction `write(feed-parse(x))` re-frames to the same
|
||||
frame for any well-framed input.
|
||||
- **F-2 sentinel layering**: `len == 0` is a legal empty datagram,
|
||||
never EOF; the codec never emits zero-length datagrams for non-empty
|
||||
frames and never treats an empty datagram as stream end.
|
||||
- **consumption accounting**: re-framing all consumed bytes and
|
||||
feeding the byte-level remainder yields the same datagram stream
|
||||
(splitting a feed at any boundary is transparent).
|
||||
- **oversize frame rejection**: `frame_datagram` errors iff payload >
|
||||
`MAX_DATAGRAM_LEN`, echoing the sizes.
|
||||
- **pending-queue bound (the §1.3 finding-class assertion)**: total
|
||||
bytes retained across `buf` + `current` + `pending` after a feed is
|
||||
bounded by the fed chunk size plus the one in-flight frame —
|
||||
an adversarial peer chunk cannot force unbounded queue growth in
|
||||
the decoder state. (NOTE: this is the *decoder-state* bound; the
|
||||
consumer-side `DatagramHalves.pending` backlog between chunk-read
|
||||
and `recv_datagram` pulls is a session-shape question tracked in
|
||||
§4/§8 — the wave-2 stateful target, or a code fix if the bound is
|
||||
judged unacceptable. The fuzzer documents the amplification factor;
|
||||
the remediation decision is ours. **Wave-2 resolution (§3.3):** the
|
||||
session-shape backlog is now an asserted invariant — the harness's
|
||||
per-op drain holds the pending queue to the routed-batch remainder
|
||||
(≤ one chunk's worth of batched datagrams, drained by
|
||||
`recv_datagram` pulls), so the amplification question is bounded
|
||||
by construction + assertion rather than remediated; the
|
||||
`datagram_backlog()` diagnostic exposes the live count.)
|
||||
|
||||
### 3.2 `params_parse` (wave 1)
|
||||
|
||||
Input: raw bytes → `serde_json` (with alkcall's bounded
|
||||
`arbitrary_value` approach — depth/width-capped `Value` generation) →
|
||||
`TunnelParams` / `TunnelDirectParams` / `TunnelForwardedParams` /
|
||||
`SubstrateAddr` deserialization + `parse_listen_reply` /
|
||||
`listen_reply_fields` (`src/params.rs`). Invariants:
|
||||
|
||||
- **no-panic on any JSON-shaped input** (untagged-enum serde dispatch,
|
||||
`deny_unknown_fields`, the `positive_u32` pin).
|
||||
- **clean failure only**: parse failures are `Err`, never panics;
|
||||
`parse_listen_reply` returns `TunnelError::MalformedReply` on
|
||||
malformed shapes.
|
||||
- **round-trip for valid params**: `from_json(to_json(p)) == p` for
|
||||
constructed params of every substrate.
|
||||
- **deny-unknown posture**: unknown top-level fields reject;
|
||||
untagged-variant leniency (extra fields inside a `SubstrateAddr`
|
||||
variant) is the documented asymmetry — asserted, not "fixed".
|
||||
- **the `listen: 0` pin**: zero rejects at serde level.
|
||||
- **projection agreement**: `listen_reply_fields` yields both-`Some`
|
||||
iff `parse_listen_reply` is `Ok`; it never panics and never refuses
|
||||
construction (the lenient view projects malformed to both-`None`).
|
||||
- **establishment-reason surface**: `establishment_reason` never
|
||||
panics on arbitrary `ChannelOpenError` shapes.
|
||||
|
||||
### 3.3 `session_pumps` (wave 2)
|
||||
|
||||
Input: op sequences (`#[derive(Arbitrary)]` `SessionSequence` — the
|
||||
`manager_routing` pattern) over one live channels stack per exec:
|
||||
MuxRunner → raw chunk stream → the real `run_demux_loop_for_client`
|
||||
demux → `ChannelManager` per side (producer Connect / consumer
|
||||
Accept over one duplex transport — the reverse POC's topology). Ops:
|
||||
`Route` (producer→consumer framed datagrams / raw stream bytes via
|
||||
`route_payload`), `AdoptDatagram` / `AdoptStream` (the
|
||||
`TunnelSession` data-plane mirrors — codec reader + `pending`, and
|
||||
`pump_bidi` against in-process peer halves), `Pump` (peer→channel;
|
||||
`len == 0` drops the p2c source — the EOF path), `Teardown`,
|
||||
`ClearAll`. Invariants:
|
||||
|
||||
- **no-panic / no-wedge on any op sequence** (the baseline; the
|
||||
harness's 5 s reconcile bound turns a wedged pump into a finding,
|
||||
and §5's `-timeout=25` fork containment is the outer net).
|
||||
- **two-pump shutdown-on-completion (convention 8)**: after the p2c
|
||||
source drops, the pump's channel-side shutdown must propagate and
|
||||
the pump must complete once the teardown EOFs the c2p source —
|
||||
pinned by the `reconcile` join with the 5 s bound.
|
||||
- **lossless p2c copy**: the pump's `p2c` count equals exactly what
|
||||
the harness pushed into the peer half (copy never truncates).
|
||||
- **EOF-sentinel teardown semantics**: after a sentinel (teardown /
|
||||
`clear_all`), the reassembled stream drains its queue before
|
||||
reporting EOF — every routed datagram consumable, nothing lost;
|
||||
the consumer map empties; the codec's `len == 0` sentinel layering
|
||||
holds at the session layer (a routed empty datagram is data; the
|
||||
channels EOF is a separate mechanism).
|
||||
- **FIFO reconciliation**: every complete frame routed through the
|
||||
real demux path arrives as exactly one datagram of the declared
|
||||
length, in order (the model walks the framing as the codec does —
|
||||
the wave-1 F-2 walk, reused at the session layer).
|
||||
- **pending-queue bound (the §1.3 finding-class assertion)**: the
|
||||
session-shape `DatagramHalves.pending` backlog never exceeds the
|
||||
unrouted remainder — i.e. the backlog holds only datagrams decoded
|
||||
from routed chunks beyond the ones consumed; with the harness's
|
||||
per-op drain the queue returns to zero after every op, so an
|
||||
amplification factor > 1× chunk size would trip the assert. (The
|
||||
wave-1 decoder-state bound re-asserted per op alongside.)
|
||||
- **monotonicity**: the manager's early-arrival / dropped counters
|
||||
survive `clear_all` (the alkcall §7.7 pattern), and the consumer
|
||||
map empties on the transport-EOF path.
|
||||
|
||||
Bounds: 128 ops, ≤ 512-byte payloads (generator-clamped; the codec's
|
||||
`MAX_DATAGRAM_LEN` stays the wire bound). Even-id masking (ADR-047
|
||||
§5 — the consumer is the Accept side; the producer allocates even).
|
||||
|
||||
## 4. Scope: two waves
|
||||
|
||||
**Wave 1 (complete):** the two targets above. Both drive already-
|
||||
`pub` APIs — no `#[cfg(fuzzing)]` exposure needed (matching alkcall's
|
||||
experience where none of five targets needed it).
|
||||
|
||||
**Wave 2 (this session):** `session_pumps`, the stateful op-sequence
|
||||
target over the two-pump tunnel shape in the alkcall `manager_routing`
|
||||
style (`#[derive(Arbitrary)]` op enum; live mux + demux runners; the
|
||||
alkcall harness lessons applied: even-id masking, drained-state
|
||||
asserts). Drives: the `TunnelSession` data-plane shapes (the datagram
|
||||
codec plane + the `pump_bidi` stream plane) over a REAL channels
|
||||
stack — the two-pump shutdown-on-completion contract (AGENTS.md
|
||||
convention 8), the EOF-sentinel teardown semantics, and the §1.3
|
||||
backlog bound as byte-accounting invariants (§3.3). The
|
||||
`forwarded_routing` correlation target stays deferred until the
|
||||
assembly-side correlation registry (ADR-008 §3, assembly
|
||||
bookkeeping) grows into this crate.
|
||||
|
||||
Sequencing mirrors alkcall §7.4: init + wave-1 targets → 10–30 min
|
||||
campaigns per target via the detached runner → fix anything found →
|
||||
wave 2 (`session_pumps`) → further targets when justified.
|
||||
|
||||
## 5. Operational isolation: campaigns never share fate with the agent session
|
||||
|
||||
**This is an environment constraint, not a nice-to-have** (upstream
|
||||
doc §7.6, adopted verbatim as policy here). Agent sessions (opencode)
|
||||
run fuzzer builds/campaigns through a bash tool that spawns the fuzzer
|
||||
as a **child of the session's own process tree and cgroup**. A fuzz
|
||||
run is precisely the workload shape that kills its own host:
|
||||
|
||||
- **OOM-killer shared fate**: the target's worst case IS unbounded
|
||||
allocation. If libFuzzer's soft RSS guard loses the race with the
|
||||
kernel, the OOM killer fires — and it picks the largest-RSS process
|
||||
in the cgroup, which can be the opencode server hosting the session.
|
||||
That is *recoverable* (sessions are recorded in a DB) but hugely
|
||||
derailing — the session's live environment access dies mid-task.
|
||||
An OOM in a fuzz target must cost the fuzzer, never the session.
|
||||
- **Fork bombs / CPU saturation**: `-fork=N` workers; a runaway
|
||||
campaign starves the session host.
|
||||
- **Wedged targets**: a deadlock in an async harness would hang a
|
||||
foreground bash call forever.
|
||||
|
||||
Three layers of defense, cheapest first — **all mandatory**:
|
||||
|
||||
1. **libFuzzer soft limits (always on)**: `-rss_limit_mb=2048`
|
||||
`-malloc_limit_mb=2048` make libFuzzer exit cleanly (artifact
|
||||
recorded) before the kernel gets involved. Note the RSS limit is
|
||||
*soft* (polls `/proc/self/statm`); a fast huge allocation can beat
|
||||
it — that is what layers 2–3 are for. Never `ulimit -v` with ASAN
|
||||
(it reserves terabytes of virtual address space); the ASAN-safe
|
||||
hard-limit recipe is `-fork=1`.
|
||||
2. **Fork mode as blast-radius containment (default)**: `-fork=1`
|
||||
(or a small number) runs each input in a short-lived child — a
|
||||
crash/timeout/OOM/leak kills only that child; the harness survives,
|
||||
records the artifact, keeps fuzzing. Also the deadlock answer: the
|
||||
per-input timeout kills the child, not the session's bash call.
|
||||
3. **Process detachment from the session tree (MANDATORY for agent-run
|
||||
campaigns)**: never run fuzzing as a foreground child of the
|
||||
session. The runner script wraps `cargo fuzz run` in `setsid` +
|
||||
`nohup` + full redirection to a log file: (a) removes the fuzzer
|
||||
from the session's controlling terminal and signal relation, (b)
|
||||
survives the session ending, (c) gives the agent a pollable log +
|
||||
artifact directory instead of a blocking call. Combined with fork
|
||||
mode, an OOM-class finding costs one child process.
|
||||
|
||||
**The runner** (`fuzz/run-detached.sh`, CWD-independent — resolves its
|
||||
own repo root):
|
||||
|
||||
```bash
|
||||
#!/usr/bin/env bash
|
||||
# Detached fuzzing runner for agent sessions: the campaign never runs
|
||||
# as a foreground child of the session (an OOM in a target must cost
|
||||
# the fuzzer, never the agent host), and survives the session ending.
|
||||
set -euo pipefail
|
||||
target="${1:?usage: run-detached.sh <target> [extra libfuzzer args...]}"
|
||||
shift
|
||||
runtime="${FUZZ_RUNTIME_SECS:-600}"
|
||||
root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
log_dir="$root/fuzz/artifacts"
|
||||
mkdir -p "$log_dir"
|
||||
log="$log_dir/${target}-$(date -u +%Y%m%d-%H%M%S).log"
|
||||
cd "$root"
|
||||
setsid nohup cargo fuzz run "$target" -- \
|
||||
-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25 \
|
||||
-max_total_time="$runtime" "$@" \
|
||||
>"$log" 2>&1 < /dev/null &
|
||||
echo "pid=$! log=$log"
|
||||
```
|
||||
|
||||
**Agent polling protocol** (never `wait` on the detached process from a
|
||||
tool call — poll instead):
|
||||
|
||||
- `tail -n 50 fuzz/artifacts/<target>-*.log` — campaign progress.
|
||||
- `ls fuzz/artifacts/` — `crash-*` / `oom-*` / `timeout-*` / `leak-*`
|
||||
artifacts are the findings.
|
||||
- `pgrep -f "cargo fuzz run <target>"` — still running?
|
||||
- One detached campaign per target at a time; log filenames carry the
|
||||
UTC timestamp.
|
||||
|
||||
**Flags worth pinning beyond the runner defaults**: `-max_len=65536`
|
||||
on the codec target; `-use_value_profile=1`; a JSON token `-dict` for
|
||||
the params target (alkcall's `json.dict` transfers as-is).
|
||||
|
||||
## 6. No hosted CI
|
||||
|
||||
Per alkcall §7.9 — the standing policy applies to this repo identically:
|
||||
no hosted CI will exist; do not add Actions/Gitea-Actions workflow
|
||||
files. **Corpus replay is the fuzz gate as plain `cargo test`**:
|
||||
`cargo test --manifest-path fuzz/shared/Cargo.toml` replays every
|
||||
committed seed through the invariant functions — add it to the release
|
||||
verification checklist (AGENTS.md). Campaigns run manually via the
|
||||
detached runner (§6) — e.g. before a release, or after touching
|
||||
`src/wire.rs`, the consumer data planes, or `src/params.rs`.
|
||||
|
||||
## 7. Implementation log
|
||||
|
||||
- **2026-09-28 — proposal adopted** (this doc). Wave-1 targets
|
||||
`datagram_codec` + `params_parse`; wave-2 deferred (§4).
|
||||
- **2026-09-28 — `alkcall` bumped 0.8.0 → 0.8.1.** Bug-fix release, no
|
||||
API/wire change; the fix (duplicate adopt/open no longer destroying
|
||||
the live channel) is directly relevant to this crate's
|
||||
`TunnelSession::adopt` path — the changelog's "open-op response
|
||||
replayed / id re-announced" scenario is our reverse path. Verification:
|
||||
full suite (135 passed), clippy, fmt, wasm check (§8 commands).
|
||||
- **2026-09-28 — wave-1 implemented.** `fuzz/` workspace laid out per
|
||||
§2 (nightly-pinned subtree, stable `fuzz/shared` library, runner,
|
||||
dict, generator); `datagram_codec` + `params_parse` targets over
|
||||
already-`pub` APIs — no `#[cfg(fuzzing)]` exposure needed. One new
|
||||
public diagnostic: `DatagramReader::retained_bytes()` (the
|
||||
decoder-state byte count the §3.1 bound asserts against, in the
|
||||
`is_mid_datagram` diagnostic style). Seeds committed: 27 +
|
||||
69 generated (fuzz/gen_fuzz_seeds.py) + 1 campaign-found crash
|
||||
input promoted to `datagram_codec/seed-900` (see the finding below).
|
||||
Stable side: corpus replay green, shared crate clippy/fmt clean;
|
||||
main suite untouched and green; `cargo fuzz build` clean.
|
||||
- **2026-09-28 — first campaign finding: a HARNESS-model bug, not a
|
||||
crate bug.** The `datagram_codec` campaign crashed in its first
|
||||
minute on input `00 2b 01 00 00 05` (`fuzz/artifacts/datagram_codec/
|
||||
crash-e9ba284e…`, exit 77, fork-mode containment worked as
|
||||
designed — the harness parent survived and persisted the artifact).
|
||||
Analysis: the codec is CORRECT — `00 2b` declares a 43-byte
|
||||
datagram; `01 00 00 05` is its in-flight payload; nothing is
|
||||
yielded. The invariant that tripped ("an empty datagram must come
|
||||
only from a [00 00] length header") was mis-modeled: it counted
|
||||
`00 00` byte pairs anywhere in the RAW stream, but a `00 00` pair
|
||||
sitting INSIDE a frame's payload region is payload, not a length
|
||||
header — exactly the same class as alkcall §7.7's probe (the
|
||||
harness claimed `4 + len` when the prefix counts only the body).
|
||||
Fix: the count now walks the framing as the decoder does (complete
|
||||
frames from the start; the walk stops where the decoder would be
|
||||
stuck mid-frame). The crash input is committed as seed-900 and
|
||||
replays clean; the campaign relaunched clean.
|
||||
- **2026-09-28 — second harness finding (caught by corpus replay,
|
||||
pre-campaign): `establishment_reason` is a pass-through extractor,
|
||||
not a validated table.** The params.rs doc names the pinned reason
|
||||
strings, but the upstream implementation (alkcall
|
||||
`channels/client.rs` `establishment_reason`) extracts
|
||||
`details.reason` AS-IS whenever the code is `channel:open_failed` —
|
||||
a made-up reason is surfaced as-is by design, and validation is the
|
||||
consumer's job. The harness first asserted a closed set of reason
|
||||
strings and corpus replay tripped it on `made_up_reason` before any
|
||||
campaign ran. Invariant corrected to assert the extraction shape
|
||||
(Some iff code == `channel:open_failed` and reason is a string).
|
||||
- **2026-09-28 — second harness finding (same replay pass):
|
||||
`is_mid_datagram` means a partial DATAGRAM, not buffered header
|
||||
bytes.** A dangling 1-byte length prefix at input end is buffered
|
||||
header state (no frame in flight, `is_mid_datagram == false`);
|
||||
the harness first asserted `true` for that case. Corrected:
|
||||
mid-datagram iff the framing walk leaves a remainder ≥ 2 bytes
|
||||
(a parsed-but-unfinished payload), not `!= 0`.
|
||||
- **2026-09-28 — wave-1 campaigns run (10-min detached runs, §6 runner;
|
||||
`-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048 -timeout=25`;
|
||||
codec with `-max_len=65536`, params with `-dict=json.dict
|
||||
-use_value_profile=1`). Both exited 0.**
|
||||
- `params_parse` — 3.14M execs (~5.3k exec/s), 33 fork jobs,
|
||||
`oom/timeout/crash: 0/0/0` throughout. Coverage 2049 edges /
|
||||
15.6k features; in-memory corpus 69 committed seeds → 3018
|
||||
entries — the serde/untagged-enum shape space was still expanding
|
||||
at budget end, so longer campaigns keep paying here.
|
||||
- `datagram_codec` (post-harness-fix) — 827k execs, 33 fork jobs,
|
||||
`oom/timeout/crash: 0/0/0` throughout. Coverage saturated at 221
|
||||
edges (ft 1198) — the 2-byte-header state machine is fully
|
||||
enumerated, matching alkcall §7.7's `chunk_header` saturation
|
||||
profile. Exec/s reads low (~400) because each exec replays the
|
||||
input through a ~35-feed 3-byte-sliced sequence alongside the
|
||||
whole-input feed; the state space is small and exhausted early.
|
||||
- The pre-fix `datagram_codec` run (the §7 finding above) crashed at
|
||||
job 4 and exited 77; fork-mode containment behaved as designed —
|
||||
the harness survived, persisted the artifact, and the run wrapped
|
||||
up. That artifact is the promoted seed-900.
|
||||
- Grown corpora (3018 params entries) were **not** merged into the
|
||||
committed seeds — per the §2 policy, grown corpora stay
|
||||
gitignored.
|
||||
- Operational note for future sessions: `cargo fuzz run` returns
|
||||
before its last fork-mode children fully exit; a `pkill -f
|
||||
"cargo fuzz run <target>"` followed immediately by another
|
||||
`pgrep` of the same pattern can wedge a bash tool call on its own
|
||||
process match — prefer `pgrep -af` to LIST, kill by explicit pid
|
||||
from the runner's echoed `pid=`, and never pattern-kill-and-recheck
|
||||
in one call.
|
||||
- **NO crate bugs found by wave 1.** The two findings this session
|
||||
were both harness-model defects (§7 above), each invisible to
|
||||
example-based review and caught by fuzzing/replay — the alkcall §7.7
|
||||
"fuzzer as a harness-checker" pattern, confirmed. The §1.3
|
||||
decoder-state bound held everywhere: `retained_bytes()` never
|
||||
exceeded fed + one frame on any adversarial input. The session-shape
|
||||
`DatagramHalves.pending` amplification remains a documented
|
||||
§1.3/§4 note (wave-2's motivation), not a campaign finding — the
|
||||
wave-1 targets assert the decoder, not the session pump.
|
||||
- **2026-09-28 — wave-2 implemented: `session_pumps` (the stateful
|
||||
two-pump/EOF-sentinel target, §3.3).** The harness mirrors
|
||||
`TunnelSession`'s data-plane shapes over one REAL channels stack
|
||||
per exec (MuxRunner → raw chunk stream → the real
|
||||
`run_demux_loop_for_client` demux → `ChannelManager` per side over
|
||||
one duplex transport; producer Connect / consumer Accept — the
|
||||
reverse POC's topology). Ops: Route/AdoptDatagram/AdoptStream/
|
||||
Pump/Teardown/ClearAll; invariants per §3.3. One diagnostic
|
||||
addition to the crate: `TunnelSession::datagram_backlog()` (the
|
||||
`DatagramHalves.pending` count the §3.3 bound asserts against, in
|
||||
the `retained_bytes()` diagnostic style) + a suite test pinning
|
||||
its rest states. Seeds: 8 committed (deterministic decode-scan
|
||||
hex patterns, the `manager_routing` precedent — arbitrary ships
|
||||
no encoder) + 2 in-source semantic fixtures (the §1.3 batched-
|
||||
backlog probe; the W4 half-close shape). Two harness-model
|
||||
findings caught by corpus replay pre-campaign:
|
||||
- **the p2c-EOF pump-join wedge is the DESIGNED half-close
|
||||
shape, not a wedge.** The first `reconcile` draft awaited the
|
||||
pump after dropping only the p2c source — but the c2p pump's
|
||||
source (the channel read half) EOFs only via teardown, so the
|
||||
join legitimately blocked (the 5 s bound tripped). `pump_bidi`
|
||||
completes when BOTH pumps finish; the half-close contract is
|
||||
that the p2c completion shuts the channel side down (the
|
||||
observable), and the pump completes once the c2p source EOFs
|
||||
too. The harness now models exactly that: drop p2c source →
|
||||
teardown the entry → join with the 5 s bound. (The alkcall §7.7
|
||||
harness-checker pattern again — the fuzzer harness's first
|
||||
campaign-found crash in wave 1 and this replay catch are both
|
||||
harness-model defects, not crate bugs.)
|
||||
- **the stream-plane c2p accounting is asynchronous** — the pump
|
||||
writes channel→peer into the peer duplex while a far-end reader
|
||||
task counts; the reconcile join (after the pump completes) is
|
||||
when the count is final. First draft read it early. Fixed by
|
||||
reading after the reader task's join.
|
||||
- **2026-09-28 — third harness finding (campaign-found, fork-mode
|
||||
containment as designed): duplicate adopt is a LEGAL manager
|
||||
outcome, not a finding.** The first campaign crashed at ~49 s on
|
||||
`AdoptDatagram { channel_id: 0 }` after the id had already been
|
||||
adopted — `ChannelExists` is the manager's documented
|
||||
id-collision error, and my adopt arms treated every error as a
|
||||
finding. Fix: both adopt arms model the documented error table
|
||||
(`ChannelExists` / `TooManyChannels` are no-ops, exactly the
|
||||
alkcall `manager_routing` harness's treatment). The crash input
|
||||
is committed as seed-900 and replays clean; the campaign
|
||||
relaunched clean.
|
||||
- **2026-09-28 — fourth harness finding (second campaign): the
|
||||
harness modeled the producer→consumer path through the WRONG
|
||||
primitive.** The second campaign crashed at ~5 s on `Route`:
|
||||
raw-id addressing (`even_id` masked arbitrary ids; collisions +
|
||||
an `UnknownChannel`-class state the model didn't track), `len ==
|
||||
0` raw routes doubling as EOF sentinels mid-model, and — the
|
||||
structural one — `route_payload` was used as the producer's
|
||||
forward write. `route_payload` routes into the LOCAL manager's
|
||||
channel entry (the demux direction); the producer pump handler
|
||||
writes the channel's SEND half (the mux pump frames onto the
|
||||
transport). Bytes never reached the consumer. Rewrite (the real
|
||||
protocol shape, matching `manager_routing`'s op-vocabulary
|
||||
lesson): producer `Open` (`open_channel` allocates the id, the
|
||||
harness holds the send half — the pump handler's write side),
|
||||
session-INDEX addressing (no masking, no collisions), `Adopt`
|
||||
consumes the producer-allocated id (the early-arrival drain is
|
||||
the real adopt-race path), `Route` writes the producer send half
|
||||
(the real wire path: mux pump → transport → consumer demux →
|
||||
reassembly), `Pull` (the `recv_datagram` pull pops the backlog +
|
||||
the model), and a STATEFUL framing model (partial frames persist
|
||||
across ops — the wave-1 walk carried across ops). EOF sentinels
|
||||
now arise only from teardown/sender-drop (the real mechanisms),
|
||||
never from `len == 0` routes (F-2: a routed empty datagram is
|
||||
DATA). All committed session_pumps seeds regenerated by the
|
||||
rewrite (the old hex patterns decode against the new op enum);
|
||||
the semantic fixtures re-pinned. Verification of the fix: the
|
||||
campaign's 0-exec crash went away; batched frames now reconcile
|
||||
FIFO through the real two-manager stack.
|
||||
- **2026-09-28 — fifth + sixth harness findings (third campaign's
|
||||
first minutes): the two ClearAll modeling defects.** (5) A
|
||||
double-adopt of a live session panicked the harness's own guard —
|
||||
but a re-adopt of a LIVE id is the manager's documented
|
||||
`ChannelExists` outcome (a legal no-op; alkcall 0.8.1's
|
||||
insert-replace fix leaves the live entry intact). Modeled as a
|
||||
no-op. (6) A `ClearAll` followed by `Route` on the same session
|
||||
tripped the backlog reconciliation — the harness retired only the
|
||||
sessions whose ids appeared in `clear_all`'s RETURN value, but
|
||||
that value is the OPENER-LEDGER records (ADR-047 §7): adopted
|
||||
channels carry no ledger entry, so the set is empty while the map
|
||||
itself empties. The alkcall §7.7 lesson ("`drained.len() ==
|
||||
open_count()` is not an invariant"), learned again at this
|
||||
crate's layer — the map-empties assert is the ground truth; the
|
||||
harness now retires EVERY session on `ClearAll`.
|
||||
- **2026-09-28 — wave-2 campaign run (10-min detached run, §6
|
||||
runner; `-fork=1 -rss_limit_mb=2048 -malloc_limit_mb=2048
|
||||
-timeout=25 -max_len=4096 -use_value_profile=1`): exited 0.**
|
||||
228,676 execs (~380 exec/s; each exec replays up to 128 ops on a
|
||||
live two-manager stack with real mux/demux tasks), 32 fork jobs,
|
||||
`oom/timeout/crash: 0/0/0` throughout. Coverage 3427 edges /
|
||||
17,529 features / 1184 in-memory corpus entries and STILL
|
||||
EXPANDING at budget end (the op-interleaving space is much larger
|
||||
than the parser targets') — longer campaigns keep paying. The
|
||||
two-pump contract held across every adversarial sequence: no
|
||||
wedged pump (the 5 s reconcile join), no lost datagram (the
|
||||
FIFO/backlog reconciliation), no premature EOF (the sentinel
|
||||
layering). The §1.3 backlog bound held everywhere: the
|
||||
`DatagramHalves.pending` queue never exceeded the
|
||||
routed-but-unpulled remainder.
|
||||
- **NO crate bugs found by wave 2.** All six findings across waves
|
||||
1–2 were harness-model defects (four this session), each
|
||||
invisible to example-based review and caught by fuzzing/replay —
|
||||
the alkcall §7.7 "fuzzer as a harness-checker" pattern,
|
||||
re-confirmed. The stateful thesis is validated from the harness
|
||||
side: the harness defects themselves (double-adopt semantics, the
|
||||
clear_all ledger/map distinction, the route_payload direction)
|
||||
are exactly the interleaving-shape mistakes the alkcall §7.8
|
||||
campaigns caught in their own drivers.
|
||||
- (progress appended as implementation proceeds — see git log)
|
||||
|
||||
## 8. Verification commands
|
||||
|
||||
```bash
|
||||
cargo test # main suite (stable)
|
||||
cargo test --manifest-path fuzz/shared/Cargo.toml # corpus replay (fuzz gate)
|
||||
cargo fuzz build # in fuzz/ (nightly, ASAN)
|
||||
fuzz/run-detached.sh <target> # campaigns (never foreground)
|
||||
cargo clippy --all-targets -- -D warnings
|
||||
cargo fmt --check
|
||||
cargo clippy --manifest-path fuzz/shared/Cargo.toml --all-targets -- -D warnings
|
||||
cargo fmt --manifest-path fuzz/shared/Cargo.toml --check
|
||||
cargo check --target wasm32-unknown-unknown # default crate stays wasm-clean
|
||||
```
|
||||
|
||||
Campaign flags per target: `datagram_codec -max_len=65536`;
|
||||
`params_parse -dict=json.dict -use_value_profile=1`;
|
||||
`session_pumps -max_len=4096 -use_value_profile=1`.
|
||||
|
||||
## 9. References
|
||||
|
||||
- Upstream campaign + operational model (authoritative for tooling,
|
||||
runner, corpus policy, no-CI posture):
|
||||
`/workspace/@alkdev/alkcall/docs/research/fuzzing.md`
|
||||
- alkcall 0.8.1 changelog — the finding our adopt path is exposed to
|
||||
- This crate: ADR-003 (codec framing), ADR-002 (ALPN), ADR-007/008
|
||||
(direct/forwarded ops), `docs/architecture/wire.md`
|
||||
- External: RUSTSEC-2026-0037 (quinn-proto — the maintainers'
|
||||
fuzzing-coverage statement), rust-fuzz trophy case, cargo-fuzz Rust
|
||||
Fuzz Book (all linked from the upstream doc's §9)
|
||||
@@ -0,0 +1,10 @@
|
||||
target
|
||||
corpus/*
|
||||
!corpus/datagram_codec
|
||||
!corpus/params_parse
|
||||
!corpus/session_pumps
|
||||
# Grown corpus entries (libFuzzer hash-named) are gitignored; only the
|
||||
# hand-made seed-* files are committed (the quinn/h2 policy).
|
||||
corpus/*/[0-9a-f]*
|
||||
artifacts
|
||||
coverage
|
||||
Generated
+1297
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,38 @@
|
||||
[package]
|
||||
name = "alktunnels-fuzz"
|
||||
version = "0.0.0"
|
||||
publish = false
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata]
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
libfuzzer-sys = "0.4"
|
||||
alktunnels-fuzz-shared = { path = "shared" }
|
||||
|
||||
[dependencies.alktunnels]
|
||||
path = ".."
|
||||
|
||||
[[bin]]
|
||||
name = "datagram_codec"
|
||||
path = "fuzz_targets/datagram_codec.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "params_parse"
|
||||
path = "fuzz_targets/params_parse.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "session_pumps"
|
||||
path = "fuzz_targets/session_pumps.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[workspace]
|
||||
@@ -0,0 +1,58 @@
|
||||
# alktunnels fuzzing
|
||||
|
||||
libFuzzer targets for alktunnels' untrusted-input surfaces (see
|
||||
`docs/research/fuzzing.md` for the full rationale, scope decision, and
|
||||
campaign plan; the tooling/corpus/runner/no-CI posture is inherited
|
||||
from alkcall's `docs/research/fuzzing.md`).
|
||||
|
||||
## Layout
|
||||
|
||||
- `fuzz_targets/` — one binary per target; thin `fuzz_target!` wrappers.
|
||||
- `shared/` — the invariant logic, as a plain library so normal
|
||||
`cargo test` (stable toolchain) can replay the committed corpora
|
||||
through the same invariants (`fuzz/shared/src/*.rs` `#[cfg(test)]`
|
||||
modules; quinn's CI pattern). The fuzz binaries are nightly-only;
|
||||
the shared crate is stable-clean.
|
||||
- `corpus/<target>/` — committed hand-made seeds (regenerate with
|
||||
`python3 fuzz/gen_fuzz_seeds.py` from the repo root). Grown corpora
|
||||
and artifacts are gitignored.
|
||||
- `run-detached.sh` — mandatory runner for agent sessions: wraps
|
||||
`cargo fuzz run` in `setsid` + `nohup` + log redirection so an OOM
|
||||
in a target can never take down the agent host (fuzzing.md §5).
|
||||
- `json.dict` — JSON token dictionary for the params target
|
||||
(transferred from alkcall).
|
||||
|
||||
## Targets
|
||||
|
||||
| Target | Drives | Invariants |
|
||||
|---|---|---|
|
||||
| `datagram_codec` | `DatagramReader::feed` / `frame_datagram` (`src/wire.rs`) | no-panic; infallible decode; yielded datagrams ≤ `MAX_DATAGRAM_LEN`; F-2 sentinel layering (empty datagram ≠ EOF, only from `[00 00]` headers); splitting transparency (3-byte slices == whole-input feed); re-frame round-trip; oversize rejection exactly at the bound; `is_mid_datagram` tracks framing; decoder-state retained-bytes bound |
|
||||
| `params_parse` | `TunnelParams` / `TunnelDirectParams` / `TunnelForwardedParams` / `SubstrateAddr` serde parses + `parse_listen_reply` / `listen_reply_fields` / `establishment_reason` (`src/params.rs`) | no-panic on raw bytes and structured Values; clean typed failure only; round-trip for valid params; `listen: 0` pin; loud/lenient projection-pair agreement; pinned establishment-reason table |
|
||||
| `session_pumps` | op sequences (`SessionSequence`) over one live channels stack per exec (MuxRunner → real demux → `ChannelManager` per side; the `TunnelSession` data-plane mirrors — codec plane + `pump_bidi` stream planes) | no-panic / no-wedge (5 s reconcile bound); two-pump shutdown-on-completion; lossless p2c copy count; EOF-sentinel teardown semantics (drain-before-EOF, map empties); FIFO datagram reconciliation (framing-walk model); `pending` backlog ≤ routed-batch remainder (the §1.3 bound); decoder-state retained-bytes bound re-asserted per op; monotonic counters |
|
||||
|
||||
## Commands
|
||||
|
||||
`cargo fuzz build` and `cargo fuzz run` must be executed with `fuzz/`
|
||||
(or deeper) as the working directory so rustup selects the pinned
|
||||
nightly toolchain — the detached runner handles that itself.
|
||||
|
||||
```sh
|
||||
# build (nightly, pinned by rust-toolchain.toml inside fuzz/; run from fuzz/)
|
||||
cargo fuzz build
|
||||
|
||||
# agent sessions: detached campaign (never foreground; CWD-independent)
|
||||
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh datagram_codec -max_len=65536
|
||||
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh params_parse -dict=json.dict -use_value_profile=1
|
||||
FUZZ_RUNTIME_SECS=600 fuzz/run-detached.sh session_pumps -max_len=4096 -use_value_profile=1
|
||||
|
||||
# corpus replay through the invariants (stable toolchain, no nightly)
|
||||
cargo test --manifest-path fuzz/shared/Cargo.toml
|
||||
|
||||
# coverage
|
||||
cargo fuzz coverage <target>
|
||||
```
|
||||
|
||||
The fuzz workspace is excluded from the main workspace and from the
|
||||
published package (`exclude` in the root `Cargo.toml`); it pins its own
|
||||
nightly toolchain via `rust-toolchain.toml` and does not affect the
|
||||
crate's stable MSRV.
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
��bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbLine truncated
|
||||
Whitespace-only changes.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
��
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
��
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
4
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "postgres-primary", "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "postgres-primary", "substrate": "udp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "sock", "substrate": "unix"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "r", "substrate": "tcp", "extra": true}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "r"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "", "substrate": ""}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": null, "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": 1, "substrate": 2}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": {"host": "db.internal", "port": 5432}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "udp", "target": {"host": "10.0.0.7", "port": 53}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "unix", "target": {"path": "/run/x.sock"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": {"port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": {"host": "h"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "unix", "target": {"path": "/p", "host": "h"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": {"host": "h", "port": 65536}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": {"host": "h", "port": -1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": [1, 2]}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "unix", "target": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "target": {}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"target": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 918273, "substrate": "tcp", "peer": {"host": "198.51.100.4", "port": 55231}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 918273, "substrate": "unix", "peer": {"path": "/run/x.sock"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 0, "substrate": "tcp", "peer": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": -1, "substrate": "tcp", "peer": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 1, "substrate": "tcp", "peer": {"host": "h", "port": 1}, "channel": 7}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 4294967296, "substrate": "tcp", "peer": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": "token", "substrate": "tcp", "peer": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": "tcp", "peer": {"host": "h", "port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 1, "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"listen": 1, "substrate": "unix", "peer": {"path": "/p", "host": "h"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "bound": {"host": "203.0.113.9", "port": 42113}, "listen": 918273}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "bound": {"path": "/run/x.sock"}, "listen": 5, "future_field": {"n": true}}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "bound": {"port": 1}}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "listen": 0}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "listen": "token"}
|
||||
@@ -0,0 +1 @@
|
||||
{"bound": {"path": "/p"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": -7}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7.5}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "bound": 5}
|
||||
@@ -0,0 +1 @@
|
||||
{"channel_id": 7, "listen": 4294967296}
|
||||
@@ -0,0 +1 @@
|
||||
{}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:open_failed", "message": "establishment failed", "details": {"reason": "dial_failed"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:open_failed", "details": {"reason": "unknown_resource"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:open_failed", "details": {"reason": "resource_shortage"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:open_failed", "details": {"reason": "handler_error"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:open_failed", "details": {"reason": "timeout"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:open_failed", "details": {"reason": "made_up_reason"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "forbidden"}
|
||||
@@ -0,0 +1 @@
|
||||
{"code": "channel:too_many_channels", "details": {"reason": "dial_failed"}}
|
||||
@@ -0,0 +1 @@
|
||||
{"details": null}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": ["a" * 1], "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": {"nested": {"deep": [1, 2, {"x": [1, [2, [3]]]}]}}, "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "ÿþ", "substrate": "tcp"}
|
||||
@@ -0,0 +1 @@
|
||||
{"resource": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxLine truncated
|
||||
@@ -0,0 +1 @@
|
||||
{"substrate": {"enum": "tcp"}}
|
||||
Loaded 100 of 134 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user