milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
CI / test (push) Successful in 1m22s
CI / test-aarch64 (push) Successful in 5m6s
CI / frontend (push) Successful in 45s
CI / cross (push) Successful in 7m53s
CI / docker (push) Failing after 1h10m57s

This commit is contained in:
2026-08-07 18:20:30 +02:00
parent c50c6d285a
commit 6f67940995
82 changed files with 3167 additions and 3114 deletions
+5 -42
View File
@@ -29,7 +29,10 @@
const std = @import("std");
const parsers = @import("parsers");
const smith_encode = @import("smith_encode.zig");
const sliceInput = smith_encode.sliceInput;
const pairInput = smith_encode.pairInput;
const wildcard = parsers.wildcard;
const Smith = std.testing.Smith;
@@ -132,9 +135,8 @@ fn labelCount(text: []const u8) usize {
// corpus
// ---------------------------------------------------------------------------
//
// `Smith` does not consume a corpus entry as raw parser input. It reads a byte
// stream in which a slice is a little-endian `u32` length followed by that many
// bytes, so every entry below is length-prefixed. The five targets share one
// `Smith` does not consume a corpus entry as raw parser input, so every entry
// below goes through the `smith_encode.zig` encoders. The five targets share one
// corpus: each starts with a slice, and the wildcard target reads a second one
// that falls back to empty when an entry carries only the first.
@@ -156,31 +158,6 @@ const long_line = "a" ** 5000 ++ ".example.com";
const element_hiding = "example.com##.ad-banner";
const scheme_anchor = "|https://ads.example.com/track";
/// Encodes `bytes` as a single `Smith.slice` value.
fn sliceInput(comptime bytes: []const u8) *const [4 + bytes.len]u8 {
return &struct {
const value: [4 + bytes.len]u8 = blk: {
var buf: [4 + bytes.len]u8 = undefined;
std.mem.writeInt(u32, buf[0..4], @intCast(bytes.len), .little);
buf[4..].* = bytes[0..bytes.len].*;
break :blk buf;
};
}.value;
}
/// Encodes two `Smith.slice` values back to back, which is what the wildcard
/// target reads.
fn pairInput(comptime a: []const u8, comptime b: []const u8) *const [8 + a.len + b.len]u8 {
return &struct {
const value: [8 + a.len + b.len]u8 = blk: {
var buf: [8 + a.len + b.len]u8 = undefined;
buf[0 .. 4 + a.len].* = sliceInput(a).*;
buf[4 + a.len ..].* = sliceInput(b).*;
break :blk buf;
};
}.value;
}
const corpus = [_][]const u8{
sliceInput(hosts_line),
sliceInput(abp_line),
@@ -197,17 +174,3 @@ const corpus = [_][]const u8{
pairInput("*.example.com", "example.com.evil.net"),
pairInput("ad*.example.com", "ads.example.com"),
};
test "a corpus entry carries its own length" {
const encoded = sliceInput(abp_line);
try std.testing.expectEqual(@as(u32, abp_line.len), std.mem.readInt(u32, encoded[0..4], .little));
try std.testing.expectEqualSlices(u8, abp_line, encoded[4..]);
}
test "a paired corpus entry carries both lengths" {
const encoded = pairInput("*.a.b", "x.a.b");
try std.testing.expectEqual(@as(u32, 5), std.mem.readInt(u32, encoded[0..4], .little));
try std.testing.expectEqualSlices(u8, "*.a.b", encoded[4..9]);
try std.testing.expectEqual(@as(u32, 5), std.mem.readInt(u32, encoded[9..13], .little));
try std.testing.expectEqualSlices(u8, "x.a.b", encoded[13..]);
}
+6 -26
View File
@@ -30,7 +30,9 @@
const std = @import("std");
const core = @import("core");
const smith_encode = @import("smith_encode.zig");
const sliceInput = smith_encode.sliceInput;
const compiler = core.compiler;
const Smith = std.testing.Smith;
@@ -132,11 +134,10 @@ fn expectConsistent(counts: compiler.Counts, bytes: []const u8) !void {
// corpus
// ---------------------------------------------------------------------------
//
// `Smith` does not consume a corpus entry as raw input. It reads a byte stream
// in which a slice is a little-endian `u32` length followed by that many bytes,
// so every entry below is length-prefixed. An entry that carries only the slice
// leaves the format index and the reader-buffer length at the low end of their
// ranges, which is the 64-byte buffer that makes `error.StreamTooLong` the
// `Smith` does not consume a corpus entry as raw input, so every entry below
// goes through the `smith_encode.zig` encoder. An entry that carries only the
// slice leaves the format index and the reader-buffer length at the low end of
// their ranges, which is the 64-byte buffer that makes `error.StreamTooLong` the
// common case.
/// Past `compiler.max_line_len`, so the discard arm at compiler.zig:72 replays
@@ -148,18 +149,6 @@ const long_line = "a" ** 5000 ++ ".example.com";
const long_line_unterminated = "0.0.0.0 kept.example.com\n" ++ long_line;
const long_line_terminated = long_line_unterminated ++ "\n0.0.0.0 after.example.com\n";
/// Encodes `bytes` as a single `Smith.slice` value.
fn sliceInput(comptime bytes: []const u8) *const [4 + bytes.len]u8 {
return &struct {
const value: [4 + bytes.len]u8 = blk: {
var buf: [4 + bytes.len]u8 = undefined;
std.mem.writeInt(u32, buf[0..4], @intCast(bytes.len), .little);
buf[4..].* = bytes[0..bytes.len].*;
break :blk buf;
};
}.value;
}
const corpus = [_][]const u8{
sliceInput(long_line_unterminated),
sliceInput(long_line_terminated),
@@ -173,12 +162,3 @@ test "the unterminated corpus entry ends on an over-long line" {
const last = std.mem.findScalarLast(u8, long_line_unterminated, '\n').? + 1;
try std.testing.expect(long_line_unterminated.len - last > compiler.max_line_len);
}
test "a corpus entry carries its own length" {
const encoded = sliceInput(long_line);
try std.testing.expectEqual(
@as(u32, long_line.len),
std.mem.readInt(u32, encoded[0..4], .little),
);
try std.testing.expectEqualSlices(u8, long_line, encoded[4..]);
}
+10 -15
View File
@@ -14,6 +14,9 @@
const std = @import("std");
const dns = @import("dns");
const smith_encode = @import("smith_encode.zig");
const sliceInput = smith_encode.sliceInput;
/// A query for example.com A with an EDNS(0) OPT record advertising 4096
/// bytes: id 0x1234, RD set, one question, one additional.
@@ -119,18 +122,6 @@ const max_jumps = dns.types.max_compression_jumps;
pub const chain_at_cap = pointerChain(max_jumps);
pub const chain_past_cap = pointerChain(max_jumps + 1);
/// Encodes `bytes` as a single `Smith.slice` value.
fn sliceInput(comptime bytes: []const u8) *const [4 + bytes.len]u8 {
return &struct {
const value: [4 + bytes.len]u8 = blk: {
var buf: [4 + bytes.len]u8 = undefined;
std.mem.writeInt(u32, buf[0..4], @intCast(bytes.len), .little);
buf[4..].* = bytes[0..bytes.len].*;
break :blk buf;
};
}.value;
}
/// Encodes `bytes` as a `Smith.slice` value followed by one integer, which the
/// name target reads as an offset and the TTL target as an elapsed time.
fn sliceIntInput(comptime bytes: []const u8, comptime int: u64) *const [12 + bytes.len]u8 {
@@ -181,8 +172,12 @@ test "the pointer chain has the documented shape" {
try std.testing.expectEqual(@as(u16, 0xc000), std.mem.readInt(u16, chain_at_cap[3..5], .big));
}
test "a slice input carries its own length" {
const encoded = sliceInput(query);
test "a slice-plus-integer input carries the length, the bytes and the integer" {
const encoded = sliceIntInput(query, 29);
try std.testing.expectEqual(@as(u32, query.len), std.mem.readInt(u32, encoded[0..4], .little));
try std.testing.expectEqualSlices(u8, query, encoded[4..]);
try std.testing.expectEqualSlices(u8, query, encoded[4 .. 4 + query.len]);
try std.testing.expectEqual(
@as(u64, 29),
std.mem.readInt(u64, encoded[4 + query.len ..][0..8], .little),
);
}
+5 -45
View File
@@ -29,7 +29,10 @@
const std = @import("std");
const http_util = @import("http_util");
const smith_encode = @import("smith_encode.zig");
const sliceInput = smith_encode.sliceInput;
const pairInput = smith_encode.pairInput;
const Smith = std.testing.Smith;
/// A target longer than this is a 414 before it reaches any parser
@@ -149,9 +152,8 @@ fn expectPrefixOf(result: []const u8, buffer: []const u8) !void {
// corpus
// ---------------------------------------------------------------------------
//
// `Smith` does not consume a corpus entry as raw parser input. It reads a byte
// stream in which a slice is a little-endian `u32` length followed by that many
// bytes, so every entry below is length-prefixed. The three targets share one
// `Smith` does not consume a corpus entry as raw parser input, so every entry
// below goes through the `smith_encode.zig` encoders. The three targets share one
// corpus: each starts with a slice, and the query target reads a second one that
// falls back to empty when an entry carries only the first.
@@ -169,31 +171,6 @@ const deep_path = "/1/2/3/4/5/6/7/8/9";
/// through, and a plus that means different things under the two rules.
const bad_escapes = "/%2/%/%zz/a+b";
/// Encodes `bytes` as a single `Smith.slice` value.
fn sliceInput(comptime bytes: []const u8) *const [4 + bytes.len]u8 {
return &struct {
const value: [4 + bytes.len]u8 = blk: {
var buf: [4 + bytes.len]u8 = undefined;
std.mem.writeInt(u32, buf[0..4], @intCast(bytes.len), .little);
buf[4..].* = bytes[0..bytes.len].*;
break :blk buf;
};
}.value;
}
/// Encodes two `Smith.slice` values back to back, which is what the query target
/// reads as its query string and its key.
fn pairInput(comptime a: []const u8, comptime b: []const u8) *const [8 + a.len + b.len]u8 {
return &struct {
const value: [8 + a.len + b.len]u8 = blk: {
var buf: [8 + a.len + b.len]u8 = undefined;
buf[0 .. 4 + a.len].* = sliceInput(a).*;
buf[4 + a.len ..].* = sliceInput(b).*;
break :blk buf;
};
}.value;
}
const corpus = [_][]const u8{
sliceInput(api_path),
sliceInput(encoded_slash),
@@ -207,20 +184,3 @@ const corpus = [_][]const u8{
pairInput("domain=" ++ "x" ** 1024, "domain"),
pairInput("domain=%zz", "domain"),
};
test "a corpus entry carries its own length" {
const encoded = sliceInput(api_path);
try std.testing.expectEqual(
@as(u32, api_path.len),
std.mem.readInt(u32, encoded[0..4], .little),
);
try std.testing.expectEqualSlices(u8, api_path, encoded[4..]);
}
test "a paired corpus entry carries both lengths" {
const encoded = pairInput("a=1", "a");
try std.testing.expectEqual(@as(u32, 3), std.mem.readInt(u32, encoded[0..4], .little));
try std.testing.expectEqualSlices(u8, "a=1", encoded[4..7]);
try std.testing.expectEqual(@as(u32, 1), std.mem.readInt(u32, encoded[7..11], .little));
try std.testing.expectEqualSlices(u8, "a", encoded[11..]);
}
+64
View File
@@ -0,0 +1,64 @@
//! The `std.testing.Smith` byte encoding, shared by every fuzz corpus.
//!
//! `Smith` does not consume a corpus entry as raw parser input. It reads a byte
//! stream in which a slice is a little-endian `u32` length followed by that many
//! bytes, and an integer is a little-endian `u64`. Every corpus entry in
//! `tests/fuzz/` is therefore length-prefixed, and every fuzz file used to spell
//! the same encoder out.
//!
//! This file imports nothing but `std` on purpose. The fuzz targets root
//! separate modules over different parts of `src/` — `blocklist_fuzz.zig` cannot
//! import `corpus.zig`, because `corpus.zig` needs the `dns` module that the
//! blocklist target's build does not have. Each fuzz module reaches this file by
//! relative path and compiles its own copy, so the dedup is at the source level;
//! the self-tests below run once per fuzz artifact.
const std = @import("std");
/// Encodes `bytes` as a single `Smith.slice` value.
pub fn sliceInput(comptime bytes: []const u8) *const [4 + bytes.len]u8 {
return &struct {
const value: [4 + bytes.len]u8 = blk: {
var buf: [4 + bytes.len]u8 = undefined;
std.mem.writeInt(u32, buf[0..4], @intCast(bytes.len), .little);
buf[4..].* = bytes[0..bytes.len].*;
break :blk buf;
};
}.value;
}
/// Encodes two `Smith.slice` values back to back, which is what a target that
/// reads two entities takes: the pattern and the domain, or the query string and
/// the key.
pub fn pairInput(comptime a: []const u8, comptime b: []const u8) *const [8 + a.len + b.len]u8 {
return &struct {
const value: [8 + a.len + b.len]u8 = blk: {
var buf: [8 + a.len + b.len]u8 = undefined;
buf[0 .. 4 + a.len].* = sliceInput(a).*;
buf[4 + a.len ..].* = sliceInput(b).*;
break :blk buf;
};
}.value;
}
test "a slice input carries its own length" {
const encoded = sliceInput("ads.example.com");
try std.testing.expectEqual(
@as(u32, "ads.example.com".len),
std.mem.readInt(u32, encoded[0..4], .little),
);
try std.testing.expectEqualSlices(u8, "ads.example.com", encoded[4..]);
}
test "a paired input carries both lengths" {
const encoded = pairInput("*.a.b", "x.a.b");
try std.testing.expectEqual(@as(u32, 5), std.mem.readInt(u32, encoded[0..4], .little));
try std.testing.expectEqualSlices(u8, "*.a.b", encoded[4..9]);
try std.testing.expectEqual(@as(u32, 5), std.mem.readInt(u32, encoded[9..13], .little));
try std.testing.expectEqualSlices(u8, "x.a.b", encoded[13..]);
}
test "an empty slice input is four bytes of zero" {
const encoded = sliceInput("");
try std.testing.expectEqualSlices(u8, &.{ 0, 0, 0, 0 }, encoded);
}