milestone 18: collapse duplicated infrastructure into shared listener core, crud list helper, resource shells, transport race, name and line helpers, ui modules
This commit is contained in:
@@ -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..]);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -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..]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
Reference in New Issue
Block a user