179 lines
5.7 KiB
Rust
179 lines
5.7 KiB
Rust
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<Self, Error> {
|
|
// 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<u8> {
|
|
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);
|
|
}
|
|
}
|