Skip to main content

geonative_shapefile/
bytes.rs

1//! Mixed-endianness byte primitives for the Esri Shapefile format.
2//!
3//! Shapefiles use **big-endian for management fields** (file/record lengths,
4//! record numbers, byte-counts) and **little-endian for data** (shape type
5//! codes, coordinates, counts). The cursor exposes both flavors so callers
6//! never silently mis-read a field.
7//!
8//! Lengths are stored in **16-bit words** (i.e. bytes / 2). Helpers convert.
9
10use crate::error::{Result, ShpError};
11
12#[derive(Debug, Clone)]
13pub struct Cursor<'a> {
14    bytes: &'a [u8],
15    pos: usize,
16}
17
18impl<'a> Cursor<'a> {
19    pub fn new(bytes: &'a [u8]) -> Self {
20        Self { bytes, pos: 0 }
21    }
22
23    pub fn position(&self) -> usize {
24        self.pos
25    }
26
27    pub fn remaining(&self) -> usize {
28        self.bytes.len() - self.pos
29    }
30
31    pub fn seek(&mut self, pos: usize) -> Result<()> {
32        if pos > self.bytes.len() {
33            return Err(ShpError::malformed(format!(
34                "seek past EOF: {pos} > {}",
35                self.bytes.len()
36            )));
37        }
38        self.pos = pos;
39        Ok(())
40    }
41
42    fn need(&self, n: usize) -> Result<()> {
43        if self.remaining() < n {
44            Err(ShpError::malformed(format!(
45                "truncated input at byte {}: need {} more, have {}",
46                self.pos,
47                n,
48                self.remaining()
49            )))
50        } else {
51            Ok(())
52        }
53    }
54
55    pub fn read_bytes(&mut self, n: usize) -> Result<&'a [u8]> {
56        self.need(n)?;
57        let s = &self.bytes[self.pos..self.pos + n];
58        self.pos += n;
59        Ok(s)
60    }
61
62    pub fn read_i32_be(&mut self) -> Result<i32> {
63        let s = self.read_bytes(4)?;
64        Ok(i32::from_be_bytes(s.try_into().unwrap()))
65    }
66
67    pub fn read_i32_le(&mut self) -> Result<i32> {
68        let s = self.read_bytes(4)?;
69        Ok(i32::from_le_bytes(s.try_into().unwrap()))
70    }
71
72    pub fn read_f64_le(&mut self) -> Result<f64> {
73        let s = self.read_bytes(8)?;
74        Ok(f64::from_le_bytes(s.try_into().unwrap()))
75    }
76}
77
78/// Convert a Shapefile 16-bit-word count to bytes.
79#[inline]
80pub fn words_to_bytes(words: i32) -> usize {
81    (words as i64 * 2) as usize
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn reads_mixed_endianness() {
90        // 0x00000001 (BE int32 = 1) + 0x01000000 (LE int32 = 1)
91        let mut c = Cursor::new(&[0, 0, 0, 1, 1, 0, 0, 0]);
92        assert_eq!(c.read_i32_be().unwrap(), 1);
93        assert_eq!(c.read_i32_le().unwrap(), 1);
94    }
95
96    #[test]
97    fn truncation_errors() {
98        let mut c = Cursor::new(&[1, 2, 3]);
99        assert!(c.read_i32_be().is_err());
100    }
101
102    #[test]
103    fn words_conversion() {
104        assert_eq!(words_to_bytes(50), 100);
105    }
106}