1use rand::{Rng, RngExt};
20
21use crate::dogstatsd::is_malformed;
22use crate::payload::dogstatsd::common;
23
24pub mod event;
25pub mod metric;
26pub mod service_check;
27
28const REMINT_TRIES: usize = 16;
31
32const RENDER_TRIES: usize = 8;
35
36pub(crate) const LEN_DIGITS: usize = 5;
38
39pub(crate) const TS_DIGITS: usize = 20;
41
42#[derive(Clone, Copy, Debug, PartialEq, Eq)]
44pub enum Kind {
45 Metric,
47 Event,
49 ServiceCheck,
51}
52
53impl Kind {
54 #[must_use]
56 pub fn sample(rng: &mut (impl Rng + ?Sized)) -> Kind {
57 match rng.random_range(0..100u32) {
58 0 => Kind::Event,
59 1 => Kind::ServiceCheck,
60 _ => Kind::Metric,
61 }
62 }
63}
64
65#[derive(Clone, Debug, PartialEq, Eq, Hash)]
67pub enum Context {
68 Metric(metric::MetricContext),
70 Event(event::EventContext),
72 ServiceCheck(service_check::ServiceCheckContext),
74}
75
76impl Context {
77 #[must_use]
86 pub fn mint_within(kind: Kind, rng: &mut (impl Rng + ?Sized), budget: usize) -> Option<Context> {
87 for _ in 0..REMINT_TRIES {
88 let context = match kind {
89 Kind::Metric => Context::Metric(metric::MetricContext::mint_within(rng, budget)?),
90 Kind::Event => Context::Event(event::EventContext::mint_within(rng, budget)?),
91 Kind::ServiceCheck => {
92 Context::ServiceCheck(service_check::ServiceCheckContext::mint_within(rng, budget)?)
93 }
94 };
95 let mut probe = Vec::new();
96 context.render(rng, &mut probe);
97 if is_malformed(&probe).is_ok() {
98 return Some(context);
99 }
100 }
101 None
102 }
103
104 pub fn render(&self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>) -> usize {
107 match self {
108 Context::Metric(c) => c.render(rng, out),
109 Context::Event(c) => c.render(rng, out),
110 Context::ServiceCheck(c) => c.render(rng, out),
111 }
112 }
113
114 fn poison(&mut self, rng: &mut (impl Rng + ?Sized)) -> bool {
122 let fields: Vec<&mut Vec<u8>> = match self {
123 Context::Metric(c) => std::iter::once(&mut c.name).chain(c.tags.iter_mut()).collect(),
124 Context::Event(c) => std::iter::once(&mut c.title).chain(c.tags.iter_mut()).collect(),
125 Context::ServiceCheck(c) => std::iter::once(&mut c.name).chain(c.tags.iter_mut()).collect(),
126 };
127 let targets: Vec<(usize, usize)> = fields
128 .iter()
129 .enumerate()
130 .flat_map(|(f, bytes)| {
131 bytes
132 .iter()
133 .enumerate()
134 .filter(|(_, &b)| !matches!(b, b':' | b'|' | b',' | b'#' | b'@'))
135 .map(move |(i, _)| (f, i))
136 })
137 .collect();
138 if targets.is_empty() {
139 return false;
140 }
141 let (field, at) = targets[rng.random_range(0..targets.len())];
142 let mut fields = fields;
143 fields[field][at] = common::invalid_utf8_byte(rng);
144 true
145 }
146
147 #[must_use]
151 pub fn mint_non_utf8_within(kind: Kind, rng: &mut (impl Rng + ?Sized), budget: usize) -> Option<Context> {
152 for _ in 0..REMINT_TRIES {
153 let mut context = Context::mint_within(kind, rng, budget)?;
154 if !context.poison(rng) {
155 continue;
156 }
157 if !context.has_non_utf8() {
162 continue;
163 }
164 let mut probe = Vec::new();
165 context.render(rng, &mut probe);
166 if is_malformed(&probe).is_ok() {
167 return Some(context);
168 }
169 }
170 None
171 }
172
173 #[must_use]
175 pub fn has_non_utf8(&self) -> bool {
176 let fields: Vec<&[u8]> = match self {
177 Context::Metric(c) => std::iter::once(c.name.as_slice())
178 .chain(c.tags.iter().map(Vec::as_slice))
179 .collect(),
180 Context::Event(c) => std::iter::once(c.title.as_slice())
181 .chain(c.tags.iter().map(Vec::as_slice))
182 .collect(),
183 Context::ServiceCheck(c) => std::iter::once(c.name.as_slice())
184 .chain(c.tags.iter().map(Vec::as_slice))
185 .collect(),
186 };
187 fields.iter().any(|f| simdutf8::basic::from_utf8(f).is_err())
188 }
189
190 #[must_use]
192 pub fn floor(&self) -> usize {
193 match self {
194 Context::Metric(c) => c.floor(),
195 Context::Event(c) => c.floor(),
196 Context::ServiceCheck(c) => c.floor(),
197 }
198 }
199
200 fn render_within(&self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>, budget: usize) -> Option<usize> {
202 match self {
203 Context::Metric(c) => c.render_within(rng, out, budget),
204 Context::Event(c) => c.render_within(rng, out, budget),
205 Context::ServiceCheck(c) => c.render_within(rng, out, budget),
206 }
207 }
208
209 pub fn render_wellformed_within(
214 &self, rng: &mut (impl Rng + ?Sized), out: &mut Vec<u8>, budget: usize,
215 ) -> Option<usize> {
216 for try_index in 0..RENDER_TRIES {
217 let start = out.len();
218 let attempt = if try_index + 1 == RENDER_TRIES {
222 self.floor().min(budget)
223 } else {
224 budget
225 };
226 let packed = self.render_within(rng, out, attempt)?;
227 if is_malformed(&out[start..]).is_ok() {
228 return Some(packed);
229 }
230 out.truncate(start);
231 }
232 None
233 }
234
235 pub fn encode(&self, out: &mut Vec<u8>) {
237 match self {
238 Context::Metric(c) => {
239 put_u8(out, 0);
240 c.encode(out);
241 }
242 Context::Event(c) => {
243 put_u8(out, 1);
244 c.encode(out);
245 }
246 Context::ServiceCheck(c) => {
247 put_u8(out, 2);
248 c.encode(out);
249 }
250 }
251 }
252
253 fn decode(buf: &[u8], pos: &mut usize) -> Option<Context> {
255 Some(match get_u8(buf, pos)? {
256 0 => Context::Metric(metric::MetricContext::decode(buf, pos)?),
257 1 => Context::Event(event::EventContext::decode(buf, pos)?),
258 2 => Context::ServiceCheck(service_check::ServiceCheckContext::decode(buf, pos)?),
259 _ => return None,
260 })
261 }
262}
263
264#[must_use]
266pub fn encode_response(contexts: &[Context]) -> Vec<u8> {
267 let mut out = Vec::new();
268 let count = u32::try_from(contexts.len()).unwrap_or(u32::MAX);
270 out.extend_from_slice(&count.to_le_bytes());
271 for context in contexts {
272 context.encode(&mut out);
273 }
274 out
275}
276
277#[must_use]
280pub fn decode_response(buf: &[u8]) -> Option<Vec<Context>> {
281 let mut pos = 0;
282 let count = get_u32(buf, &mut pos)?;
283 let mut contexts = Vec::new();
284 for _ in 0..count {
285 contexts.push(Context::decode(buf, &mut pos)?);
286 }
287 Some(contexts)
288}
289
290pub(crate) fn fresh_timestamp(rng: &mut (impl Rng + ?Sized)) -> u64 {
292 rng.random_range(1..=2_000_000_000u64)
293}
294
295pub(crate) fn put_u8(out: &mut Vec<u8>, b: u8) {
299 out.push(b);
300}
301
302fn put_u16(out: &mut Vec<u8>, len: usize) {
305 let len = u16::try_from(len).unwrap_or(u16::MAX);
306 out.extend_from_slice(&len.to_le_bytes());
307}
308
309pub(crate) fn put_bytes(out: &mut Vec<u8>, bytes: &[u8]) {
311 put_u16(out, bytes.len());
312 out.extend_from_slice(bytes);
313}
314
315pub(crate) fn put_tags(out: &mut Vec<u8>, tags: &[Vec<u8>]) {
317 put_u16(out, tags.len());
318 for tag in tags {
319 put_bytes(out, tag);
320 }
321}
322
323pub(crate) fn get_u8(buf: &[u8], pos: &mut usize) -> Option<u8> {
325 let byte = *buf.get(*pos)?;
326 *pos += 1;
327 Some(byte)
328}
329
330fn get_u16(buf: &[u8], pos: &mut usize) -> Option<usize> {
332 let end = pos.checked_add(2)?;
333 let slice = buf.get(*pos..end)?;
334 *pos = end;
335 Some(u16::from_le_bytes([slice[0], slice[1]]) as usize)
336}
337
338fn get_u32(buf: &[u8], pos: &mut usize) -> Option<usize> {
340 let end = pos.checked_add(4)?;
341 let slice = buf.get(*pos..end)?;
342 *pos = end;
343 Some(u32::from_le_bytes([slice[0], slice[1], slice[2], slice[3]]) as usize)
344}
345
346pub(crate) fn get_bytes<'a>(buf: &'a [u8], pos: &mut usize) -> Option<&'a [u8]> {
348 let len = get_u16(buf, pos)?;
349 let end = pos.checked_add(len)?;
350 let slice = buf.get(*pos..end)?;
351 *pos = end;
352 Some(slice)
353}
354
355pub(crate) fn get_tags(buf: &[u8], pos: &mut usize) -> Option<Vec<Vec<u8>>> {
357 let count = get_u16(buf, pos)?;
358 let mut tags = Vec::new();
359 for _ in 0..count {
360 tags.push(get_bytes(buf, pos)?.to_vec());
361 }
362 Some(tags)
363}
364
365#[cfg(test)]
366mod tests {
367 use proptest::prelude::*;
368 use rand::rngs::SmallRng;
369 use rand::SeedableRng;
370
371 use super::{decode_response, encode_response, Context, Kind};
372 use crate::dogstatsd::is_malformed;
373
374 fn any_kind() -> impl Strategy<Value = Kind> {
375 prop_oneof![Just(Kind::Metric), Just(Kind::Event), Just(Kind::ServiceCheck)]
376 }
377
378 proptest! {
379 #[test]
383 fn property_test_a_corrupt_mint_is_corrupt(seed: u64) {
384 let mut rng = SmallRng::seed_from_u64(seed);
385 for _ in 0..16 {
386 if let Some(context) = Context::mint_non_utf8_within(Kind::sample(&mut rng), &mut rng, 8_191) {
387 prop_assert!(context.has_non_utf8(), "a corrupt mint carried no invalid byte: {context:?}");
388 }
389 }
390 }
391
392 #[test]
395 fn property_test_render_wellformed_always_forwards(seed: u64, kind in any_kind()) {
396 let mut rng = SmallRng::seed_from_u64(seed);
397 let Some(context) = Context::mint_within(kind, &mut rng, 8_192) else { return Ok(()) };
398 for _ in 0..8 {
399 let mut line = Vec::new();
400 if context
401 .render_wellformed_within(&mut rng, &mut line, 8_192)
402 .is_some()
403 {
404 prop_assert_eq!(is_malformed(&line), Ok(()), "a rendered line was droppable");
405 }
406 }
407 }
408
409 #[test]
411 fn property_test_response_round_trips(seed: u64) {
412 let mut rng = SmallRng::seed_from_u64(seed);
413 let contexts: Vec<Context> = (0..8)
414 .filter_map(|_| Context::mint_within(Kind::sample(&mut rng), &mut rng, 8_192))
415 .collect();
416 let wire = encode_response(&contexts);
417 let decoded = decode_response(&wire);
418 prop_assert_eq!(decoded.as_deref(), Some(contexts.as_slice()));
419 }
420
421 #[test]
423 fn property_test_decode_rejects_truncation(seed: u64) {
424 let mut rng = SmallRng::seed_from_u64(seed);
425 let contexts: Vec<Context> = (0..4)
426 .filter_map(|_| Context::mint_within(Kind::sample(&mut rng), &mut rng, 8_192))
427 .collect();
428 let wire = encode_response(&contexts);
429 for cut in 0..wire.len() {
430 let _ = decode_response(&wire[..cut]);
431 }
432 prop_assert_eq!(decode_response(&wire).map(|c| c.len()), Some(contexts.len()));
433 }
434 }
435}