use crate::error::Result;
pub(crate) use protohoggr::{
Cursor, PackedBoolIter, PackedInt32Iter, PackedSint32Iter, PackedSint64Iter, PackedUint32Iter,
WIRE_LEN, WIRE_VARINT,
};
#[derive(Clone, Debug)]
pub(crate) struct WireStringTable<'a> {
buffer: &'a [u8],
entries_offset: u32,
entries_count: u32,
}
impl<'a> WireStringTable<'a> {
fn scan_entries(data: &[u8], buffer: &[u8], out: &mut Vec<(u32, u32)>) -> Result<()> {
let mut cursor = Cursor::new(data);
out.clear();
while let Some((field, wire_type)) = cursor.read_tag()? {
if field == 1 && wire_type == WIRE_LEN {
let bytes = cursor.read_len_delimited()?;
let offset = bytes.as_ptr() as usize - buffer.as_ptr() as usize;
#[allow(clippy::cast_possible_truncation)]
out.push((offset as u32, bytes.len() as u32));
} else {
cursor.skip_field(wire_type)?;
}
}
Ok(())
}
fn new(buffer: &'a [u8], entries_offset: u32, entries_count: u32) -> Self {
Self {
buffer,
entries_offset,
entries_count,
}
}
#[inline]
pub fn len(&self) -> usize {
self.entries_count as usize
}
#[inline]
pub fn get(&self, index: usize) -> Option<&'a [u8]> {
if index >= self.entries_count as usize {
return None;
}
let base = self.entries_offset as usize + index * 8;
if base + 8 > self.buffer.len() {
return None;
}
let off = u32::from_le_bytes([
self.buffer[base],
self.buffer[base + 1],
self.buffer[base + 2],
self.buffer[base + 3],
]);
let len = u32::from_le_bytes([
self.buffer[base + 4],
self.buffer[base + 5],
self.buffer[base + 6],
self.buffer[base + 7],
]);
Some(&self.buffer[off as usize..off as usize + len as usize])
}
}
#[derive(Debug)]
pub(crate) struct WireBlock<'a> {
buffer: &'a [u8],
pub stringtable: WireStringTable<'a>,
#[allow(dead_code)]
st_raw_offset: u32,
#[allow(dead_code)]
st_raw_len: u32,
group_ranges_offset: u32,
group_ranges_count: u32,
pub granularity: i32,
pub lat_offset: i64,
pub lon_offset: i64,
pub date_granularity: i32,
pub proto_len: u32,
}
impl<'a> WireBlock<'a> {
#[hotpath::measure]
#[allow(clippy::cast_possible_truncation)]
pub fn parse_and_inline(buf: &mut Vec<u8>) -> Result<WireBlockMeta> {
let mut st_scratch = Vec::new();
let mut gr_scratch = Vec::new();
Self::parse_and_inline_with_scratch(buf, &mut st_scratch, &mut gr_scratch)
}
#[hotpath::measure]
#[allow(clippy::cast_possible_truncation)]
pub fn parse_and_inline_with_scratch(
buf: &mut Vec<u8>,
st_entries: &mut Vec<(u32, u32)>,
group_entries: &mut Vec<(u32, u32)>,
) -> Result<WireBlockMeta> {
let proto_len = buf.len() as u32;
st_entries.clear();
group_entries.clear();
let mut granularity: i32 = 100;
let mut lat_offset: i64 = 0;
let mut lon_offset: i64 = 0;
let mut date_granularity: i32 = 1000;
let mut stringtable_offset: usize = 0;
let mut stringtable_len: usize = 0;
{
let buffer: &[u8] = buf;
let mut cursor = Cursor::new(buffer);
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_LEN) => {
let data = cursor.read_len_delimited()?;
stringtable_offset = data.as_ptr() as usize - buffer.as_ptr() as usize;
stringtable_len = data.len();
}
(2, WIRE_LEN) => {
let data = cursor.read_len_delimited()?;
let offset = data.as_ptr() as usize - buffer.as_ptr() as usize;
group_entries.push((offset as u32, data.len() as u32));
}
(17, WIRE_VARINT) => {
#[allow(clippy::cast_possible_wrap)]
{
granularity = cursor.read_varint()? as i32;
}
}
(19, WIRE_VARINT) => {
lat_offset = cursor.read_varint_i64()?;
}
(20, WIRE_VARINT) => {
lon_offset = cursor.read_varint_i64()?;
}
(18, WIRE_VARINT) => {
#[allow(clippy::cast_possible_wrap)]
{
date_granularity = cursor.read_varint()? as i32;
}
}
_ => cursor.skip_field(wire_type)?,
}
}
let st_data = if stringtable_len > 0 {
&buffer[stringtable_offset..stringtable_offset + stringtable_len]
} else {
&[]
};
WireStringTable::scan_entries(st_data, buffer, st_entries)?;
}
let append_size = (st_entries.len() + group_entries.len()) * 8;
buf.reserve(append_size);
let st_inline_offset = buf.len() as u32;
let st_inline_count = st_entries.len() as u32;
for &(off, len) in st_entries.iter() {
buf.extend_from_slice(&off.to_le_bytes());
buf.extend_from_slice(&len.to_le_bytes());
}
let gr_inline_offset = buf.len() as u32;
let gr_inline_count = group_entries.len() as u32;
for &(off, len) in group_entries.iter() {
buf.extend_from_slice(&off.to_le_bytes());
buf.extend_from_slice(&len.to_le_bytes());
}
#[allow(clippy::cast_possible_truncation)]
Ok(WireBlockMeta {
proto_len,
st_raw_offset: stringtable_offset as u32,
st_raw_len: stringtable_len as u32,
st_inline_offset,
st_inline_count,
gr_inline_offset,
gr_inline_count,
granularity,
lat_offset,
lon_offset,
date_granularity,
})
}
pub fn from_inline(buffer: &'a [u8], meta: &WireBlockMeta) -> Self {
Self {
buffer,
stringtable: WireStringTable::new(buffer, meta.st_inline_offset, meta.st_inline_count),
st_raw_offset: meta.st_raw_offset,
st_raw_len: meta.st_raw_len,
group_ranges_offset: meta.gr_inline_offset,
group_ranges_count: meta.gr_inline_count,
granularity: meta.granularity,
lat_offset: meta.lat_offset,
lon_offset: meta.lon_offset,
date_granularity: meta.date_granularity,
proto_len: meta.proto_len,
}
}
#[inline]
pub fn group_count(&self) -> usize {
self.group_ranges_count as usize
}
#[inline]
#[allow(dead_code)]
pub fn raw_stringtable(&self) -> &'a [u8] {
&self.buffer
[self.st_raw_offset as usize..self.st_raw_offset as usize + self.st_raw_len as usize]
}
#[inline]
#[allow(dead_code)]
pub fn raw_proto(&self) -> &'a [u8] {
&self.buffer[..self.proto_len as usize]
}
#[inline]
pub fn group(&self, index: usize) -> &'a [u8] {
let base = self.group_ranges_offset as usize + index * 8;
let off = u32::from_le_bytes([
self.buffer[base],
self.buffer[base + 1],
self.buffer[base + 2],
self.buffer[base + 3],
]);
let len = u32::from_le_bytes([
self.buffer[base + 4],
self.buffer[base + 5],
self.buffer[base + 6],
self.buffer[base + 7],
]);
&self.buffer[off as usize..off as usize + len as usize]
}
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct WireBlockMeta {
pub proto_len: u32,
pub st_raw_offset: u32,
pub st_raw_len: u32,
pub st_inline_offset: u32,
pub st_inline_count: u32,
pub gr_inline_offset: u32,
pub gr_inline_count: u32,
pub granularity: i32,
pub lat_offset: i64,
pub lon_offset: i64,
pub date_granularity: i32,
}
pub(crate) struct WireGroup<'a> {
data: &'a [u8],
}
impl<'a> WireGroup<'a> {
#[inline]
pub fn new(data: &'a [u8]) -> Self {
Self { data }
}
#[inline]
pub fn nodes(&self) -> WireMessageIter<'a> {
WireMessageIter::new(self.data, 1)
}
pub fn dense(&self) -> Result<Option<&'a [u8]>> {
let mut cursor = Cursor::new(self.data);
while let Some((field, wire_type)) = cursor.read_tag()? {
if field == 2 && wire_type == WIRE_LEN {
return Ok(Some(cursor.read_len_delimited()?));
}
cursor.skip_field(wire_type)?;
}
Ok(None)
}
#[inline]
pub fn ways(&self) -> WireMessageIter<'a> {
WireMessageIter::new(self.data, 3)
}
#[inline]
pub fn relations(&self) -> WireMessageIter<'a> {
WireMessageIter::new(self.data, 4)
}
}
pub(crate) struct WireMessageIter<'a> {
cursor: Cursor<'a>,
target_field: u32,
}
impl<'a> WireMessageIter<'a> {
fn new(data: &'a [u8], target_field: u32) -> Self {
Self {
cursor: Cursor::new(data),
target_field,
}
}
pub fn empty() -> Self {
Self {
cursor: Cursor::new(&[]),
target_field: 0,
}
}
}
impl<'a> Iterator for WireMessageIter<'a> {
type Item = &'a [u8];
#[inline]
fn next(&mut self) -> Option<&'a [u8]> {
loop {
let (field, wire_type) = self.cursor.read_tag().ok()??;
if field == self.target_field && wire_type == WIRE_LEN {
return self.cursor.read_len_delimited().ok();
}
if self.cursor.skip_field(wire_type).is_err() {
return None;
}
}
}
}
#[derive(Clone, Debug)]
pub(crate) struct WireNode<'a> {
pub id: i64,
pub lat: i64,
pub lon: i64,
pub keys_data: &'a [u8],
pub vals_data: &'a [u8],
pub info_data: Option<&'a [u8]>,
}
impl<'a> WireNode<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut id: i64 = 0;
let mut lat: i64 = 0;
let mut lon: i64 = 0;
let mut keys_data: &[u8] = &[];
let mut vals_data: &[u8] = &[];
let mut info_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_VARINT) => id = cursor.read_sint64()?,
(2, WIRE_LEN) => keys_data = cursor.read_len_delimited()?,
(3, WIRE_LEN) => vals_data = cursor.read_len_delimited()?,
(4, WIRE_LEN) => info_data = Some(cursor.read_len_delimited()?),
(8, WIRE_VARINT) => lat = cursor.read_sint64()?,
(9, WIRE_VARINT) => lon = cursor.read_sint64()?,
_ => cursor.skip_field(wire_type)?,
}
}
Ok(Self {
id,
lat,
lon,
keys_data,
vals_data,
info_data,
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct WireWay<'a> {
pub id: i64,
pub keys_data: &'a [u8],
pub vals_data: &'a [u8],
pub refs_data: &'a [u8],
pub lat_data: &'a [u8],
pub lon_data: &'a [u8],
pub info_data: Option<&'a [u8]>,
pub pins_data: Option<&'a [u8]>,
}
impl<'a> WireWay<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut id: i64 = 0;
let mut keys_data: &[u8] = &[];
let mut vals_data: &[u8] = &[];
let mut refs_data: &[u8] = &[];
let mut lat_data: &[u8] = &[];
let mut lon_data: &[u8] = &[];
let mut info_data: Option<&[u8]> = None;
let mut pins_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_VARINT) => id = cursor.read_varint_i64()?,
(2, WIRE_LEN) => keys_data = cursor.read_len_delimited()?,
(3, WIRE_LEN) => vals_data = cursor.read_len_delimited()?,
(4, WIRE_LEN) => info_data = Some(cursor.read_len_delimited()?),
(8, WIRE_LEN) => refs_data = cursor.read_len_delimited()?,
(9, WIRE_LEN) => lat_data = cursor.read_len_delimited()?,
(10, WIRE_LEN) => lon_data = cursor.read_len_delimited()?,
(20, WIRE_LEN) => pins_data = Some(cursor.read_len_delimited()?),
_ => cursor.skip_field(wire_type)?,
}
}
Ok(Self {
id,
keys_data,
vals_data,
refs_data,
lat_data,
lon_data,
info_data,
pins_data,
})
}
}
#[derive(Clone, Debug)]
pub(crate) struct WireRelation<'a> {
pub id: i64,
pub keys_data: &'a [u8],
pub vals_data: &'a [u8],
pub roles_sid_data: &'a [u8],
pub memids_data: &'a [u8],
pub types_data: &'a [u8],
pub info_data: Option<&'a [u8]>,
}
impl<'a> WireRelation<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut id: i64 = 0;
let mut keys_data: &[u8] = &[];
let mut vals_data: &[u8] = &[];
let mut roles_sid_data: &[u8] = &[];
let mut memids_data: &[u8] = &[];
let mut types_data: &[u8] = &[];
let mut info_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_VARINT) => id = cursor.read_varint_i64()?,
(2, WIRE_LEN) => keys_data = cursor.read_len_delimited()?,
(3, WIRE_LEN) => vals_data = cursor.read_len_delimited()?,
(4, WIRE_LEN) => info_data = Some(cursor.read_len_delimited()?),
(8, WIRE_LEN) => roles_sid_data = cursor.read_len_delimited()?,
(9, WIRE_LEN) => memids_data = cursor.read_len_delimited()?,
(10, WIRE_LEN) => types_data = cursor.read_len_delimited()?,
_ => cursor.skip_field(wire_type)?,
}
}
Ok(Self {
id,
keys_data,
vals_data,
roles_sid_data,
memids_data,
types_data,
info_data,
})
}
}
#[derive(Clone, Copy)]
pub(crate) struct WireDenseNodes<'a> {
pub id_data: &'a [u8],
pub lat_data: &'a [u8],
pub lon_data: &'a [u8],
pub keys_vals_data: &'a [u8],
pub info_data: Option<&'a [u8]>,
}
impl<'a> WireDenseNodes<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut id_data: &[u8] = &[];
let mut lat_data: &[u8] = &[];
let mut lon_data: &[u8] = &[];
let mut keys_vals_data: &[u8] = &[];
let mut info_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_LEN) => id_data = cursor.read_len_delimited()?,
(5, WIRE_LEN) => info_data = Some(cursor.read_len_delimited()?),
(8, WIRE_LEN) => lat_data = cursor.read_len_delimited()?,
(9, WIRE_LEN) => lon_data = cursor.read_len_delimited()?,
(10, WIRE_LEN) => keys_vals_data = cursor.read_len_delimited()?,
_ => cursor.skip_field(wire_type)?,
}
}
Ok(Self {
id_data,
lat_data,
lon_data,
keys_vals_data,
info_data,
})
}
}
#[derive(Clone, Copy)]
pub(crate) struct WireDenseInfo<'a> {
pub version_data: &'a [u8],
pub timestamp_data: &'a [u8],
pub changeset_data: &'a [u8],
pub uid_data: &'a [u8],
pub user_sid_data: &'a [u8],
pub visible_data: &'a [u8],
}
impl<'a> WireDenseInfo<'a> {
pub fn parse(data: &'a [u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut version_data: &[u8] = &[];
let mut timestamp_data: &[u8] = &[];
let mut changeset_data: &[u8] = &[];
let mut uid_data: &[u8] = &[];
let mut user_sid_data: &[u8] = &[];
let mut visible_data: &[u8] = &[];
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_LEN) => version_data = cursor.read_len_delimited()?,
(2, WIRE_LEN) => timestamp_data = cursor.read_len_delimited()?,
(3, WIRE_LEN) => changeset_data = cursor.read_len_delimited()?,
(4, WIRE_LEN) => uid_data = cursor.read_len_delimited()?,
(5, WIRE_LEN) => user_sid_data = cursor.read_len_delimited()?,
(6, WIRE_LEN) => visible_data = cursor.read_len_delimited()?,
_ => cursor.skip_field(wire_type)?,
}
}
Ok(Self {
version_data,
timestamp_data,
changeset_data,
uid_data,
user_sid_data,
visible_data,
})
}
}
#[derive(Clone, Debug, Default)]
pub(crate) struct WireInfo {
pub version: Option<i32>,
pub timestamp: Option<i64>,
pub changeset: Option<i64>,
pub uid: Option<i32>,
pub user_sid: Option<i32>,
pub visible: Option<bool>,
}
impl WireInfo {
pub fn parse(data: &[u8]) -> Result<Self> {
let mut cursor = Cursor::new(data);
let mut info = Self::default();
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
(1, WIRE_VARINT) => info.version = Some(cursor.read_varint()? as i32),
(2, WIRE_VARINT) => info.timestamp = Some(cursor.read_varint_i64()?),
(3, WIRE_VARINT) => info.changeset = Some(cursor.read_varint_i64()?),
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
(4, WIRE_VARINT) => info.uid = Some(cursor.read_varint()? as i32),
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
(5, WIRE_VARINT) => info.user_sid = Some(cursor.read_varint()? as i32),
(6, WIRE_VARINT) => info.visible = Some(cursor.read_varint()? != 0),
_ => cursor.skip_field(wire_type)?,
}
}
if info.version == Some(-1) {
info.version = None;
}
if info.changeset == Some(-1) {
info.changeset = None;
}
Ok(info)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn wire_info_parse() {
let data = [
0x08, 0x05, 0x20, 0x2A, 0x30, 0x01, ];
let info = WireInfo::parse(&data).unwrap();
assert_eq!(info.version, Some(5));
assert_eq!(info.uid, Some(42));
assert_eq!(info.visible, Some(true));
assert_eq!(info.timestamp, None);
assert_eq!(info.changeset, None);
assert_eq!(info.user_sid, None);
}
#[test]
fn wire_way_parses_field_20_pins() {
let with_pins = [
0x08, 0x01, 0x42, 0x01, 0x00, 0xA2, 0x01, 0x01, 0xFF, ];
let way = WireWay::parse(&with_pins).unwrap();
assert_eq!(way.pins_data, Some([0xFFu8].as_slice()));
let without_pins = [0x08, 0x01, 0x42, 0x01, 0x00];
let way = WireWay::parse(&without_pins).unwrap();
assert_eq!(way.pins_data, None);
}
}