use bytes::Bytes;
use crate::error::{BlobError, Result, new_blob_error, new_wire_error};
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum BlobKind {
OsmHeader,
OsmData,
Unknown(String),
}
#[derive(Clone, Debug)]
pub(crate) struct WireBlobHeader {
pub blob_type: BlobKind,
pub datasize: i32,
pub indexdata: Option<[u8; crate::blob_meta::INDEX_SIZE]>,
pub tagdata: Option<Box<[u8]>>,
pub waymembers: Option<Box<[u8]>>,
}
impl WireBlobHeader {
#[hotpath::measure]
pub fn parse(
data: &[u8],
parse_tagdata: bool,
parse_indexdata: bool,
parse_waymembers: bool,
) -> Result<Self> {
use super::wire::Cursor;
let mut cursor = Cursor::new(data);
let mut blob_type = BlobKind::Unknown(String::new());
let mut datasize: i32 = 0;
let mut indexdata: Option<[u8; crate::blob_meta::INDEX_SIZE]> = None;
let mut tagdata: Option<Box<[u8]>> = None;
let mut waymembers: Option<Box<[u8]>> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match field {
1 => {
let bytes = cursor.read_len_delimited()?;
blob_type = match bytes {
b"OSMHeader" => BlobKind::OsmHeader,
b"OSMData" => BlobKind::OsmData,
_ => BlobKind::Unknown(
String::from_utf8(bytes.to_vec())
.map_err(|_| new_wire_error("invalid UTF-8 in BlobHeader type"))?,
),
};
}
2 if parse_indexdata => {
let bytes = cursor.read_len_delimited()?;
let len = bytes.len();
if len == crate::blob_meta::INDEX_SIZE || len == 26 {
let mut buf = [0u8; crate::blob_meta::INDEX_SIZE];
buf[..len].copy_from_slice(bytes);
indexdata = Some(buf);
}
}
3 => {
#[allow(clippy::cast_possible_truncation)]
{
datasize = cursor.read_varint()? as i32;
}
}
4 if parse_tagdata => {
let bytes = cursor.read_len_delimited()?;
if !bytes.is_empty() {
tagdata = Some(bytes.into());
}
}
5 if parse_waymembers => {
let bytes = cursor.read_len_delimited()?;
if !bytes.is_empty() {
waymembers = Some(bytes.into());
}
}
_ => cursor.skip_field(wire_type)?,
}
}
Ok(WireBlobHeader {
blob_type,
datasize,
indexdata,
tagdata,
waymembers,
})
}
}
#[derive(Clone, Debug)]
pub(crate) enum BlobData {
Raw(Bytes),
Zlib(Bytes),
Zstd(Bytes),
}
#[derive(Clone, Debug)]
pub(crate) struct WireBlob {
pub data: Option<BlobData>,
pub raw_size: Option<i32>,
}
impl WireBlob {
pub fn parse(input: &Bytes) -> Result<Self> {
use super::wire::Cursor;
let mut cursor = Cursor::new(input);
let mut data: Option<BlobData> = None;
let mut raw_size: Option<i32> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match field {
1 => {
let slice = cursor.read_len_delimited()?;
let offset = slice.as_ptr() as usize - input.as_ptr() as usize;
data = Some(BlobData::Raw(input.slice(offset..offset + slice.len())));
}
2 => {
#[allow(clippy::cast_possible_truncation)]
{
raw_size = Some(cursor.read_varint()? as i32);
}
}
3 => {
let slice = cursor.read_len_delimited()?;
let offset = slice.as_ptr() as usize - input.as_ptr() as usize;
data = Some(BlobData::Zlib(input.slice(offset..offset + slice.len())));
}
7 => {
let slice = cursor.read_len_delimited()?;
let offset = slice.as_ptr() as usize - input.as_ptr() as usize;
data = Some(BlobData::Zstd(input.slice(offset..offset + slice.len())));
}
_ => cursor.skip_field(wire_type)?,
}
}
Ok(WireBlob { data, raw_size })
}
pub fn parse_slice(data: &[u8]) -> Result<Self> {
let bytes = Bytes::copy_from_slice(data);
Self::parse(&bytes)
}
#[allow(clippy::cast_sign_loss)]
pub fn estimated_capacity(&self) -> usize {
self.raw_size.unwrap_or(0).max(0) as usize
}
}
pub const MAX_BLOB_HEADER_SIZE: u64 = 64 * 1024;
pub const MAX_BLOB_MESSAGE_SIZE: u64 = 32 * 1024 * 1024;
pub const MAX_BLOB_DATASIZE: u64 = MAX_BLOB_MESSAGE_SIZE;
#[allow(clippy::type_complexity)]
pub(crate) fn parse_blob_header_with_index(
header_bytes: &[u8],
) -> Result<(
BlobKind,
usize,
Option<[u8; crate::blob_meta::INDEX_SIZE]>,
Option<Box<[u8]>>,
)> {
let header = WireBlobHeader::parse(header_bytes, true, true, false)?;
if header.datasize < 0 {
return Err(new_blob_error(BlobError::InvalidDataSize {
size: header.datasize,
}));
}
#[allow(clippy::cast_sign_loss)]
let datasize_u64 = header.datasize as u64;
if datasize_u64 >= MAX_BLOB_DATASIZE {
return Err(new_blob_error(BlobError::DataSizeTooBig {
size: datasize_u64,
}));
}
#[allow(clippy::cast_sign_loss)]
Ok((
header.blob_type,
header.datasize as usize,
header.indexdata,
header.tagdata,
))
}
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::cast_possible_truncation)]
mod tests {
use super::{BlobError, BlobKind, MAX_BLOB_DATASIZE, parse_blob_header_with_index};
use crate::error::ErrorKind;
fn header_with_datasize(datasize: u64) -> Vec<u8> {
let mut h = Vec::new();
h.push(0x0A);
h.push(7);
h.extend_from_slice(b"OSMData");
h.push(0x18);
let mut v = datasize;
loop {
let mut byte = (v & 0x7f) as u8;
v >>= 7;
if v != 0 {
byte |= 0x80;
}
h.push(byte);
if v == 0 {
break;
}
}
h
}
#[test]
fn datasize_at_or_over_cap_is_rejected() {
let max_legal = MAX_BLOB_DATASIZE - 1;
let (_, data_size, _, _) =
parse_blob_header_with_index(&header_with_datasize(max_legal)).unwrap();
assert_eq!(data_size as u64, max_legal);
let err =
parse_blob_header_with_index(&header_with_datasize(MAX_BLOB_DATASIZE)).unwrap_err();
match err.into_kind() {
ErrorKind::Blob(BlobError::DataSizeTooBig { size }) => {
assert_eq!(size, MAX_BLOB_DATASIZE);
}
other => panic!("expected DataSizeTooBig, got {other:?}"),
}
let over = MAX_BLOB_DATASIZE + 4096;
let err = parse_blob_header_with_index(&header_with_datasize(over)).unwrap_err();
match err.into_kind() {
ErrorKind::Blob(BlobError::DataSizeTooBig { size }) => {
assert_eq!(size, over);
}
other => panic!("expected DataSizeTooBig, got {other:?}"),
}
}
#[test]
fn small_datasize_parses_normally() {
let (blob_type, data_size, _, _) =
parse_blob_header_with_index(&header_with_datasize(1234)).unwrap();
assert_eq!(blob_type, BlobKind::OsmData);
assert_eq!(data_size, 1234);
}
}