#![allow(
clippy::unwrap_used,
clippy::cognitive_complexity,
clippy::too_many_lines,
clippy::cast_possible_truncation
)]
use std::io::Cursor;
use pbfhogg::block_builder::{self, BlockBuilder};
use pbfhogg::writer::{Compression, PbfWriter};
use pbfhogg::{BlobError, BlobReader, BlobType, ErrorKind};
fn write_header_only_pbf() -> Vec<u8> {
let mut buf = Vec::new();
{
let mut writer = PbfWriter::new(&mut buf, Compression::default());
let header = block_builder::HeaderBuilder::new().build().unwrap();
writer.write_header(&header).unwrap();
writer.flush().unwrap();
}
buf
}
fn write_one_block_pbf() -> Vec<u8> {
let mut buf = Vec::new();
{
let mut writer = PbfWriter::new(&mut buf, Compression::default());
let header = block_builder::HeaderBuilder::new().build().unwrap();
writer.write_header(&header).unwrap();
let mut bb = BlockBuilder::new();
bb.add_node(1, 0, 0, std::iter::empty::<(&str, &str)>(), None);
writer
.write_primitive_block(bb.take().unwrap().unwrap())
.unwrap();
writer.flush().unwrap();
}
buf
}
#[test]
fn empty_file() {
let data: &[u8] = &[];
let mut reader = BlobReader::new(Cursor::new(data));
assert!(reader.next().is_none(), "empty input should yield None");
}
#[test]
fn truncated_header_size() {
for len in 1..=3 {
let data = vec![0xAA; len];
let mut reader = BlobReader::new(Cursor::new(data));
assert!(
reader.next().is_none(),
"1-3 trailing bytes should be tolerated as clean EOF (got Some for len={len})",
);
}
}
#[test]
fn header_too_big() {
let data = 65536u32.to_be_bytes().to_vec();
let mut reader = BlobReader::new(Cursor::new(data));
let err = reader.next().unwrap().unwrap_err();
match err.into_kind() {
ErrorKind::Blob(BlobError::HeaderTooBig { size }) => {
assert_eq!(size, 65536);
}
other => panic!("expected HeaderTooBig, got {other:?}"),
}
}
#[test]
fn truncated_header_data() {
let mut data = Vec::new();
data.extend_from_slice(&20u32.to_be_bytes()); data.extend_from_slice(&[0x0A; 5]); let mut reader = BlobReader::new(Cursor::new(data));
let err = reader.next().unwrap().unwrap_err();
match err.into_kind() {
ErrorKind::Io(ref e) if e.kind() == std::io::ErrorKind::UnexpectedEof => {}
other => panic!("expected Io(UnexpectedEof) for truncated header, got {other:?}"),
}
}
#[test]
fn garbage_header() {
let mut data = Vec::new();
data.extend_from_slice(&10u32.to_be_bytes()); data.extend_from_slice(&[0xFF; 10]); let mut reader = BlobReader::new(Cursor::new(data));
let err = reader.next().unwrap().unwrap_err();
match err.into_kind() {
ErrorKind::WireFormat { .. } => {}
other => panic!("expected WireFormat error for garbage header, got {other:?}"),
}
}
#[test]
fn truncated_blob_data() {
let full = write_one_block_pbf();
let second_offset = {
let mut reader =
BlobReader::new_seekable(Cursor::new(full.as_slice())).unwrap();
let _ = reader.next().unwrap().unwrap(); let second = reader.next().unwrap().unwrap(); second.offset().unwrap().0 as usize
};
let truncated = &full[..second_offset + 6];
let mut reader = BlobReader::new(Cursor::new(truncated));
let first = reader.next().unwrap().unwrap();
assert_eq!(first.get_type(), BlobType::OsmHeader);
match reader.next() {
Some(Err(_)) => {} other => panic!("expected error for truncated blob, got {other:?}"),
}
}
#[test]
fn has_indexdata_rejects_oversized_header_length() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("adversarial.osm.pbf");
std::fs::write(&path, 65536u32.to_be_bytes()).unwrap();
let err = pbfhogg::has_indexdata(&path, false).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("header") && (msg.contains("too big") || msg.contains("65536")),
"expected HeaderTooBig surface, got: {msg}",
);
}
#[test]
fn cat_rejects_oversized_header_length() {
let dir = tempfile::tempdir().unwrap();
let input = dir.path().join("adversarial.osm.pbf");
let output = dir.path().join("out.osm.pbf");
std::fs::write(&input, 65536u32.to_be_bytes()).unwrap();
let result = pbfhogg::cat::cat(
&[input.as_path()],
&output,
None,
&pbfhogg::cat::CleanAttrs::default(),
Compression::default(),
false,
false,
&pbfhogg::HeaderOverrides::default(),
);
let err = match result {
Ok(_) => panic!("expected cat() to error on adversarial file"),
Err(e) => e,
};
let msg = format!("{err}");
assert!(
msg.contains("header") && (msg.contains("too big") || msg.contains("65536")),
"expected HeaderTooBig surface, got: {msg}",
);
}
#[cfg(feature = "commands")]
#[test]
fn check_refs_rejects_schedule_entry_past_eof() {
let dir = tempfile::tempdir().unwrap();
let input = dir.path().join("truncated.osm.pbf");
let full = write_one_block_pbf();
assert!(full.len() > 60);
let truncated = &full[..full.len() - 20];
std::fs::write(&input, truncated).unwrap();
let result = pbfhogg::check::refs::check_refs(
&input,
false, false, false, );
let err = match result {
Ok(_) => panic!("expected check_refs to error on truncated PBF"),
Err(e) => e,
};
let msg = format!("{err}");
assert!(
msg.contains("data_size")
|| msg.contains("file is only")
|| msg.contains("blob payload truncated"),
"expected schedule-builder or walker truncation error, got: {msg}",
);
}
#[cfg(feature = "commands")]
#[test]
fn inspect_rejects_oversized_header_length_via_walker() {
let dir = tempfile::tempdir().unwrap();
let path = dir.path().join("adversarial.osm.pbf");
std::fs::write(&path, 65536u32.to_be_bytes()).unwrap();
let result = pbfhogg::inspect::inspect(&path, false, false, false, false, false);
let err = match result {
Ok(_) => panic!("expected inspect() to error on adversarial file"),
Err(e) => e,
};
let msg = format!("{err}");
assert!(
msg.contains("header") && (msg.contains("too big") || msg.contains("65536")),
"expected HeaderTooBig surface, got: {msg}",
);
}
#[test]
fn iteration_stops_after_error() {
let mut data = write_header_only_pbf();
data.extend_from_slice(&20u32.to_be_bytes());
data.extend_from_slice(&[0x0A; 5]);
let mut reader = BlobReader::new(Cursor::new(data));
let first = reader.next().unwrap().unwrap();
assert_eq!(first.get_type(), BlobType::OsmHeader);
let second = reader.next().unwrap();
assert!(second.is_err());
assert!(reader.next().is_none());
}