Skip to main content

harness/payload/dogstatsd/
common.rs

1//! Shared `DogStatsD` payload sampling: vibe, segment and number builders, tags.
2
3use antithesis_sdk::random::random_choice;
4use rand::distr::Distribution;
5use rand::Rng;
6
7use crate::rand::Boundary;
8
9/// Clean by-the-book output, or feral.
10#[derive(Clone, Copy, Debug)]
11pub enum Vibe {
12    /// Well-formed.
13    Clean,
14    /// Aberrant.
15    Feral,
16}
17
18/// Sample a per-line vibe, evenly.
19#[must_use]
20pub fn sample_vibe() -> Vibe {
21    match random_choice(&[Vibe::Clean, Vibe::Feral]) {
22        Some(Vibe::Feral) => Vibe::Feral,
23        _ => Vibe::Clean,
24    }
25}
26
27/// The Agent's name-legal separators, for joining name-like segments.
28pub(crate) const NAME_SEPARATORS: &[u8] = b"._- ";
29
30/// Compliant identifier segments: names, hosts, keys, source types.
31pub(crate) const COMPLIANT_WORD: &[&[u8]] = &[
32    b"adp",
33    b"dogstatsd",
34    b"requests",
35    b"latency",
36    b"errors",
37    b"count",
38    b"total",
39    b"bytes",
40    b"queue",
41    b"workers",
42];
43
44/// Aberrant identifier segments: empty, whitespace, NUL, embedded delimiters,
45/// invalid UTF-8, message-type prefixes.
46pub(crate) const ABERRANT_WORD: &[&[u8]] = &[
47    b"",
48    b" ",
49    b"\t",
50    b"\0",
51    b"a:b",
52    b"a|b",
53    b"a,b",
54    b"#hash",
55    b"@at",
56    b"_sc",
57    b"_e{1,1}",
58    b"\x80",
59    b"\xc3",
60    b"\xed\xa0\x80",
61    b"\xc0\x80",
62    b"\xff\xfe",
63    b"emoji\xf0\x9f\x92\xa9",
64];
65
66/// Values that break number parsers, including long encodings and unicode that
67/// looks numeric: infinity, fullwidth and Arabic-Indic digits.
68pub(crate) const ABERRANT_VALUES: &[&[u8]] = &[
69    b"0",
70    b"-0",
71    b"inf",
72    b"-inf",
73    b"+inf",
74    b"nan",
75    b"infinity",
76    b"1e999999",
77    b"-1e999999",
78    b"0x1p4",
79    b"1_000",
80    b".",
81    b"+",
82    b"-",
83    b"1.",
84    b".5",
85    b"1:2:3:4:5",
86    b"00000000000000000000000000000000000000000000000000000001.5",
87    b"3.141592653589793115997963468544185161590576171875000000000000000000000000",
88    "\u{221e}".as_bytes(),
89    "-\u{221e}".as_bytes(),
90    "\u{ff11}\u{ff12}\u{ff13}".as_bytes(),
91    "\u{0664}\u{0662}".as_bytes(),
92];
93
94/// Unix-timestamp payloads (the `d:` / `T` fields).
95pub(crate) const COMPLIANT_TS: &[&[u8]] = &[b"1700000000", b"1", b"1609459200"];
96
97const COMPLIANT_TAG_KEYS: &[&[u8]] = &[b"env", b"service", b"region", b"version", b"team", b"host", b"shard"];
98const ABERRANT_TAG_KEYS: &[&[u8]] = &[b"", b" ", b":", b",", b"#", b"\0", b"\x80"];
99const COMPLIANT_TAG_VALUES: &[&[u8]] = &[
100    b"prod",
101    b"staging",
102    b"adp",
103    b"us-east-1",
104    b"eu-west-1",
105    b"1.2.3",
106    b"web01",
107    b"0",
108];
109const ABERRANT_TAG_VALUES: &[&[u8]] = &[b"", b",", b"|", b":", b"\xff", b"\xed\xa0\x80", b"a,b"];
110
111/// Compact, or a cursed-but-equivalent padded encoding.
112#[derive(Clone, Copy)]
113enum Form {
114    Compact,
115    Expanded,
116}
117
118/// Extend `buf` with one item. Clean draws from `compliant`; feral chooses
119/// between compliant and aberrant — a choice, never a coin flip.
120pub(crate) fn extend_choice(buf: &mut Vec<u8>, vibe: Vibe, compliant: &[&[u8]], aberrant: &[&[u8]]) {
121    let pools: &[&[&[u8]]] = match vibe {
122        Vibe::Clean => &[compliant],
123        Vibe::Feral => &[compliant, aberrant],
124    };
125    if let Some(&pool) = random_choice(pools) {
126        if let Some(&item) = random_choice(pool) {
127            buf.extend_from_slice(item);
128        }
129    }
130}
131
132/// Sample a count of segments and join them with sampled `separators`. A pool of
133/// `N` segments over a count `c` gives `N^c` results.
134pub(crate) fn write_segments<R: Rng + ?Sized>(
135    rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe, compliant: &[&[u8]], aberrant: &[&[u8]], separators: &[u8],
136) {
137    let count = Boundary::<u8>::new().sample(rng);
138    for i in 0..count {
139        if i > 0 {
140            if let Some(&sep) = random_choice(separators) {
141                buf.push(sep);
142            }
143        }
144        extend_choice(buf, vibe, compliant, aberrant);
145    }
146}
147
148/// An identifier (name, host, key, source) built from word segments.
149pub(crate) fn write_words<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe) {
150    write_segments(rng, buf, vibe, COMPLIANT_WORD, ABERRANT_WORD, NAME_SEPARATORS);
151}
152
153/// Append `|<prefix><item>`, the item chosen for the vibe.
154pub(crate) fn write_field(buf: &mut Vec<u8>, vibe: Vibe, prefix: &[u8], compliant: &[&[u8]], aberrant: &[&[u8]]) {
155    buf.push(b'|');
156    buf.extend_from_slice(prefix);
157    extend_choice(buf, vibe, compliant, aberrant);
158}
159
160/// A boundary-sampled count of `key:value` tags joined by ','. A count of zero
161/// writes no tags. Clean draws compliant keys and values; feral mixes aberrant
162/// ones in, key and value independently.
163pub(crate) fn write_tags<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>, vibe: Vibe) {
164    let count = Boundary::<u8>::new().sample(rng);
165    for t in 0..count {
166        if t == 0 {
167            buf.extend_from_slice(b"|#");
168        } else {
169            buf.push(b',');
170        }
171        write_segments(rng, buf, vibe, COMPLIANT_TAG_KEYS, ABERRANT_TAG_KEYS, NAME_SEPARATORS);
172        buf.push(b':');
173        write_segments(
174            rng,
175            buf,
176            vibe,
177            COMPLIANT_TAG_VALUES,
178            ABERRANT_TAG_VALUES,
179            NAME_SEPARATORS,
180        );
181    }
182}
183
184/// Write `digits` to `buf` as-is, or padded with equivalent leading zeros (and
185/// trailing zeros when there is a fractional part). Same value, cursed encoding.
186pub(crate) fn write_number<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>, digits: &[u8]) {
187    match random_choice(&[Form::Compact, Form::Expanded]) {
188        Some(Form::Expanded) => {
189            let (sign, rest) = match digits.first() {
190                Some(&(b'-' | b'+')) => (&digits[..1], &digits[1..]),
191                _ => (&digits[..0], digits),
192            };
193            buf.extend_from_slice(sign);
194            pad_zeros(rng, buf);
195            buf.extend_from_slice(rest);
196            let fractional = rest.contains(&b'.') && !rest.iter().any(|&c| c == b'e' || c == b'E');
197            if fractional {
198                pad_zeros(rng, buf);
199            }
200        }
201        _ => buf.extend_from_slice(digits),
202    }
203}
204
205/// Append a boundary-sampled run of '0' bytes to `buf`.
206fn pad_zeros<R: Rng + ?Sized>(rng: &mut R, buf: &mut Vec<u8>) {
207    let zeros = usize::from(Boundary::<u8>::new().sample(rng));
208    buf.resize(buf.len() + zeros, b'0');
209}