Skip to main content

ghast_protocol/
testgen.rs

1//! Deterministic random value generation for tests (feature `testgen`).
2//!
3//! Every generated type implements [`Generate`]. Generated values are always encodable:
4//! switch discriminants are generated first and conditional fields follow them, counts
5//! match their arrays, packed fields are in range. Values are biased toward the case
6//! keys that switches compare against, so every branch gets exercised.
7//!
8//! This module is test support, not part of the stable API.
9
10use bytes::{Bytes, BytesMut};
11use bytestring::ByteString;
12use uuid::Uuid;
13
14use crate::codec::EncodeResult;
15use crate::nbt::{Compound, List, NamedTag, RawNbt, Tag, tag};
16use crate::types::BlockPos;
17
18/// A type that can produce random values.
19pub trait Generate: Sized {
20    /// Generates one value.
21    fn generate(g: &mut Gen) -> Self;
22}
23
24/// Seeded pseudo-random generator (SplitMix64).
25#[derive(Debug, Clone)]
26pub struct Gen {
27    state: u64,
28    /// Restrict values to what node-minecraft-protocol round-trips (its `varlong` is
29    /// really 32-bit, its NBT root reader cannot take a bare TAG_End).
30    pub node_compat: bool,
31}
32
33impl Gen {
34    /// Creates a generator from a seed.
35    pub fn new(seed: u64) -> Self {
36        Gen {
37            state: seed ^ 0x9e37_79b9_7f4a_7c15,
38            node_compat: false,
39        }
40    }
41
42    /// Next raw 64 random bits.
43    pub fn u64(&mut self) -> u64 {
44        self.state = self.state.wrapping_add(0x9e37_79b9_7f4a_7c15);
45        let mut z = self.state;
46        z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
47        z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
48        z ^ (z >> 31)
49    }
50
51    /// Uniform in `0..n` (n > 0).
52    pub fn below(&mut self, n: u64) -> u64 {
53        self.u64() % n
54    }
55
56    /// True with probability `percent`/100.
57    pub fn chance(&mut self, percent: u64) -> bool {
58        self.below(100) < percent
59    }
60
61    /// A random boolean.
62    pub fn bool(&mut self) -> bool {
63        self.u64() & 1 == 1
64    }
65
66    /// An integer in `[lo, hi]`, preferring `hints`, small values and the bounds.
67    pub fn int_in(&mut self, lo: i64, hi: i64, hints: &[i64]) -> i64 {
68        let ok: Vec<i64> = hints
69            .iter()
70            .copied()
71            .filter(|v| (lo..=hi).contains(v))
72            .collect();
73        if !ok.is_empty() && self.chance(65) {
74            return ok[self.below(ok.len() as u64) as usize];
75        }
76        match self.below(10) {
77            0..=2 => (self.below(20) as i64 - 3).clamp(lo, hi),
78            3 => lo,
79            4 => hi,
80            _ => {
81                let span = (hi as i128 - lo as i128 + 1) as u128;
82                (lo as i128 + (u128::from(self.u64()) % span) as i128) as i64
83            }
84        }
85    }
86
87    /// A count for a field that sizes an array (0..=3).
88    pub fn small_count(&mut self) -> u8 {
89        self.below(4) as u8
90    }
91
92    /// A collection length (mostly small).
93    pub fn count(&mut self) -> usize {
94        if self.chance(90) {
95            self.below(4) as usize
96        } else {
97            self.below(13) as usize
98        }
99    }
100
101    /// A random `i64`.
102    pub fn i64(&mut self) -> i64 {
103        self.int_in(i64::MIN, i64::MAX, &[])
104    }
105
106    /// A random VarLong value (non-negative 32-bit in node-compat mode: node's varlong
107    /// is really an unsigned 32-bit VarInt).
108    pub fn varlong(&mut self) -> i64 {
109        if self.node_compat {
110            self.int_in(0, i64::from(i32::MAX), &[])
111        } else {
112            self.i64()
113        }
114    }
115
116    /// A random finite `f32`.
117    pub fn f32(&mut self) -> f32 {
118        match self.below(4) {
119            0 => [0.0, -0.0, 1.0, -1.5, 0.25, 64.0][self.below(6) as usize],
120            _ => loop {
121                let v = f32::from_bits(self.u64() as u32);
122                if v.is_finite() {
123                    break v;
124                }
125            },
126        }
127    }
128
129    /// A random finite `f64`.
130    pub fn f64(&mut self) -> f64 {
131        match self.below(4) {
132            0 => [0.0, -0.0, 1.0, -1.5, 0.25, 1e300][self.below(6) as usize],
133            _ => loop {
134                let v = f64::from_bits(self.u64());
135                if v.is_finite() {
136                    break v;
137                }
138            },
139        }
140    }
141
142    /// A random UUID.
143    pub fn uuid(&mut self) -> Uuid {
144        Uuid::from_u64_pair(self.u64(), self.u64())
145    }
146
147    fn text(&mut self, bmp_only: bool) -> String {
148        const CHARS: &[char] = &[
149            'a',
150            'b',
151            'z',
152            'A',
153            'Q',
154            '0',
155            '9',
156            ' ',
157            '_',
158            ':',
159            '{',
160            '"',
161            '\u{a7}',
162            '\u{e9}',
163            '\u{131}',
164            '\u{65e5}',
165            '\u{1f600}',
166        ];
167        let n = self.below(12) as usize;
168        (0..n)
169            .map(|_| CHARS[self.below(CHARS.len() as u64 - u64::from(bmp_only)) as usize])
170            .collect()
171    }
172
173    /// A random string.
174    pub fn string(&mut self) -> ByteString {
175        ByteString::from(self.text(false))
176    }
177
178    /// One of `hints` (usually) or a random string.
179    pub fn string_in(&mut self, hints: &[&str]) -> ByteString {
180        if !hints.is_empty() && self.chance(80) {
181            ByteString::from(hints[self.below(hints.len() as u64) as usize].to_string())
182        } else {
183            self.string()
184        }
185    }
186
187    /// Random bytes.
188    pub fn bytes(&mut self) -> Bytes {
189        let n = self.below(25) as usize;
190        (0..n).map(|_| self.u64() as u8).collect::<Vec<u8>>().into()
191    }
192
193    /// A random packable block position.
194    pub fn position(&mut self) -> BlockPos {
195        BlockPos::new(
196            self.int_in(-(1 << 25), (1 << 25) - 1, &[0]) as i32,
197            self.int_in(-2048, 2047, &[0, 64]) as i32,
198            self.int_in(-(1 << 25), (1 << 25) - 1, &[0]) as i32,
199        )
200    }
201
202    fn nbt_tag(&mut self, ty: u8, depth: u32) -> Tag {
203        match ty {
204            tag::BYTE => Tag::Byte(self.u64() as i8),
205            tag::SHORT => Tag::Short(self.u64() as i16),
206            tag::INT => Tag::Int(self.u64() as i32),
207            tag::LONG => Tag::Long(self.u64() as i64),
208            tag::FLOAT => Tag::Float(self.f32()),
209            tag::DOUBLE => Tag::Double(self.f64()),
210            tag::BYTE_ARRAY => Tag::ByteArray(self.bytes()),
211            tag::STRING => Tag::String(self.text(true)),
212            tag::LIST => {
213                let elem = if depth >= 3 {
214                    1 + self.below(8) as u8
215                } else {
216                    1 + self.below(12) as u8
217                };
218                let n = self.count();
219                let items: Vec<Tag> = (0..n).map(|_| self.nbt_tag(elem, depth + 1)).collect();
220                let elem = if items.is_empty() && self.bool() {
221                    tag::END
222                } else {
223                    elem
224                };
225                Tag::List(List {
226                    elem_type: elem,
227                    items,
228                })
229            }
230            tag::COMPOUND => {
231                let n = if depth >= 3 { 0 } else { self.count() };
232                // Unique keys that do not look like array indices (JavaScript objects
233                // reorder those, which would break byte comparisons with node).
234                Tag::Compound(Compound(
235                    (0..n)
236                        .map(|i| {
237                            let t = 1 + self.below(12) as u8;
238                            (
239                                format!("k{i}{}", self.text(true)),
240                                self.nbt_tag(t, depth + 1),
241                            )
242                        })
243                        .collect(),
244                ))
245            }
246            tag::INT_ARRAY => Tag::IntArray((0..self.count()).map(|_| self.u64() as i32).collect()),
247            _ => Tag::LongArray((0..self.count()).map(|_| self.u64() as i64).collect()),
248        }
249    }
250
251    /// A random named root compound as raw NBT.
252    pub fn nbt(&mut self) -> RawNbt {
253        if !self.node_compat && self.chance(10) {
254            return RawNbt::END;
255        }
256        let t = NamedTag {
257            name: if self.chance(70) {
258                String::new()
259            } else {
260                self.text(true)
261            },
262            tag: self.nbt_tag(tag::COMPOUND, 0),
263        };
264        RawNbt::from_named(&t).expect("generated NBT is encodable")
265    }
266
267    /// Optional NBT (`None` 30% of the time).
268    pub fn opt_nbt(&mut self) -> Option<RawNbt> {
269        if self.chance(30) {
270            None
271        } else {
272            let t = NamedTag {
273                name: String::new(),
274                tag: self.nbt_tag(tag::COMPOUND, 0),
275            };
276            Some(RawNbt::from_named(&t).expect("generated NBT is encodable"))
277        }
278    }
279}
280
281/// Encodes with `f` and checks the first written byte with `pred` (used to keep
282/// generated entity-metadata entries from looking like terminators).
283pub fn first_byte_ok(
284    f: impl FnOnce(&mut BytesMut) -> EncodeResult,
285    pred: impl Fn(u8) -> bool,
286) -> bool {
287    let mut w = BytesMut::new();
288    f(&mut w).is_ok() && w.first().is_some_and(|&b| pred(b))
289}
290
291impl Generate for crate::types::slot::legacy::Slot {
292    fn generate(g: &mut Gen) -> Self {
293        use crate::types::slot::legacy::{ItemStack, Slot};
294        if g.chance(25) {
295            return Slot(None);
296        }
297        let id = loop {
298            let id = g.int_in(i64::from(i16::MIN), i64::from(i16::MAX), &[1, 276]) as i16;
299            if id != -1 {
300                break id;
301            }
302        };
303        Slot(Some(ItemStack {
304            id,
305            count: g.u64() as i8,
306            damage: g.u64() as i16,
307            nbt: g.opt_nbt(),
308        }))
309    }
310}
311
312impl Generate for crate::types::slot::modern::Slot {
313    fn generate(g: &mut Gen) -> Self {
314        use crate::types::slot::modern::{ItemStack, Slot};
315        if g.chance(25) {
316            return Slot(None);
317        }
318        Slot(Some(ItemStack {
319            id: g.int_in(i64::from(i32::MIN), i64::from(i32::MAX), &[1, 600]) as i32,
320            count: g.u64() as i8,
321            nbt: g.opt_nbt(),
322        }))
323    }
324}