fuzz: land the name_validation target (§3.3, wave 7)
This commit is contained in:
1 parent
f7c1bd79b0
commit
5043f7bcd8
52 files changed
+465
-6
No files matched your search
@@ -2,5 +2,6 @@ target
|
||||
corpus/*
|
||||
!corpus/payload_codec
|
||||
!corpus/every_parse
|
||||
!corpus/name_validation
|
||||
artifacts
|
||||
coverage
|
||||
@@ -28,4 +28,11 @@ test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[[bin]]
|
||||
name = "name_validation"
|
||||
path = "fuzz_targets/name_validation.rs"
|
||||
test = false
|
||||
doc = false
|
||||
bench = false
|
||||
|
||||
[workspace]
|
||||
+1
-1
@@ -29,7 +29,7 @@ libFuzzer targets for alkstore's parse/codec surfaces (see
|
||||
|---|---|---|
|
||||
| `payload_codec` | `encode_payload` / `payload_as::<T>` (`alkstore/src/payload.rs`) | no-panic on any byte sequence; every decode failure typed `Error::Codec`; deep nesting loud at serde_json's recursion limit; `Value` round-trip (serde_json canonicalization pinned); typed round-trip for a representative payload set |
|
||||
| `every_parse` | the three `parse_every_interval` implementations (differential, through the engines' `#[doc(hidden)]` re-exports) + each engine's `skip_forward` boundary arithmetic | no-panic on any UTF-8-lossy string (each parser, each boundary path); typed rejection (`Error::InvalidSpec` on pg/mem, the substrate's error under its mapping on SQLite); three-way agreement on the Ok/Err partition and the `Ok` interval value (error identity not pinned — the SQLite `Result<i64, String>` vs `InvalidSpec` asymmetry is known); overflow posture: `skip_forward` over adversarial `(next_fire_at, now, interval)` triples rejects-or-saturates, never wraps or panics; the `n <= 0` and giant-`n` arms (leading zeros, digit overflow at exactly `i64::MAX/86400`, positive-zero rejection) |
|
||||
| `name_validation` | `validate_name` / `validate_shared_name` / `validate_local_name` (`alkstore/src/validation.rs`) | filled by its target task |
|
||||
| `name_validation` | `validate_name` / `validate_shared_name` / `validate_local_name` (`alkstore/src/validation.rs`) | no-panic on any byte sequence (lossy-decoded and `arbitrary`-structured strings); deterministic accept/reject (double-invocation-pinned); rejection only via the typed taxonomy — `Error::InvalidName` for empty/whitespace-only on both kinds, `Error::ReservedName` for the leading byte-exact `__alkstore_` prefix on the shared kind only (the local kind takes the non-empty rule only, so a leading reserved prefix is legal there); the reserved-prefix rule exact in every casing/boundary form (casing deviations, missing trailing underscore, embedded and whitespace-led forms accept); the rejection carries the supplied name verbatim (names are not trimmed); verdict-class length-independence across the probed neighborhoods (0↔1, 11↔12, 63↔64, 4095↔4096, 65536) — no length cap is pinned (ADR-008 §4), so no length is ever a rejection cause |
|
||||
|
||||
## Commands
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
a
|
||||
@@ -0,0 +1 @@
|
||||
aa
|
||||
@@ -0,0 +1 @@
|
||||
aaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
Whitespace-only changes.
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_x
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_listener_reconnected__
|
||||
@@ -0,0 +1 @@
|
||||
__ALKSTORE_x
|
||||
@@ -0,0 +1 @@
|
||||
__Alkstore_x
|
||||
@@ -0,0 +1 @@
|
||||
__alkStore_x
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore
|
||||
@@ -0,0 +1 @@
|
||||
x__alkstore_y
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_x
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
__alkstore_aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
|
||||
@@ -0,0 +1 @@
|
||||
é-😀-名前
|
||||
@@ -0,0 +1 @@
|
||||
é́
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1 @@
|
||||
tab name
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
�(
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
//! The §3.3 target: core's name-validation invariants, driven through
|
||||
//! the already-`pub` validation surface (`validate_name` over all
|
||||
//! `NameKind`s, `validate_shared_name`, `validate_local_name`).
|
||||
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
alkstore_fuzz_shared::name_validation::fuzz_name_validation(data);
|
||||
});
|
||||
@@ -245,9 +245,77 @@ def every_parse_seeds():
|
||||
i += 1
|
||||
|
||||
|
||||
def name_validation_seeds():
|
||||
i = 0
|
||||
|
||||
# Plain-content names at the probed lengths: the empty↔one-char
|
||||
# boundary, the pg-identifier (±1) and target-max_len (±1/±2)
|
||||
# neighborhoods. No length cap is pinned; the seeds pin
|
||||
# length-independence of the verdict class.
|
||||
for length in [1, 2, 4, 62, 63, 64, 65, 4095, 4096]:
|
||||
write_seed("name_validation", i, b"a" * length)
|
||||
i += 1
|
||||
|
||||
# Empty and whitespace-only shapes (spaces, tab, newline, mixed,
|
||||
# unicode whitespace) — InvalidName on both kinds.
|
||||
for raw in [b"", b" ", b" ", b"\t", b"\n", b" \t\n ", b"\xc2\xa0", b"\xe2\x80\x83"]:
|
||||
write_seed("name_validation", i, raw)
|
||||
i += 1
|
||||
|
||||
# The reserved prefix: the boundary form (alone), suffixed, at a
|
||||
# boundary length, and the one v1-reserved string.
|
||||
for raw in [
|
||||
b"__alkstore_",
|
||||
b"__alkstore_x",
|
||||
b"__alkstore_" + b"a" * 63,
|
||||
b"__alkstore_listener_reconnected__",
|
||||
]:
|
||||
write_seed("name_validation", i, raw)
|
||||
i += 1
|
||||
|
||||
# Prefix near-misses: casing deviations, the missing trailing
|
||||
# underscore, embedded and whitespace-led forms — the pinned rule
|
||||
# is byte-exact and leading-only, so these must accept.
|
||||
for raw in [
|
||||
b"__ALKSTORE_x",
|
||||
b"__Alkstore_x",
|
||||
b"__alkStore_x",
|
||||
b"__alkstore",
|
||||
b"x__alkstore_y",
|
||||
b" __alkstore_x",
|
||||
b"__alkstore_ ",
|
||||
]:
|
||||
write_seed("name_validation", i, raw)
|
||||
i += 1
|
||||
|
||||
# Whitespace-only and reserved-prefixed at the pg-identifier
|
||||
# neighborhood lengths (±1, ±2).
|
||||
for length in [62, 63, 64, 65]:
|
||||
write_seed("name_validation", i, b" " * length)
|
||||
i += 1
|
||||
write_seed("name_validation", i, b"__alkstore_" + b"a" * (length - len(b"__alkstore_")))
|
||||
i += 1
|
||||
|
||||
# Unicode and control-char shapes, including invalid UTF-8 (lossy
|
||||
# decode) and embedded NUL.
|
||||
for raw in [
|
||||
"é-😀-名前".encode(),
|
||||
"\u00e9\u0301".encode(),
|
||||
b"\x00\x01\x02",
|
||||
b"a\x00b",
|
||||
b"tab\tname",
|
||||
b"\x7f",
|
||||
b"\xc3\x28",
|
||||
b"\xff\xfe",
|
||||
]:
|
||||
write_seed("name_validation", i, raw)
|
||||
i += 1
|
||||
|
||||
|
||||
def main():
|
||||
payload_codec_seeds()
|
||||
every_parse_seeds()
|
||||
name_validation_seeds()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
|
||||
pub mod arbitrary_value;
|
||||
pub mod every_parse;
|
||||
pub mod name_validation;
|
||||
pub mod payload_codec;
|
||||
@@ -0,0 +1,272 @@
|
||||
//! Invariant logic for the `name_validation` target (docs/research/
|
||||
//! fuzzing.md §3.3, `alkstore/src/validation.rs`): the entry-point
|
||||
//! name-validation surface over the whole string space — no-panic on
|
||||
//! any byte sequence (lossy-decoded and `arbitrary`-structured alike),
|
||||
//! deterministic accept/reject (double-invocation pinned), rejection
|
||||
//! only via the typed `InvalidName`/`ReservedName` taxonomy
|
||||
//! (ADR-008 §4/§5), the reserved-prefix rule byte-exact and
|
||||
//! leading-only with the shared/local kind split (the local kind takes
|
||||
//! the non-empty rule only — a leading `__alkstore_` is legal there),
|
||||
//! the error carrying the supplied name verbatim (names are not
|
||||
//! trimmed), and length-independence of the verdict class. No length
|
||||
//! cap is pinned (ADR-008 §4 pins non-empty + reserved-prefix only), so
|
||||
//! "exactness at the cap" is pinned as: nothing at any length flips a
|
||||
//! verdict — the only exact boundaries in the rule are empty↔one-char,
|
||||
//! whitespace-only↔content, and the byte-exact prefix itself.
|
||||
|
||||
use arbitrary::{Arbitrary, Unstructured};
|
||||
|
||||
use alkstore::{
|
||||
validate_local_name, validate_name, validate_shared_name, Error, NameKind,
|
||||
RESERVED_LISTENER_RECONNECTED, RESERVED_PREFIX,
|
||||
};
|
||||
|
||||
/// The probed length neighborhoods: the empty↔one-char boundary
|
||||
/// (0↔1), the reserved-prefix length (11↔12), the Postgres identifier
|
||||
/// bytes (63↔64), the target's max_len (4095↔4096), and the
|
||||
/// payload-cap scale (65536). None of these is a pinned cap — the ADR
|
||||
/// pins no length limit — so the invariant is that the verdict class
|
||||
/// never changes with length.
|
||||
const LENGTH_PROBES: &[usize] = &[0, 1, 2, 3, 11, 12, 62, 63, 64, 65, 4095, 4096, 65_536];
|
||||
|
||||
/// The verdict of one validation call, narrowed to the taxonomy's typed
|
||||
/// rejections: `Accept`, `InvalidName { name }`, or
|
||||
/// `ReservedName { name }`. Any other outcome is a contract violation
|
||||
/// and panics at construction — the typed-rejection invariant.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
enum Verdict {
|
||||
Accept,
|
||||
InvalidName(String),
|
||||
ReservedName(String),
|
||||
}
|
||||
|
||||
pub fn fuzz_name_validation(data: &[u8]) {
|
||||
let lossy = String::from_utf8_lossy(data);
|
||||
assert_verdict_invariants(&lossy);
|
||||
assert_verdict_battery();
|
||||
assert_structured(data);
|
||||
assert_length_independence();
|
||||
}
|
||||
|
||||
/// One string across the whole invariant set: deterministic (the
|
||||
/// double-invocation pin), typed-only, all three entry points agreeing,
|
||||
/// and the observed verdicts matching the locally recomputed rule.
|
||||
fn assert_verdict_invariants(name: &str) {
|
||||
let shared = shared_verdict(name);
|
||||
assert_eq!(
|
||||
shared,
|
||||
shared_verdict(name),
|
||||
"the shared-kind verdict must be deterministic for {name:?}"
|
||||
);
|
||||
let local = local_verdict(name);
|
||||
assert_eq!(
|
||||
local,
|
||||
local_verdict(name),
|
||||
"the local-kind verdict must be deterministic for {name:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
shared,
|
||||
kind_verdict(NameKind::Shared, name),
|
||||
"`validate_shared_name` must agree with `validate_name(Shared)` for {name:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
local,
|
||||
kind_verdict(NameKind::Local, name),
|
||||
"`validate_local_name` must agree with `validate_name(Local)` for {name:?}"
|
||||
);
|
||||
assert_classification(name, &shared, &local);
|
||||
}
|
||||
|
||||
/// The rule of record recomputed locally and asserted against the
|
||||
/// observed verdicts: empty or whitespace-only → `InvalidName` on both
|
||||
/// kinds (names are not trimmed — the rejection never trims the name it
|
||||
/// reports); otherwise the byte-exact leading reserved prefix rejects
|
||||
/// `ReservedName` on the shared kind and is legal on the local kind;
|
||||
/// anything else accepts on both kinds.
|
||||
fn assert_classification(name: &str, shared: &Verdict, local: &Verdict) {
|
||||
if name.is_empty() || name.trim().is_empty() {
|
||||
assert_eq!(
|
||||
shared,
|
||||
&Verdict::InvalidName(name.to_owned()),
|
||||
"empty/whitespace-only must reject `InvalidName` on the shared kind for {name:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
local,
|
||||
&Verdict::InvalidName(name.to_owned()),
|
||||
"empty/whitespace-only must reject `InvalidName` on the local kind for {name:?}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
if name.starts_with(RESERVED_PREFIX) {
|
||||
assert_eq!(
|
||||
shared,
|
||||
&Verdict::ReservedName(name.to_owned()),
|
||||
"a leading reserved prefix must reject `ReservedName` on the shared kind for {name:?} — the error carries the name verbatim"
|
||||
);
|
||||
assert_eq!(
|
||||
local,
|
||||
&Verdict::Accept,
|
||||
"the local kind takes the non-empty rule only — a leading reserved prefix is legal there: {name:?}"
|
||||
);
|
||||
return;
|
||||
}
|
||||
assert_eq!(
|
||||
shared,
|
||||
&Verdict::Accept,
|
||||
"a non-empty, non-whitespace shared name without the exact reserved prefix must accept: {name:?}"
|
||||
);
|
||||
assert_eq!(
|
||||
local,
|
||||
&Verdict::Accept,
|
||||
"a non-empty, non-whitespace local name must accept: {name:?}"
|
||||
);
|
||||
}
|
||||
|
||||
/// The casing/boundary battery for the reserved-prefix rule: the rule
|
||||
/// is byte-exact and leading-only, so the near-miss forms (casing
|
||||
/// deviations, the missing trailing underscore, embedded and
|
||||
/// whitespace-led prefix occurrences) must accept on the shared kind,
|
||||
/// while the byte-exact prefix (alone, suffixed, whitespace-suffixed —
|
||||
/// anything not all-whitespace) must reject.
|
||||
const BATTERY: &[&str] = &[
|
||||
"",
|
||||
" ",
|
||||
"\t\n",
|
||||
"\u{a0}",
|
||||
"a",
|
||||
"é-😀",
|
||||
"a\u{0}b",
|
||||
"__alkstore_",
|
||||
"__alkstore_x",
|
||||
RESERVED_LISTENER_RECONNECTED,
|
||||
"__alkstore_ ",
|
||||
"__alkstore_x_",
|
||||
"__alkstore",
|
||||
"__ALKSTORE_x",
|
||||
"__Alkstore_x",
|
||||
"__alkStore_x",
|
||||
"__alkstore_X",
|
||||
"x__alkstore_y",
|
||||
" __alkstore_x",
|
||||
];
|
||||
|
||||
fn assert_verdict_battery() {
|
||||
for name in BATTERY {
|
||||
assert_verdict_invariants(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// The structured path: `arbitrary`-derived strings plus their
|
||||
/// boundary-derivative forms (leading/trailing/embedded reserved
|
||||
/// prefix, whitespace-wrapped, case-shifted) through the same
|
||||
/// invariant set.
|
||||
fn assert_structured(data: &[u8]) {
|
||||
let mut u = Unstructured::new(data);
|
||||
for _ in 0..2 {
|
||||
let Ok(name) = String::arbitrary(&mut u) else {
|
||||
return;
|
||||
};
|
||||
assert_verdict_invariants(&name);
|
||||
for variant in derived_variants(&name) {
|
||||
assert_verdict_invariants(&variant);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn derived_variants(name: &str) -> Vec<String> {
|
||||
let mut variants = vec![
|
||||
format!("{}{}", RESERVED_PREFIX, name),
|
||||
format!("{}{}", name, RESERVED_PREFIX),
|
||||
format!("x{}{}y", RESERVED_PREFIX, name),
|
||||
format!(" {}", name),
|
||||
format!("{} ", name),
|
||||
];
|
||||
variants.push(name.to_uppercase());
|
||||
variants
|
||||
}
|
||||
|
||||
/// Verdict-class length-independence: the same content class (plain,
|
||||
/// whitespace-only, reserved-prefixed, casing near-miss, embedded
|
||||
/// prefix) holds its verdict at every probed length. No length cap is
|
||||
/// pinned — a length anywhere is not a rejection cause.
|
||||
fn assert_length_independence() {
|
||||
for &len in LENGTH_PROBES {
|
||||
let plain = "a".repeat(len);
|
||||
assert_verdict_invariants(&plain);
|
||||
let ws_only = " ".repeat(len);
|
||||
assert_verdict_invariants(&ws_only);
|
||||
let reserved = format!("{}{}", RESERVED_PREFIX, "a".repeat(len));
|
||||
assert_verdict_invariants(&reserved);
|
||||
let casing = format!("__ALKSTORE_{}", "a".repeat(len));
|
||||
assert_verdict_invariants(&casing);
|
||||
let embedded = format!("{}{}{}", "a".repeat(len), RESERVED_PREFIX, "b".repeat(len));
|
||||
assert_verdict_invariants(&embedded);
|
||||
}
|
||||
}
|
||||
|
||||
fn shared_verdict(name: &str) -> Verdict {
|
||||
match validate_shared_name(name) {
|
||||
Ok(()) => Verdict::Accept,
|
||||
Err(Error::InvalidName { name }) => Verdict::InvalidName(name),
|
||||
Err(Error::ReservedName { name }) => Verdict::ReservedName(name),
|
||||
Err(other) => panic!(
|
||||
"shared-name rejection must be typed `InvalidName`/`ReservedName`, got {other:?} for {name:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn local_verdict(name: &str) -> Verdict {
|
||||
match validate_local_name(name) {
|
||||
Ok(()) => Verdict::Accept,
|
||||
Err(Error::InvalidName { name }) => Verdict::InvalidName(name),
|
||||
Err(Error::ReservedName { name }) => Verdict::ReservedName(name),
|
||||
Err(other) => panic!(
|
||||
"local-name rejection must be typed `InvalidName`/`ReservedName`, got {other:?} for {name:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn kind_verdict(kind: NameKind, name: &str) -> Verdict {
|
||||
match validate_name(kind, name) {
|
||||
Ok(()) => Verdict::Accept,
|
||||
Err(Error::InvalidName { name }) => Verdict::InvalidName(name),
|
||||
Err(Error::ReservedName { name }) => Verdict::ReservedName(name),
|
||||
Err(other) => panic!(
|
||||
"name rejection must be typed `InvalidName`/`ReservedName`, got {other:?} for {name:?}"
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod corpus_replay {
|
||||
use super::*;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[test]
|
||||
fn committed_corpus_replays_through_the_invariants() {
|
||||
let corpus = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../corpus/name_validation");
|
||||
let mut count = 0usize;
|
||||
for entry in std::fs::read_dir(&corpus).expect("corpus directory is committed") {
|
||||
let path = entry.expect("corpus entry readable").path();
|
||||
let data = std::fs::read(&path).expect("corpus entry readable");
|
||||
fuzz_name_validation(&data);
|
||||
count += 1;
|
||||
}
|
||||
assert!(
|
||||
count >= 32,
|
||||
"committed seed corpus is present, found {count}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invariants_hold_without_corpus() {
|
||||
fuzz_name_validation(b"");
|
||||
fuzz_name_validation(b"a");
|
||||
fuzz_name_validation(b" ");
|
||||
fuzz_name_validation(b"__alkstore_");
|
||||
fuzz_name_validation(b"__alkstore_listener_reconnected__");
|
||||
fuzz_name_validation(b"__alkstore_l\xffistener");
|
||||
fuzz_name_validation(&[0u8; 16]);
|
||||
fuzz_name_validation(&[b'a'; 65_537]);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user