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_s2shape_index::EncodedS2ShapeIndex;
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::encoding::{S2Decode, S2Encode};
use s2rst::s2::lax_polygon::LaxPolygon;
use s2rst::s2::lax_polyline::LaxPolyline;
use s2rst::s2::point_compression::{
decode_points_compressed, encode_points_compressed, points_to_xyz_face_si_ti,
};
use s2rst::s2::point_vector::PointVector;
use s2rst::s2::polyline::Polyline;
use s2rst::s2::shape_index::ShapeIndex;
use s2rst::s2::{Cap, Cell, CellId, CellUnion, LatLng, Loop, Point, Polygon, Rect};
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 no_panic_bytes<F>(label: &str, decode: F, input: &[u8])
where
F: Fn(&[u8]),
{
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| decode(input)));
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);
}
#[test]
fn regression_high_level_crashes() {
let shape_index_bound: &[u8] = &[
0x2a, 0x08, 0x4a, 0x01, 0x01, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
0x3f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x8a, 0x01, 0x00, 0x00, 0x27, 0xdc, 0xf7, 0xff, 0xff, 0xff, 0xfc,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xc9, 0x58, 0x00, 0x00, 0x00, 0x00, 0x00, 0xde, 0xa1, 0x3f, 0x08,
0xe0, 0x10, 0x08, 0x01, 0x00,
];
let shape_index_overflow: &[u8] = &[
0x2a, 0x80, 0x00, 0xf0, 0x12, 0x2c, 0xef, 0x11, 0x30, 0x10, 0x12, 0x10, 0x10, 0x2c, 0xef,
0xe7, 0x11, 0x2a, 0x08, 0x4a, 0x02, 0x02, 0x04, 0x19, 0xff, 0x01, 0x00, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0x12, 0x2c, 0xef, 0x11, 0x30, 0x10, 0x12, 0x10, 0x10,
0x2c, 0xef, 0xe7, 0x11, 0x2a, 0x08, 0x4a, 0x02, 0x02, 0x04, 0x19, 0xff, 0x01, 0x00, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x08, 0xff, 0xff, 0x10, 0x10, 0xff, 0xff, 0x10,
0x10,
];
for input in [shape_index_bound, shape_index_overflow] {
no_panic_bytes(
"s2shape_index",
|b| {
let mut idx = EncodedS2ShapeIndex::new();
let _unused = idx.init(b);
},
input,
);
}
let polygon_compressed_nan: &[u8] = &[
0x04, 0x03, 0x22, 0x0b, 0xc8, 0x05, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x10, 0x00, 0x01, 0x05, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x04, 0x03, 0x22, 0x80, 0x00,
0x00, 0x00, 0x00,
];
no_panic(
"polygon",
|r| <Polygon as S2Decode>::decode(r),
polygon_compressed_nan,
);
let polygon_compressed_degenerate: &[u8] = &[
0x04, 0x04, 0x02, 0x0a, 0x59, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x08, 0x04, 0x04, 0x0a, 0xdd, 0x01, 0x00, 0x00, 0x40, 0x00, 0x04, 0x00, 0x00,
0x00,
];
no_panic(
"polygon",
|r| <Polygon as S2Decode>::decode(r),
polygon_compressed_degenerate,
);
}
#[test]
fn compressed_points_robustness() {
let mut r = rng(0xE5);
let level = 20u8;
for _ in 0..100 {
let n = r.gen_range(1..32);
let points = rand_points(&mut r, n);
let xyz = points_to_xyz_face_si_ti(&points);
let mut buf = Vec::new();
encode_points_compressed(&mut buf, &xyz, level).unwrap();
let decoded = decode_points_compressed(&mut buf.as_slice(), level, points.len()).unwrap();
assert_eq!(decoded.len(), points.len());
for cut in 0..=buf.len() {
no_panic(
"compressed_points",
|rr| decode_points_compressed(rr, level, points.len()),
&buf[..cut],
);
}
}
for _ in 0..20_000 {
let len = r.gen_range(0..=64);
let bytes: Vec<u8> = (0..len).map(|_| r.r#gen()).collect();
if bytes.len() < 3 {
continue;
}
let lvl: u8 = bytes[0] % 31; let num_points = (u16::from_le_bytes([bytes[1], bytes[2]]) % 4096) as usize;
no_panic(
"compressed_points",
|rr| decode_points_compressed(rr, lvl, num_points),
&bytes[3..],
);
}
}
#[test]
fn high_level_decoders_truncation() {
let square = Polygon::from_loops(vec![Loop::new(vec![
LatLng::from_degrees(-10.0, -10.0).to_point(),
LatLng::from_degrees(-10.0, 10.0).to_point(),
LatLng::from_degrees(10.0, 10.0).to_point(),
LatLng::from_degrees(10.0, -10.0).to_point(),
])]);
for poly in [Polygon::empty(), Polygon::full(), square] {
let mut buf = Vec::new();
poly.encode(&mut buf).unwrap();
let decoded = <Polygon as S2Decode>::decode(&mut buf.as_slice()).unwrap();
assert_eq!(decoded.num_loops(), poly.num_loops());
for cut in 0..=buf.len() {
no_panic("polygon", |r| <Polygon as S2Decode>::decode(r), &buf[..cut]);
}
}
let mut index = ShapeIndex::new();
index.build();
let mut buf = Vec::new();
index.encode_to_writer(&mut buf).unwrap();
let mut enc = EncodedS2ShapeIndex::new();
enc.init(&buf).unwrap();
assert_eq!(enc.num_shape_ids(), 0);
for cut in 0..=buf.len() {
no_panic_bytes(
"s2shape_index",
|b| {
let mut idx = EncodedS2ShapeIndex::new();
let _unused = idx.init(b);
},
&buf[..cut],
);
}
}
#[test]
fn no_decoder_panics_on_any_input() {
fn enc<T: S2Encode>(x: &T) -> Vec<u8> {
let mut b = Vec::new();
x.encode(&mut b).unwrap();
b
}
let mut r = rng(0x5A);
let square: Vec<Point> = [(-10.0, -10.0), (-10.0, 10.0), (10.0, 10.0), (10.0, -10.0)]
.iter()
.map(|&(lat, lng)| LatLng::from_degrees(lat, lng).to_point())
.collect();
let pts = rand_points(&mut r, 6);
let cells = rand_cell_ids(&mut r, 6);
let mut index = ShapeIndex::new();
index.add(Box::new(LaxPolyline::new(pts.clone())));
index.add(Box::new(PointVector::new(pts.clone())));
index.add(Box::new(LaxPolygon::from_loops(&[&square])));
index.build();
let mut si_bytes = Vec::new();
index.encode_to_writer(&mut si_bytes).unwrap();
let mut seeds: Vec<Vec<u8>> = vec![
enc(&rand_point(&mut r)),
enc(&Cap::from_point(rand_point(&mut r))),
enc(&Rect::from_point_pair(
LatLng::from_degrees(-1.0, -2.0),
LatLng::from_degrees(3.0, 4.0),
)),
enc(&CellId::from_point(&rand_point(&mut r))),
enc(&CellUnion::from_cell_ids(cells.clone())),
enc(&Cell::from(CellId::from_point(&rand_point(&mut r)))),
enc(&Polyline::new(pts.clone())),
enc(&Loop::new(square.clone())),
enc(&Polygon::from_loops(vec![Loop::new(square.clone())])),
enc(&LaxPolyline::new(pts.clone())),
enc(&LaxPolygon::from_loops(&[&square])),
enc(&PointVector::new(pts.clone())),
si_bytes,
enc_u32_vec(&[1, 2, 3, 4]),
enc_u64_vec(&[1, 2, 3, 4]),
enc_string_vec(&[vec![1, 2], vec![3]]),
enc_cell_id_vec(&cells),
enc_point_vec(&pts, CodingHint::Fast),
enc_point_vec(&pts, CodingHint::Compact),
];
{
let xyz = points_to_xyz_face_si_ti(&pts);
let count = (pts.len() as u16).to_le_bytes();
let mut b = vec![20u8, count[0], count[1]];
encode_points_compressed(&mut b, &xyz, 20u8).unwrap();
seeds.push(b);
}
let mut corpus: Vec<Vec<u8>> = Vec::new();
for s in &seeds {
corpus.push(s.clone());
for cut in 0..s.len() {
corpus.push(s[..cut].to_vec());
}
}
for _ in 0..2000 {
let base = &seeds[r.gen_range(0..seeds.len())];
let mut b = base.clone();
for _ in 0..r.gen_range(1..=6) {
if b.is_empty() {
b.push(r.r#gen());
continue;
}
match r.gen_range(0..4) {
0 => {
let i = r.gen_range(0..b.len());
b[i] ^= 1 << r.gen_range(0..8);
}
1 => {
let i = r.gen_range(0..b.len());
b[i] = r.r#gen();
}
2 => {
let i = r.gen_range(0..=b.len());
b.insert(i, r.r#gen());
}
_ => {
let i = r.gen_range(0..b.len());
b.remove(i);
}
}
}
corpus.push(b);
}
for _ in 0..10_000 {
let n = r.gen_range(0..=64);
corpus.push((0..n).map(|_| r.r#gen()).collect());
}
corpus.push(vec![]);
for b in 0u16..=255 {
corpus.push(vec![b as u8]);
}
for n in [1usize, 2, 4, 8, 10, 16, 32, 64] {
corpus.push(vec![0xFF; n]);
}
type Decoder = Box<dyn Fn(&[u8])>;
let decoders: Vec<(&str, Decoder)> = vec![
(
"uint_vector_u32",
Box::new(|b| drop(decode_uint_vector_u32(&mut &b[..]))),
),
(
"uint_vector_u64",
Box::new(|b| drop(decode_uint_vector_u64(&mut &b[..]))),
),
(
"string_vector",
Box::new(|b| drop(decode_string_vector(&mut &b[..]))),
),
(
"s2cell_id_vector",
Box::new(|b| drop(decode_s2cell_id_vector(&mut &b[..]))),
),
(
"s2point_vector",
Box::new(|b| drop(decode_s2point_vector(&mut &b[..]))),
),
(
"point",
Box::new(|b| drop(<Point as S2Decode>::decode(&mut &b[..]))),
),
(
"cap",
Box::new(|b| drop(<Cap as S2Decode>::decode(&mut &b[..]))),
),
(
"rect",
Box::new(|b| drop(<Rect as S2Decode>::decode(&mut &b[..]))),
),
(
"cellid",
Box::new(|b| drop(<CellId as S2Decode>::decode(&mut &b[..]))),
),
(
"cellunion",
Box::new(|b| drop(<CellUnion as S2Decode>::decode(&mut &b[..]))),
),
(
"cell",
Box::new(|b| drop(<Cell as S2Decode>::decode(&mut &b[..]))),
),
(
"polyline",
Box::new(|b| drop(<Polyline as S2Decode>::decode(&mut &b[..]))),
),
(
"loop",
Box::new(|b| drop(<Loop as S2Decode>::decode(&mut &b[..]))),
),
(
"polygon",
Box::new(|b| drop(<Polygon as S2Decode>::decode(&mut &b[..]))),
),
(
"lax_polyline",
Box::new(|b| drop(<LaxPolyline as S2Decode>::decode(&mut &b[..]))),
),
(
"lax_polygon",
Box::new(|b| drop(<LaxPolygon as S2Decode>::decode(&mut &b[..]))),
),
(
"point_vector",
Box::new(|b| drop(<PointVector as S2Decode>::decode(&mut &b[..]))),
),
(
"s2shape_index",
Box::new(|b| {
let mut idx = EncodedS2ShapeIndex::new();
drop(idx.init(b));
}),
),
(
"points_compressed",
Box::new(|b| {
if b.len() >= 3 {
let lvl = b[0] % 31;
let n = (u16::from_le_bytes([b[1], b[2]]) % 4096) as usize;
drop(decode_points_compressed(&mut &b[3..], lvl, n));
}
}),
),
];
thread_local! {
static LAST_PANIC: std::cell::RefCell<String> = const { std::cell::RefCell::new(String::new()) };
}
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|info| {
let loc = info
.location()
.map_or_else(String::new, |l| format!("{}:{}", l.file(), l.line()));
LAST_PANIC.with(|c| *c.borrow_mut() = loc);
}));
let mut failures: Vec<(&str, String, Vec<u8>)> = Vec::new();
for (name, decode) in &decoders {
for input in &corpus {
let res = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| decode(input)));
if res.is_err() {
let loc = LAST_PANIC.with(|c| c.borrow().clone());
failures.push((name, loc, input.clone()));
break;
}
}
}
std::panic::set_hook(prev_hook);
assert!(
failures.is_empty(),
"decoders panicked on crafted input:\n{}",
failures
.iter()
.map(|(n, loc, b)| format!(" {n} @ {loc}: {b:02x?}"))
.collect::<Vec<_>>()
.join("\n")
);
}