use crate::error::Error; /// Location represents a physical position in a database file /// /// It consists of a file number and a position within that file. /// This allows OurDB to span multiple files for large datasets. #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct Location { /// File number (0-65535) pub file_nr: u16, /// Position within the file pub position: u32, } impl Location { /// Creates a new Location from bytes based on keysize /// /// - keysize = 2: Only position (2 bytes), file_nr = 0 /// - keysize = 3: Only position (3 bytes), file_nr = 0 /// - keysize = 4: Only position (4 bytes), file_nr = 0 /// - keysize = 6: file_nr (2 bytes) + position (4 bytes) pub fn from_bytes(bytes: &[u8], keysize: u8) -> Result { // Validate keysize if ![2, 3, 4, 6].contains(&keysize) { return Err(Error::InvalidOperation(format!( "Invalid keysize: {}", keysize ))); } // Create padded bytes let mut padded = vec![0u8; keysize as usize]; if bytes.len() > keysize as usize { return Err(Error::InvalidOperation( "Input bytes exceed keysize".to_string(), )); } let start_idx = keysize as usize - bytes.len(); for (i, &b) in bytes.iter().enumerate() { if i + start_idx < padded.len() { padded[start_idx + i] = b; } } let mut location = Location::default(); match keysize { 2 => { // Only position, 2 bytes big endian location.position = u32::from(padded[0]) << 8 | u32::from(padded[1]); location.file_nr = 0; // Verify limits if location.position > 0xFFFF { return Err(Error::InvalidOperation( "Position exceeds max value for keysize=2 (max 65535)".to_string(), )); } } 3 => { // Only position, 3 bytes big endian location.position = u32::from(padded[0]) << 16 | u32::from(padded[1]) << 8 | u32::from(padded[2]); location.file_nr = 0; // Verify limits if location.position > 0xFFFFFF { return Err(Error::InvalidOperation( "Position exceeds max value for keysize=3 (max 16777215)".to_string(), )); } } 4 => { // Only position, 4 bytes big endian location.position = u32::from(padded[0]) << 24 | u32::from(padded[1]) << 16 | u32::from(padded[2]) << 8 | u32::from(padded[3]); location.file_nr = 0; } 6 => { // 2 bytes file_nr + 4 bytes position, all big endian location.file_nr = u16::from(padded[0]) << 8 | u16::from(padded[1]); location.position = u32::from(padded[2]) << 24 | u32::from(padded[3]) << 16 | u32::from(padded[4]) << 8 | u32::from(padded[5]); } _ => unreachable!(), } Ok(location) } /// Converts the location to bytes (always 6 bytes) /// /// Format: [file_nr (2 bytes)][position (4 bytes)] pub fn to_bytes(&self) -> Vec { let mut bytes = Vec::with_capacity(6); // Put file_nr first (2 bytes) bytes.push((self.file_nr >> 8) as u8); bytes.push(self.file_nr as u8); // Put position next (4 bytes) bytes.push((self.position >> 24) as u8); bytes.push((self.position >> 16) as u8); bytes.push((self.position >> 8) as u8); bytes.push(self.position as u8); bytes } /// Converts the location to a u64 value /// /// The file_nr is stored in the most significant bits pub fn to_u64(&self) -> u64 { (u64::from(self.file_nr) << 32) | u64::from(self.position) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_location_from_bytes_keysize_2() { let bytes = vec![0x12, 0x34]; let location = Location::from_bytes(&bytes, 2).unwrap(); assert_eq!(location.file_nr, 0); assert_eq!(location.position, 0x1234); } #[test] fn test_location_from_bytes_keysize_3() { let bytes = vec![0x12, 0x34, 0x56]; let location = Location::from_bytes(&bytes, 3).unwrap(); assert_eq!(location.file_nr, 0); assert_eq!(location.position, 0x123456); } #[test] fn test_location_from_bytes_keysize_4() { let bytes = vec![0x12, 0x34, 0x56, 0x78]; let location = Location::from_bytes(&bytes, 4).unwrap(); assert_eq!(location.file_nr, 0); assert_eq!(location.position, 0x12345678); } #[test] fn test_location_from_bytes_keysize_6() { let bytes = vec![0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78]; let location = Location::from_bytes(&bytes, 6).unwrap(); assert_eq!(location.file_nr, 0xABCD); assert_eq!(location.position, 0x12345678); } #[test] fn test_location_to_bytes() { let location = Location { file_nr: 0xABCD, position: 0x12345678, }; let bytes = location.to_bytes(); assert_eq!(bytes, vec![0xAB, 0xCD, 0x12, 0x34, 0x56, 0x78]); } #[test] fn test_location_to_u64() { let location = Location { file_nr: 0xABCD, position: 0x12345678, }; let value = location.to_u64(); assert_eq!(value, 0xABCD_0000_0000 | 0x12345678); } }