use rand::{Rng, SeedableRng};
use rand_chacha::ChaCha8Rng;
use s2rst::s2::encoded_s2cell_id_vector::{decode_s2cell_id_vector, encode_s2cell_id_vector};
use s2rst::s2::encoded_s2point_vector::{CodingHint, decode_s2point_vector, encode_s2point_vector};
use s2rst::s2::encoded_string_vector::{decode_string_vector, encode_string_vector};
use s2rst::s2::encoded_uint_vector::{
decode_uint_vector_u32, decode_uint_vector_u64, encode_uint_vector_u32, encode_uint_vector_u64,
};
use s2rst::s2::{CellId, LatLng, Point};
fn rng(seed: u64) -> ChaCha8Rng {
ChaCha8Rng::seed_from_u64(seed)
}
fn rand_point(r: &mut ChaCha8Rng) -> Point {
let lat = r.gen_range(-90.0..90.0);
let lng = r.gen_range(-180.0..180.0);
LatLng::from_degrees(lat, lng).to_point()
}
fn rand_points(r: &mut ChaCha8Rng, n: usize) -> Vec<Point> {
(0..n).map(|_| rand_point(r)).collect()
}
fn rand_cell_ids(r: &mut ChaCha8Rng, n: usize) -> Vec<CellId> {
(0..n)
.map(|_| {
let leaf = CellId::from_point(&rand_point(r));
let level: u8 = r.gen_range(0..=30);
leaf.parent_at_level(level)
})
.collect()
}
fn enc_u32_vec(v: &[u32]) -> Vec<u8> {
let mut b = Vec::new();
encode_uint_vector_u32(v, &mut b).unwrap();
b
}
fn enc_u64_vec(v: &[u64]) -> Vec<u8> {
let mut b = Vec::new();
encode_uint_vector_u64(v, &mut b).unwrap();
b
}
fn enc_string_vec(v: &[Vec<u8>]) -> Vec<u8> {
let refs: Vec<&[u8]> = v.iter().map(Vec::as_slice).collect();
let mut b = Vec::new();
encode_string_vector(&refs, &mut b).unwrap();
b
}
fn enc_cell_id_vec(v: &[CellId]) -> Vec<u8> {
let mut b = Vec::new();
encode_s2cell_id_vector(v, &mut b).unwrap();
b
}
fn enc_point_vec(v: &[Point], hint: CodingHint) -> Vec<u8> {
let mut b = Vec::new();
encode_s2point_vector(v, hint, &mut b).unwrap();
b
}
fn no_panic<T, F>(label: &str, decode: F, input: &[u8])
where
F: Fn(&mut &[u8]) -> std::io::Result<T>,
{
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
let mut r = input;
let _unused = decode(&mut r);
}));
assert!(
res.is_ok(),
"{label} panicked on {}-byte input: {input:02x?}",
input.len()
);
}
fn for_each_decoder(mut f: impl FnMut(&str, &dyn Fn(&[u8]))) {
f("uint_vector_u32", &|b| {
no_panic("uint_vector_u32", |r| decode_uint_vector_u32(r), b);
});
f("uint_vector_u64", &|b| {
no_panic("uint_vector_u64", |r| decode_uint_vector_u64(r), b);
});
f("string_vector", &|b| {
no_panic("string_vector", |r| decode_string_vector(r), b);
});
f("s2cell_id_vector", &|b| {
no_panic("s2cell_id_vector", |r| decode_s2cell_id_vector(r), b);
});
f("s2point_vector", &|b| {
no_panic("s2point_vector", |r| decode_s2point_vector(r), b);
});
}
fn valid_encodings(r: &mut ChaCha8Rng) -> Vec<(&'static str, Vec<u8>)> {
let u32s: Vec<u32> = (0..16).map(|_| r.r#gen()).collect();
let u64s: Vec<u64> = (0..16).map(|_| r.r#gen()).collect();
let strings: Vec<Vec<u8>> = (0..6)
.map(|_| {
let n = r.gen_range(0..12);
(0..n).map(|_| r.r#gen()).collect()
})
.collect();
let cells = rand_cell_ids(r, 16);
let points = rand_points(r, 16);
vec![
("uint_vector_u32", enc_u32_vec(&u32s)),
("uint_vector_u64", enc_u64_vec(&u64s)),
("string_vector", enc_string_vec(&strings)),
("s2cell_id_vector", enc_cell_id_vec(&cells)),
(
"s2point_vector_fast",
enc_point_vec(&points, CodingHint::Fast),
),
(
"s2point_vector_compact",
enc_point_vec(&points, CodingHint::Compact),
),
]
}
fn decode_by_label(label: &str, b: &[u8]) {
let _ = label;
for_each_decoder(|_, run| run(b));
}
#[test]
fn round_trip_valid() {
let mut r = rng(0xA1);
for _ in 0..200 {
let n = r.gen_range(0..32);
let u32s: Vec<u32> = (0..n).map(|_| r.r#gen()).collect();
assert_eq!(
decode_uint_vector_u32(&mut enc_u32_vec(&u32s).as_slice()).unwrap(),
u32s
);
let u64s: Vec<u64> = (0..n).map(|_| r.r#gen()).collect();
assert_eq!(
decode_uint_vector_u64(&mut enc_u64_vec(&u64s).as_slice()).unwrap(),
u64s
);
let strings: Vec<Vec<u8>> = (0..n)
.map(|_| {
let m = r.gen_range(0..16);
(0..m).map(|_| r.r#gen()).collect()
})
.collect();
assert_eq!(
decode_string_vector(&mut enc_string_vec(&strings).as_slice()).unwrap(),
strings
);
let cells = rand_cell_ids(&mut r, n);
assert_eq!(
decode_s2cell_id_vector(&mut enc_cell_id_vec(&cells).as_slice()).unwrap(),
cells
);
let points = rand_points(&mut r, n);
let decoded_fast =
decode_s2point_vector(&mut enc_point_vec(&points, CodingHint::Fast).as_slice())
.unwrap();
assert_eq!(decoded_fast, points);
let decoded_compact =
decode_s2point_vector(&mut enc_point_vec(&points, CodingHint::Compact).as_slice())
.unwrap();
assert_eq!(decoded_compact.len(), points.len());
}
}
#[test]
fn truncation_resilience() {
let mut r = rng(0xB2);
for (_, bytes) in valid_encodings(&mut r) {
for cut in 0..=bytes.len() {
decode_by_label("", &bytes[..cut]);
}
}
}
#[test]
fn mutation_fuzz() {
let mut r = rng(0xC3);
for _ in 0..4000 {
let seeds = valid_encodings(&mut r);
let (_, base) = &seeds[r.gen_range(0..seeds.len())];
let mut bytes = base.clone();
for _ in 0..r.gen_range(1..=6) {
if bytes.is_empty() {
bytes.push(r.r#gen());
continue;
}
match r.gen_range(0..4) {
0 => {
let i = r.gen_range(0..bytes.len());
bytes[i] ^= 1 << r.gen_range(0..8);
}
1 => {
let i = r.gen_range(0..bytes.len());
bytes[i] = r.r#gen();
}
2 => {
let i = r.gen_range(0..=bytes.len());
bytes.insert(i, r.r#gen());
}
_ => {
let i = r.gen_range(0..bytes.len());
bytes.remove(i);
}
}
}
decode_by_label("", &bytes);
}
}
#[test]
fn random_bytes_fuzz() {
let mut r = rng(0xD4);
for _ in 0..20_000 {
let len = r.gen_range(0..=48);
let bytes: Vec<u8> = (0..len).map(|_| r.r#gen()).collect();
decode_by_label("", &bytes);
}
}
#[test]
fn adversarial_inputs() {
decode_by_label("", &[]);
for b in 0u16..=255 {
decode_by_label("", &[b as u8]);
}
for n in [1usize, 2, 4, 8, 10, 16, 32] {
decode_by_label("", &vec![0xFFu8; n]);
}
let mut overstated = Vec::new();
encode_uint_vector_u64(&(0..100_000u64).collect::<Vec<u64>>(), &mut overstated).unwrap();
let header_only = &overstated[..overstated.len().min(4)];
decode_by_label("", header_only);
}
#[test]
fn regression_fuzz_crashes() {
let s2point_overflow: &[u8] = &[
0x99, 0x60, 0x68, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x6f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0d, 0x3c, 0x0d, 0x0d,
0x7a, 0x0d, 0x0d, 0x0d, 0x28, 0xe0, 0xe0, 0xdb, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d,
0x0d, 0x29, 0x29, 0x29, 0xb9, 0x00, 0x00, 0x07, 0x20,
];
decode_by_label("", s2point_overflow);
}