use crate::jenkins3::hashlittle2;
use crate::{Error, Result};
pub fn jenkins3_hashpath(path: &str) -> u64 {
let normalised = path.to_ascii_uppercase().replace('/', "\\");
let mut pc = 0;
let mut pb = 0;
hashlittle2(normalised.as_bytes(), &mut pc, &mut pb);
(u64::from(pc) << 32) | u64::from(pb)
}
pub fn read_uint40(data: &[u8]) -> Result<u64> {
if data.len() < 5 {
return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("Need 5 bytes for uint40, got {}", data.len()),
)));
}
Ok((data[0] as u64)
| ((data[1] as u64) << 8)
| ((data[2] as u64) << 16)
| ((data[3] as u64) << 24)
| ((data[4] as u64) << 32))
}
pub fn write_uint40(value: u64) -> [u8; 5] {
assert!(
value < (1u64 << 40),
"Value {value:#x} exceeds 40-bit range"
);
[
(value & 0xFF) as u8,
((value >> 8) & 0xFF) as u8,
((value >> 16) & 0xFF) as u8,
((value >> 24) & 0xFF) as u8,
((value >> 32) & 0xFF) as u8,
]
}
pub fn read_uint40_from<R: std::io::Read>(reader: &mut R) -> Result<u64> {
let mut buf = [0u8; 5];
reader.read_exact(&mut buf)?;
read_uint40(&buf)
}
pub fn read_varint(data: &[u8]) -> Result<(u32, usize)> {
let mut result = 0u32;
let mut shift = 0;
let mut consumed = 0;
for &byte in data {
consumed += 1;
let value = (byte & 0x7F) as u32;
if shift >= 32 || (shift == 28 && value > 0x0F) {
return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Varint exceeds 32-bit range",
)));
}
result |= value << shift;
if byte & 0x80 == 0 {
return Ok((result, consumed));
}
shift += 7;
if consumed >= 5 {
return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"Varint exceeds maximum length",
)));
}
}
Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
"Incomplete varint",
)))
}
pub fn write_varint(mut value: u32) -> Vec<u8> {
let mut result = Vec::new();
loop {
let mut byte = (value & 0x7F) as u8;
value >>= 7;
if value != 0 {
byte |= 0x80; result.push(byte);
} else {
result.push(byte);
break;
}
}
result
}
pub fn read_cstring(data: &[u8]) -> Result<(String, usize)> {
let null_pos = data.iter().position(|&b| b == 0).ok_or_else(|| {
Error::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
"No null terminator found in C string",
))
})?;
let string = std::str::from_utf8(&data[..null_pos])
.map_err(|e| {
Error::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid UTF-8 in C string: {e}"),
))
})?
.to_string();
Ok((string, null_pos + 1)) }
pub fn read_uint40_be(data: &[u8]) -> Result<u64> {
if data.len() < 5 {
return Err(Error::IOError(std::io::Error::new(
std::io::ErrorKind::UnexpectedEof,
format!("Need 5 bytes for uint40, got {}", data.len()),
)));
}
let high_byte = data[0] as u64;
let low_u32 = u32::from_be_bytes([data[1], data[2], data[3], data[4]]) as u64;
Ok((high_byte << 32) | low_u32)
}
pub fn write_uint40_be(value: u64) -> [u8; 5] {
assert!(
value < (1u64 << 40),
"Value {value:#x} exceeds 40-bit range"
);
let high_byte = ((value >> 32) & 0xFF) as u8;
let low_u32 = (value & 0xFFFFFFFF) as u32;
let low_bytes = low_u32.to_be_bytes();
[
high_byte,
low_bytes[0],
low_bytes[1],
low_bytes[2],
low_bytes[3],
]
}
pub fn read_uint40_be_from<R: std::io::Read>(reader: &mut R) -> Result<u64> {
let mut buf = [0u8; 5];
reader.read_exact(&mut buf)?;
read_uint40_be(&buf)
}
pub fn read_cstring_from<R: std::io::Read>(reader: &mut R) -> Result<String> {
let mut bytes = Vec::new();
let mut byte = [0u8; 1];
loop {
reader.read_exact(&mut byte)?;
if byte[0] == 0 {
break;
}
bytes.push(byte[0]);
}
String::from_utf8(bytes).map_err(|e| {
Error::IOError(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Invalid UTF-8 in C string: {e}"),
))
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_uint40_roundtrip() {
let test_values = [
0u64,
1,
255,
256,
65535,
65536,
0xFFFFFFFF,
0x123456789A,
0xFFFFFFFFFF, ];
for value in test_values {
let bytes = write_uint40(value);
let decoded = read_uint40(&bytes).unwrap();
assert_eq!(value, decoded, "Failed for value {value:#x}");
}
}
#[test]
fn test_uint40_little_endian() {
let data = [0x12, 0x34, 0x56, 0x78, 0x9A];
let value = read_uint40(&data).unwrap();
assert_eq!(value, 0x9A78563412);
let bytes = write_uint40(0x9A78563412);
assert_eq!(bytes, [0x12, 0x34, 0x56, 0x78, 0x9A]);
}
#[test]
#[should_panic(expected = "exceeds 40-bit range")]
fn test_uint40_overflow() {
write_uint40(0x10000000000); }
#[test]
fn test_uint40_insufficient_data() {
let data = [0x12, 0x34, 0x56, 0x78]; assert!(read_uint40(&data).is_err());
}
#[test]
fn test_varint_single_byte() {
let data = [0x08];
let (value, consumed) = read_varint(&data).unwrap();
assert_eq!(value, 8);
assert_eq!(consumed, 1);
let encoded = write_varint(8);
assert_eq!(encoded, vec![0x08]);
}
#[test]
fn test_varint_multi_byte() {
let data = [0x96, 0x01]; let (value, consumed) = read_varint(&data).unwrap();
assert_eq!(value, 150);
assert_eq!(consumed, 2);
let encoded = write_varint(150);
assert_eq!(encoded, vec![0x96, 0x01]);
}
#[test]
fn test_varint_max_value() {
let value = 0xFFFFFFFF;
let encoded = write_varint(value);
let (decoded, _) = read_varint(&encoded).unwrap();
assert_eq!(decoded, value);
}
#[test]
fn test_varint_known_values() {
let test_cases = [
(0, vec![0x00]),
(1, vec![0x01]),
(127, vec![0x7F]),
(128, vec![0x80, 0x01]),
(300, vec![0xAC, 0x02]),
(16384, vec![0x80, 0x80, 0x01]),
];
for (value, expected) in test_cases {
let encoded = write_varint(value);
assert_eq!(encoded, expected, "Encoding failed for {value}");
let (decoded, consumed) = read_varint(&expected).unwrap();
assert_eq!(decoded, value, "Decoding failed for {value}");
assert_eq!(consumed, expected.len());
}
}
#[test]
fn test_cstring() {
let data = b"Hello, World!\0extra data";
let (string, consumed) = read_cstring(data).unwrap();
assert_eq!(string, "Hello, World!");
assert_eq!(consumed, 14); }
#[test]
fn test_cstring_empty() {
let data = b"\0";
let (string, consumed) = read_cstring(data).unwrap();
assert_eq!(string, "");
assert_eq!(consumed, 1);
}
#[test]
fn test_cstring_no_terminator() {
let data = b"No null here";
assert!(read_cstring(data).is_err());
}
#[test]
fn test_uint40_big_endian() {
let data = [0x01, 0x00, 0x00, 0x00, 0x00];
let value = read_uint40_be(&data).unwrap();
assert_eq!(value, 0x100000000);
let data = [0x0A, 0x12, 0x34, 0x56, 0x78];
let value = read_uint40_be(&data).unwrap();
assert_eq!(value, 0x0A12345678);
let original = 0x0A12345678u64;
let bytes = write_uint40_be(original);
let restored = read_uint40_be(&bytes).unwrap();
assert_eq!(original, restored);
}
#[test]
fn test_uint40_be_from_reader() {
use std::io::Cursor;
let data = [0x01, 0x00, 0x00, 0x00, 0x00]; let mut cursor = Cursor::new(&data);
let value = read_uint40_be_from(&mut cursor).unwrap();
assert_eq!(value, 0x100000000);
}
}