#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ByteOrder {
LittleEndian,
BigEndian,
}
#[derive(Debug, Clone, PartialEq)]
pub enum WkbError {
UnexpectedEof,
InvalidByteOrder(u8),
UnknownGeometryType(u32),
UnsupportedDimension,
TrailingBytes,
NestingTooDeep,
MismatchedMemberType {
expected: u32,
found: u32,
},
}
impl core::fmt::Display for WkbError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
WkbError::UnexpectedEof => f.write_str("unexpected end of WKB input"),
WkbError::InvalidByteOrder(b) => {
write!(
f,
"invalid byte-order flag {b:#04x} (expected 0x00 or 0x01)"
)
}
WkbError::UnknownGeometryType(t) => write!(f, "unknown WKB geometry type {t}"),
WkbError::UnsupportedDimension => {
f.write_str("unsupported WKB dimension (Z/M ordinates); this reader is 2D only")
}
WkbError::TrailingBytes => f.write_str("trailing bytes after WKB geometry"),
WkbError::NestingTooDeep => {
f.write_str("WKB nesting too deep; exceeded the reader's recursion limit")
}
WkbError::MismatchedMemberType { expected, found } => write!(
f,
"WKB multi-geometry member has type {found}, expected {expected}"
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for WkbError {}
pub(crate) struct Cursor<'a> {
bytes: &'a [u8],
pos: usize,
}
impl<'a> Cursor<'a> {
pub(crate) fn new(bytes: &'a [u8]) -> Self {
Self { bytes, pos: 0 }
}
pub(crate) fn is_empty(&self) -> bool {
self.pos >= self.bytes.len()
}
pub(crate) fn remaining(&self) -> usize {
self.bytes.len().saturating_sub(self.pos)
}
pub(crate) fn read_slice(&mut self, len: usize) -> Result<&'a [u8], WkbError> {
let end = self.pos.checked_add(len).ok_or(WkbError::UnexpectedEof)?;
let slice = self
.bytes
.get(self.pos..end)
.ok_or(WkbError::UnexpectedEof)?;
self.pos = end;
Ok(slice)
}
fn read_array<const N: usize>(&mut self) -> Result<[u8; N], WkbError> {
let slice = self.read_slice(N)?;
let mut buf = [0u8; N];
buf.copy_from_slice(slice);
Ok(buf)
}
pub(crate) fn read_u8(&mut self) -> Result<u8, WkbError> {
Ok(self.read_array::<1>()?[0])
}
pub(crate) fn read_u32(&mut self, order: ByteOrder) -> Result<u32, WkbError> {
let b = self.read_array::<4>()?;
Ok(match order {
ByteOrder::LittleEndian => u32::from_le_bytes(b),
ByteOrder::BigEndian => u32::from_be_bytes(b),
})
}
pub(crate) fn read_f64(&mut self, order: ByteOrder) -> Result<f64, WkbError> {
let b = self.read_array::<8>()?;
Ok(match order {
ByteOrder::LittleEndian => f64::from_le_bytes(b),
ByteOrder::BigEndian => f64::from_be_bytes(b),
})
}
pub(crate) fn read_byte_order(&mut self) -> Result<ByteOrder, WkbError> {
match self.read_u8()? {
0x00 => Ok(ByteOrder::BigEndian),
0x01 => Ok(ByteOrder::LittleEndian),
other => Err(WkbError::InvalidByteOrder(other)),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reads_le_u32() {
let mut c = Cursor::new(&[0x01, 0x00, 0x00, 0x00]);
assert_eq!(c.read_u32(ByteOrder::LittleEndian).unwrap(), 1);
}
#[test]
fn reads_be_u32() {
let mut c = Cursor::new(&[0x00, 0x00, 0x00, 0x01]);
assert_eq!(c.read_u32(ByteOrder::BigEndian).unwrap(), 1);
}
#[test]
fn reads_byte_order_flags() {
assert_eq!(
Cursor::new(&[0x00]).read_byte_order().unwrap(),
ByteOrder::BigEndian
);
assert_eq!(
Cursor::new(&[0x01]).read_byte_order().unwrap(),
ByteOrder::LittleEndian
);
}
#[test]
fn invalid_byte_order_is_rejected() {
let err = Cursor::new(&[0x02]).read_byte_order().unwrap_err();
assert_eq!(err, WkbError::InvalidByteOrder(0x02));
}
#[test]
fn short_buffer_is_eof() {
let err = Cursor::new(&[0x00, 0x00])
.read_u32(ByteOrder::LittleEndian)
.unwrap_err();
assert_eq!(err, WkbError::UnexpectedEof);
}
#[test]
fn every_error_variant_displays_descriptively() {
extern crate alloc;
use alloc::format;
assert_eq!(
format!("{}", WkbError::UnexpectedEof),
"unexpected end of WKB input"
);
assert_eq!(
format!("{}", WkbError::InvalidByteOrder(0x02)),
"invalid byte-order flag 0x02 (expected 0x00 or 0x01)"
);
assert_eq!(
format!("{}", WkbError::UnknownGeometryType(9)),
"unknown WKB geometry type 9"
);
assert!(
format!("{}", WkbError::UnsupportedDimension).contains("2D only"),
"dimension message"
);
assert_eq!(
format!("{}", WkbError::TrailingBytes),
"trailing bytes after WKB geometry"
);
assert!(
format!("{}", WkbError::NestingTooDeep).contains("nesting too deep"),
"nesting message"
);
assert_eq!(
format!(
"{}",
WkbError::MismatchedMemberType {
expected: 1,
found: 2
}
),
"WKB multi-geometry member has type 2, expected 1"
);
}
}