1use arrow_schema::DataType;
14
15use crate::error::{Invariant, Result, Violation};
16
17pub const VIEW_WIDTH: usize = 16;
22
23pub const VIEW_INLINE: u32 = 12;
25
26pub fn check_dictionary(
37 keys: &[u8],
38 key_type: &DataType,
39 len: u64,
40 dictionary_len: u64,
41 path: &str,
42) -> Result<()> {
43 let (width, signed) = match key_type {
44 DataType::Int8 => (1usize, true),
45 DataType::Int16 => (2, true),
46 DataType::Int32 => (4, true),
47 DataType::Int64 => (8, true),
48 DataType::UInt8 => (1, false),
49 DataType::UInt16 => (2, false),
50 DataType::UInt32 => (4, false),
51 DataType::UInt64 => (8, false),
52 other => {
53 return Err(Violation::at(
54 Invariant::Unsupported,
55 path,
56 format!("{other} is not an integer, so it cannot be a dictionary key"),
57 ));
58 }
59 };
60
61 let slots = usize::try_from(len).map_err(|_| {
62 Violation::at(
63 Invariant::Size,
64 path,
65 format!("{len} keys is more than this host can address"),
66 )
67 })?;
68 let needed = slots.checked_mul(width).ok_or_else(|| {
69 Violation::at(
70 Invariant::Size,
71 path,
72 format!("{len} keys of {width} bytes is more than this host can address"),
73 )
74 })?;
75 if keys.len() < needed {
76 return Err(Violation::at(
77 Invariant::BufferLength,
78 path,
79 format!(
80 "{len} keys need {needed} bytes and there are {}",
81 keys.len()
82 ),
83 ));
84 }
85
86 for slot in 0..slots {
87 let at = slot * width;
88 let raw = &keys[at..at + width];
89 let key = if signed {
90 let value = read_signed(raw);
91 if value < 0 {
92 return Err(Violation::at(
93 Invariant::DictionaryIndex,
94 path,
95 format!("key {slot} is {value}, and a key is a position"),
96 ));
97 }
98 u64::try_from(value).unwrap_or(u64::MAX)
99 } else {
100 read_unsigned(raw)
101 };
102
103 if key >= dictionary_len {
104 return Err(Violation::at(
105 Invariant::DictionaryIndex,
106 path,
107 format!("key {slot} is {key} and the dictionary has {dictionary_len} values"),
108 ));
109 }
110 }
111
112 Ok(())
113}
114
115pub fn check_views<B: AsRef<[u8]>>(views: &[u8], data: &[B], len: u64, path: &str) -> Result<()> {
127 let slots = usize::try_from(len).map_err(|_| {
128 Violation::at(
129 Invariant::Size,
130 path,
131 format!("{len} views is more than this host can address"),
132 )
133 })?;
134 let needed = slots.checked_mul(VIEW_WIDTH).ok_or_else(|| {
135 Violation::at(
136 Invariant::Size,
137 path,
138 format!("{len} views of {VIEW_WIDTH} bytes is more than this host can address"),
139 )
140 })?;
141 if views.len() < needed {
142 return Err(Violation::at(
143 Invariant::BufferLength,
144 path,
145 format!(
146 "{len} views need {needed} bytes and there are {}",
147 views.len()
148 ),
149 ));
150 }
151
152 for slot in 0..slots {
153 let at = slot * VIEW_WIDTH;
154 let view = &views[at..at + VIEW_WIDTH];
155 let length = read_u32(&view[0..4]);
156
157 if length <= VIEW_INLINE {
160 continue;
161 }
162
163 let index = read_u32(&view[8..12]);
164 let offset = read_u32(&view[12..16]);
165
166 let buffer = usize::try_from(index)
167 .ok()
168 .and_then(|index| data.get(index))
169 .ok_or_else(|| {
170 Violation::at(
171 Invariant::ViewBuffer,
172 path,
173 format!(
174 "view {slot} points at data buffer {index} and there are {}",
175 data.len()
176 ),
177 )
178 })?;
179
180 let end = u64::from(offset) + u64::from(length);
181 let have = u64::try_from(buffer.as_ref().len()).unwrap_or(u64::MAX);
182 if end > have {
183 return Err(Violation::at(
184 Invariant::ViewBuffer,
185 path,
186 format!(
187 "view {slot} reads {length} bytes at {offset} of data buffer {index}, which is \
188 {have} bytes"
189 ),
190 ));
191 }
192 }
193
194 Ok(())
195}
196
197fn read_signed(raw: &[u8]) -> i64 {
198 let mut wide = [0u8; 8];
199 wide[..raw.len()].copy_from_slice(raw);
200 let value = i64::from_le_bytes(wide);
201 let spare = 64 - u32::try_from(raw.len()).unwrap_or(8) * 8;
203 (value << spare) >> spare
204}
205
206fn read_unsigned(raw: &[u8]) -> u64 {
207 let mut wide = [0u8; 8];
208 wide[..raw.len()].copy_from_slice(raw);
209 u64::from_le_bytes(wide)
210}
211
212fn read_u32(raw: &[u8]) -> u32 {
213 let mut wide = [0u8; 4];
214 wide.copy_from_slice(raw);
215 u32::from_le_bytes(wide)
216}
217
218#[cfg(test)]
219mod tests {
220 use arrow_schema::DataType;
221
222 use super::{check_dictionary, check_views};
223 use crate::error::Invariant;
224
225 fn view(length: u32, index: u32, offset: u32) -> Vec<u8> {
226 let mut out = Vec::with_capacity(16);
227 out.extend_from_slice(&length.to_le_bytes());
228 out.extend_from_slice(&[0u8; 4]);
229 out.extend_from_slice(&index.to_le_bytes());
230 out.extend_from_slice(&offset.to_le_bytes());
231 out
232 }
233
234 #[test]
235 fn keys_inside_the_dictionary_pass() {
236 let keys: Vec<u8> = [0i32, 1, 2].iter().flat_map(|k| k.to_le_bytes()).collect();
237 check_dictionary(&keys, &DataType::Int32, 3, 3, "d").expect("every key is a slot");
238 }
239
240 #[test]
241 fn a_key_equal_to_the_dictionary_length_is_caught() {
242 let keys: Vec<u8> = [0i32, 3].iter().flat_map(|k| k.to_le_bytes()).collect();
243 let err = check_dictionary(&keys, &DataType::Int32, 2, 3, "d")
244 .expect_err("three is one past the last slot");
245 assert_eq!(err.invariant, Invariant::DictionaryIndex);
246 assert!(err.to_string().contains("has 3 values"), "{err}");
247 }
248
249 #[test]
250 fn a_negative_key_is_caught_rather_than_read_as_enormous() {
251 let keys: Vec<u8> = (-1i32).to_le_bytes().to_vec();
252 let err =
253 check_dictionary(&keys, &DataType::Int32, 1, 3, "d").expect_err("a key is a position");
254 assert_eq!(err.invariant, Invariant::DictionaryIndex);
255 assert!(err.to_string().contains("is -1"), "{err}");
256 }
257
258 #[test]
259 fn an_unsigned_key_type_is_read_as_unsigned() {
260 let keys: Vec<u8> = 200u8.to_le_bytes().to_vec();
261 check_dictionary(&keys, &DataType::UInt8, 1, 255, "d").expect("200 is a slot in 255");
262 let err = check_dictionary(&keys, &DataType::UInt8, 1, 200, "d")
263 .expect_err("200 is not a slot in 200");
264 assert_eq!(err.invariant, Invariant::DictionaryIndex);
265 }
266
267 #[test]
268 fn a_key_type_that_is_not_an_integer_is_refused_by_name() {
269 let err = check_dictionary(&[], &DataType::Utf8, 0, 0, "d")
270 .expect_err("a string is not a key type");
271 assert_eq!(err.invariant, Invariant::Unsupported);
272 }
273
274 #[test]
275 fn views_inside_their_buffers_pass() {
276 let data: Vec<Vec<u8>> = vec![b"hello there friend".to_vec()];
277 let views = [view(18, 0, 0), view(4, 0, 3)].concat();
278 check_views(&views, &data, 2, "v").expect("both views are inside the buffer");
279 }
280
281 #[test]
282 fn a_view_buffer_index_equal_to_the_buffer_count_is_caught() {
283 let data: Vec<Vec<u8>> = vec![b"hello there friend".to_vec()];
284 let views = view(18, 1, 0);
285 let err = check_views(&views, &data, 1, "v").expect_err("there is no buffer 1");
286 assert_eq!(err.invariant, Invariant::ViewBuffer);
287 assert!(err.to_string().contains("there are 1"), "{err}");
288 }
289
290 #[test]
291 fn a_view_that_runs_off_the_end_of_its_buffer_is_caught() {
292 let data: Vec<Vec<u8>> = vec![b"hello there friend".to_vec()];
293 let views = view(18, 0, 1);
294 let err = check_views(&views, &data, 1, "v").expect_err("that is one byte too many");
295 assert_eq!(err.invariant, Invariant::ViewBuffer);
296 }
297
298 #[test]
299 fn a_short_value_lives_in_the_view_and_points_at_nothing() {
300 let data: Vec<Vec<u8>> = Vec::new();
301 let views = view(12, 99, 99);
302 check_views(&views, &data, 1, "v").expect("an inline value indexes nothing");
303 }
304
305 #[test]
306 fn a_views_buffer_that_is_short_is_caught() {
307 let data: Vec<Vec<u8>> = Vec::new();
308 let err = check_views(&[0u8; 8], &data, 1, "v").expect_err("a view is sixteen bytes");
309 assert_eq!(err.invariant, Invariant::BufferLength);
310 }
311}