pub(crate) fn vbyte(out: &mut Vec<u8>, mut value: u64) {
while value > 127 {
out.push((value & 127) as u8);
value >>= 7;
}
out.push((value | 0x80) as u8);
}
pub(crate) fn crc8(data: &[u8]) -> u8 {
let mut crc: u8 = 0;
for &b in data {
crc ^= b;
for _ in 0..8 {
crc = if crc & 0x80 != 0 {
(crc << 1) ^ 0x07
} else {
crc << 1
};
}
}
crc
}
pub(crate) fn crc16(data: &[u8]) -> u16 {
let mut crc: u16 = 0;
for &b in data {
crc ^= b as u16;
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ 0xA001
} else {
crc >> 1
};
}
}
crc
}
pub(crate) fn crc32c(data: &[u8]) -> u32 {
let mut crc: u32 = 0xFFFF_FFFF;
for &b in data {
crc ^= b as u32;
for _ in 0..8 {
crc = if crc & 1 != 0 {
(crc >> 1) ^ 0x82F6_3B78
} else {
crc >> 1
};
}
}
!crc
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum CiType {
Global = 1,
Header = 2,
Dictionary = 3,
Triples = 4,
}
pub(crate) fn control_info(
out: &mut Vec<u8>,
kind: CiType,
format: &str,
properties: &[(&str, String)],
) {
let start = out.len();
out.extend_from_slice(b"$HDT");
out.push(kind as u8);
out.extend_from_slice(format.as_bytes());
out.push(0);
for (k, v) in properties {
out.extend_from_slice(k.as_bytes());
out.push(b'=');
out.extend_from_slice(v.as_bytes());
out.push(b';');
}
out.push(0);
let crc = crc16(&out[start..]);
out.extend_from_slice(&crc.to_le_bytes());
}
pub(crate) const GLOBAL_FORMAT: &str = "<http://purl.org/HDT/hdt#HDTv1>";
pub(crate) const HEADER_FORMAT: &str = "ntriples";
pub(crate) const DICTIONARY_FORMAT: &str = "<http://purl.org/HDT/hdt#dictionaryFour>";
pub(crate) const TRIPLES_FORMAT: &str = "<http://purl.org/HDT/hdt#triplesBitmap>";
pub(crate) const ORDER_SPO: u32 = 1;
pub(crate) fn bits_for(n: u64) -> u8 {
(64 - n.leading_zeros()) as u8
}
pub(crate) struct LogSequence {
numbits: u8,
numentries: u64,
data: Vec<u8>,
}
impl LogSequence {
#[cfg(test)]
pub(crate) fn new(values: &[u64]) -> Self {
let max = values.iter().copied().max().unwrap_or(0);
Self::with_bits(values, bits_for(max))
}
pub(crate) fn with_bits(values: &[u64], numbits: u8) -> Self {
let numentries = values.len() as u64;
let total_bits = numbits as u64 * numentries;
let numbytes = total_bits.div_ceil(8) as usize;
let mut data = vec![0u8; numbytes];
if numbits > 0 {
for (i, &v) in values.iter().enumerate() {
let start = i as u64 * numbits as u64;
for b in 0..numbits as u64 {
if (v >> b) & 1 == 1 {
let pos = start + b;
data[(pos / 8) as usize] |= 1 << (pos % 8);
}
}
}
}
Self {
numbits,
numentries,
data,
}
}
pub(crate) fn write(&self, out: &mut Vec<u8>) {
let mut head = Vec::with_capacity(11);
head.push(0x01u8); head.push(self.numbits);
vbyte(&mut head, self.numentries);
out.extend_from_slice(&head);
out.push(crc8(&head));
out.extend_from_slice(&self.data);
out.extend_from_slice(&crc32c(&self.data).to_le_bytes());
}
}
#[derive(Default)]
pub(crate) struct BitSequence {
numbits: u64,
data: Vec<u8>,
}
impl BitSequence {
pub(crate) fn with_capacity(bits: usize) -> Self {
Self {
numbits: 0,
data: Vec::with_capacity(bits.div_ceil(8)),
}
}
pub(crate) fn push(&mut self, bit: bool) {
let idx = (self.numbits / 8) as usize;
if idx >= self.data.len() {
self.data.push(0);
}
if bit {
self.data[idx] |= 1 << (self.numbits % 8);
}
self.numbits += 1;
}
#[cfg(test)]
pub(crate) fn len(&self) -> u64 {
self.numbits
}
#[cfg(test)]
pub(crate) fn get(&self, i: u64) -> bool {
self.data[(i / 8) as usize] >> (i % 8) & 1 == 1
}
pub(crate) fn write(&self, out: &mut Vec<u8>) {
let mut head = Vec::with_capacity(11);
head.push(0x01u8); vbyte(&mut head, self.numbits);
out.extend_from_slice(&head);
out.push(crc8(&head));
if self.data.is_empty() {
let z = [0u8];
out.extend_from_slice(&z);
out.extend_from_slice(&crc32c(&z).to_le_bytes());
} else {
out.extend_from_slice(&self.data);
out.extend_from_slice(&crc32c(&self.data).to_le_bytes());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vbyte_matches_the_reference_file() {
let cases: &[(u64, &[u8])] = &[
(0, &[0x80]),
(5, &[0x85]),
(16, &[0x90]),
(127, &[0xFF]),
(128, &[0x00, 0x81]),
(300, &[0x2C, 0x82]),
(1_812_715, &[0x6B, 0x51, 0xEE]),
(19_936_656, &[0x10, 0x6B, 0x40, 0x89]),
(88_150_324, &[0x34, 0x22, 0x04, 0xAA]),
];
for (value, want) in cases {
let mut got = Vec::new();
vbyte(&mut got, *value);
assert_eq!(&got[..], *want, "vbyte({value})");
}
}
#[test]
fn vbyte_marks_the_last_byte_not_the_continuations() {
let mut got = Vec::new();
vbyte(&mut got, 1_812_715);
assert!(
got[..got.len() - 1].iter().all(|b| b & 0x80 == 0),
"every byte but the last must have the high bit CLEAR: {got:02x?}"
);
assert!(
got[got.len() - 1] & 0x80 != 0,
"the last byte must have the high bit SET: {got:02x?}"
);
}
#[test]
fn crc8_is_smbus() {
assert_eq!(crc8(b"123456789"), 0xF4);
assert_eq!(crc8(&[]), 0x00);
}
#[test]
fn crc16_is_arc() {
assert_eq!(crc16(b"123456789"), 0xBB3D);
assert_eq!(crc16(&[]), 0x0000);
}
#[test]
fn crc32_is_castagnoli_not_zlib() {
assert_eq!(crc32c(b"123456789"), 0xE306_9283);
assert_ne!(crc32c(b"123456789"), 0xCBF4_3926);
}
#[test]
fn global_control_info_matches_the_reference_file() {
let mut out = Vec::new();
control_info(&mut out, CiType::Global, GLOBAL_FORMAT, &[]);
let mut want = Vec::new();
want.extend_from_slice(b"$HDT");
want.push(1);
want.extend_from_slice(GLOBAL_FORMAT.as_bytes());
want.push(0);
want.push(0);
want.extend_from_slice(&0x3576u16.to_le_bytes());
assert_eq!(out, want, "got {:02x?}", out);
assert_eq!(out.len(), 0x28, "the header CI starts at 0x28");
}
#[test]
fn triples_control_info_matches_the_reference_file() {
let mut out = Vec::new();
control_info(
&mut out,
CiType::Triples,
TRIPLES_FORMAT,
&[("order", ORDER_SPO.to_string())],
);
assert!(out.ends_with(&0xe959u16.to_le_bytes()), "got {:02x?}", out);
let text = String::from_utf8_lossy(&out);
assert!(text.contains("order=1;"));
assert!(!text.contains("numTriples"));
}
#[test]
fn bits_for_counts_the_high_bit() {
assert_eq!(bits_for(0), 0);
assert_eq!(bits_for(1), 1);
assert_eq!(bits_for(16), 5);
assert_eq!(bits_for(19_936_656), 25); assert_eq!(bits_for(u32::MAX as u64), 32);
}
#[test]
fn log_sequence_packs_lsb_first() {
let seq = LogSequence::new(&[1, 2, 3]);
assert_eq!(seq.numbits, 2, "max is 3, which needs 2 bits");
let seq = LogSequence::with_bits(&[1, 2, 3], 5);
assert_eq!(seq.data.len(), (15u64).div_ceil(8) as usize);
let get = |i: usize| -> u64 {
let mut v = 0u64;
for b in 0..5u64 {
let pos = i as u64 * 5 + b;
if seq.data[(pos / 8) as usize] >> (pos % 8) & 1 == 1 {
v |= 1 << b;
}
}
v
};
assert_eq!((get(0), get(1), get(2)), (1, 2, 3));
}
#[test]
fn log_sequence_of_nothing_is_well_formed() {
let seq = LogSequence::new(&[]);
let mut out = Vec::new();
seq.write(&mut out);
assert_eq!(out[0], 0x01);
assert_eq!(out[1], 0x00);
assert_eq!(out[2], 0x80);
assert_eq!(out.len(), 3 + 1 + 4);
}
#[test]
fn bit_sequence_is_lsb_first_and_never_empty_on_disk() {
let mut b = BitSequence::default();
for bit in [true, false, false, true] {
b.push(bit);
}
assert_eq!(b.len(), 4);
assert_eq!(b.data[0], 0b1001);
let empty = BitSequence::default();
let mut out = Vec::new();
empty.write(&mut out);
assert_eq!(out.len(), 8, "got {:02x?}", out);
}
#[test]
fn bit_sequence_spans_byte_boundaries() {
let mut b = BitSequence::default();
for i in 0..20 {
b.push(i % 3 == 0);
}
assert_eq!(b.len(), 20);
assert_eq!(b.data.len(), 3);
for i in 0..20usize {
let got = b.data[i / 8] >> (i % 8) & 1 == 1;
assert_eq!(got, i % 3 == 0, "bit {i}");
}
}
}