use arrow_schema::DataType;
use crate::error::{Invariant, Result, Violation};
pub const VIEW_WIDTH: usize = 16;
pub const VIEW_INLINE: u32 = 12;
pub fn check_dictionary(
keys: &[u8],
key_type: &DataType,
len: u64,
dictionary_len: u64,
path: &str,
) -> Result<()> {
let (width, signed) = match key_type {
DataType::Int8 => (1usize, true),
DataType::Int16 => (2, true),
DataType::Int32 => (4, true),
DataType::Int64 => (8, true),
DataType::UInt8 => (1, false),
DataType::UInt16 => (2, false),
DataType::UInt32 => (4, false),
DataType::UInt64 => (8, false),
other => {
return Err(Violation::at(
Invariant::Unsupported,
path,
format!("{other} is not an integer, so it cannot be a dictionary key"),
));
}
};
let slots = usize::try_from(len).map_err(|_| {
Violation::at(
Invariant::Size,
path,
format!("{len} keys is more than this host can address"),
)
})?;
let needed = slots.checked_mul(width).ok_or_else(|| {
Violation::at(
Invariant::Size,
path,
format!("{len} keys of {width} bytes is more than this host can address"),
)
})?;
if keys.len() < needed {
return Err(Violation::at(
Invariant::BufferLength,
path,
format!(
"{len} keys need {needed} bytes and there are {}",
keys.len()
),
));
}
for slot in 0..slots {
let at = slot * width;
let raw = &keys[at..at + width];
let key = if signed {
let value = read_signed(raw);
if value < 0 {
return Err(Violation::at(
Invariant::DictionaryIndex,
path,
format!("key {slot} is {value}, and a key is a position"),
));
}
u64::try_from(value).unwrap_or(u64::MAX)
} else {
read_unsigned(raw)
};
if key >= dictionary_len {
return Err(Violation::at(
Invariant::DictionaryIndex,
path,
format!("key {slot} is {key} and the dictionary has {dictionary_len} values"),
));
}
}
Ok(())
}
pub fn check_views<B: AsRef<[u8]>>(views: &[u8], data: &[B], len: u64, path: &str) -> Result<()> {
let slots = usize::try_from(len).map_err(|_| {
Violation::at(
Invariant::Size,
path,
format!("{len} views is more than this host can address"),
)
})?;
let needed = slots.checked_mul(VIEW_WIDTH).ok_or_else(|| {
Violation::at(
Invariant::Size,
path,
format!("{len} views of {VIEW_WIDTH} bytes is more than this host can address"),
)
})?;
if views.len() < needed {
return Err(Violation::at(
Invariant::BufferLength,
path,
format!(
"{len} views need {needed} bytes and there are {}",
views.len()
),
));
}
for slot in 0..slots {
let at = slot * VIEW_WIDTH;
let view = &views[at..at + VIEW_WIDTH];
let length = read_u32(&view[0..4]);
if length <= VIEW_INLINE {
continue;
}
let index = read_u32(&view[8..12]);
let offset = read_u32(&view[12..16]);
let buffer = usize::try_from(index)
.ok()
.and_then(|index| data.get(index))
.ok_or_else(|| {
Violation::at(
Invariant::ViewBuffer,
path,
format!(
"view {slot} points at data buffer {index} and there are {}",
data.len()
),
)
})?;
let end = u64::from(offset) + u64::from(length);
let have = u64::try_from(buffer.as_ref().len()).unwrap_or(u64::MAX);
if end > have {
return Err(Violation::at(
Invariant::ViewBuffer,
path,
format!(
"view {slot} reads {length} bytes at {offset} of data buffer {index}, which is \
{have} bytes"
),
));
}
}
Ok(())
}
fn read_signed(raw: &[u8]) -> i64 {
let mut wide = [0u8; 8];
wide[..raw.len()].copy_from_slice(raw);
let value = i64::from_le_bytes(wide);
let spare = 64 - u32::try_from(raw.len()).unwrap_or(8) * 8;
(value << spare) >> spare
}
fn read_unsigned(raw: &[u8]) -> u64 {
let mut wide = [0u8; 8];
wide[..raw.len()].copy_from_slice(raw);
u64::from_le_bytes(wide)
}
fn read_u32(raw: &[u8]) -> u32 {
let mut wide = [0u8; 4];
wide.copy_from_slice(raw);
u32::from_le_bytes(wide)
}
#[cfg(test)]
mod tests {
use arrow_schema::DataType;
use super::{check_dictionary, check_views};
use crate::error::Invariant;
fn view(length: u32, index: u32, offset: u32) -> Vec<u8> {
let mut out = Vec::with_capacity(16);
out.extend_from_slice(&length.to_le_bytes());
out.extend_from_slice(&[0u8; 4]);
out.extend_from_slice(&index.to_le_bytes());
out.extend_from_slice(&offset.to_le_bytes());
out
}
#[test]
fn keys_inside_the_dictionary_pass() {
let keys: Vec<u8> = [0i32, 1, 2].iter().flat_map(|k| k.to_le_bytes()).collect();
check_dictionary(&keys, &DataType::Int32, 3, 3, "d").expect("every key is a slot");
}
#[test]
fn a_key_equal_to_the_dictionary_length_is_caught() {
let keys: Vec<u8> = [0i32, 3].iter().flat_map(|k| k.to_le_bytes()).collect();
let err = check_dictionary(&keys, &DataType::Int32, 2, 3, "d")
.expect_err("three is one past the last slot");
assert_eq!(err.invariant, Invariant::DictionaryIndex);
assert!(err.to_string().contains("has 3 values"), "{err}");
}
#[test]
fn a_negative_key_is_caught_rather_than_read_as_enormous() {
let keys: Vec<u8> = (-1i32).to_le_bytes().to_vec();
let err =
check_dictionary(&keys, &DataType::Int32, 1, 3, "d").expect_err("a key is a position");
assert_eq!(err.invariant, Invariant::DictionaryIndex);
assert!(err.to_string().contains("is -1"), "{err}");
}
#[test]
fn an_unsigned_key_type_is_read_as_unsigned() {
let keys: Vec<u8> = 200u8.to_le_bytes().to_vec();
check_dictionary(&keys, &DataType::UInt8, 1, 255, "d").expect("200 is a slot in 255");
let err = check_dictionary(&keys, &DataType::UInt8, 1, 200, "d")
.expect_err("200 is not a slot in 200");
assert_eq!(err.invariant, Invariant::DictionaryIndex);
}
#[test]
fn a_key_type_that_is_not_an_integer_is_refused_by_name() {
let err = check_dictionary(&[], &DataType::Utf8, 0, 0, "d")
.expect_err("a string is not a key type");
assert_eq!(err.invariant, Invariant::Unsupported);
}
#[test]
fn views_inside_their_buffers_pass() {
let data: Vec<Vec<u8>> = vec![b"hello there friend".to_vec()];
let views = [view(18, 0, 0), view(4, 0, 3)].concat();
check_views(&views, &data, 2, "v").expect("both views are inside the buffer");
}
#[test]
fn a_view_buffer_index_equal_to_the_buffer_count_is_caught() {
let data: Vec<Vec<u8>> = vec![b"hello there friend".to_vec()];
let views = view(18, 1, 0);
let err = check_views(&views, &data, 1, "v").expect_err("there is no buffer 1");
assert_eq!(err.invariant, Invariant::ViewBuffer);
assert!(err.to_string().contains("there are 1"), "{err}");
}
#[test]
fn a_view_that_runs_off_the_end_of_its_buffer_is_caught() {
let data: Vec<Vec<u8>> = vec![b"hello there friend".to_vec()];
let views = view(18, 0, 1);
let err = check_views(&views, &data, 1, "v").expect_err("that is one byte too many");
assert_eq!(err.invariant, Invariant::ViewBuffer);
}
#[test]
fn a_short_value_lives_in_the_view_and_points_at_nothing() {
let data: Vec<Vec<u8>> = Vec::new();
let views = view(12, 99, 99);
check_views(&views, &data, 1, "v").expect("an inline value indexes nothing");
}
#[test]
fn a_views_buffer_that_is_short_is_caught() {
let data: Vec<Vec<u8>> = Vec::new();
let err = check_views(&[0u8; 8], &data, 1, "v").expect_err("a view is sixteen bytes");
assert_eq!(err.invariant, Invariant::BufferLength);
}
}