Skip to main content

lance_table/rowids/
serde.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright The Lance Authors
3
4use crate::{format::pb, rowids::bitmap::Bitmap};
5use lance_core::{Error, Result};
6
7use super::{RowIdSequence, U64Segment, encoded_array::EncodedU64Array};
8use prost::Message;
9
10const ROW_ID_METADATA: &str = "row ID metadata";
11
12fn corrupt_row_id_metadata(message: impl Into<String>) -> Error {
13    Error::corrupt_file_named(ROW_ID_METADATA, message)
14}
15
16fn validate_range(segment_type: &str, start: u64, end: u64) -> Result<usize> {
17    let len = end.checked_sub(start).ok_or_else(|| {
18        corrupt_row_id_metadata(format!(
19            "{segment_type} range start {start} exceeds end {end}"
20        ))
21    })?;
22    usize::try_from(len).map_err(|_| {
23        corrupt_row_id_metadata(format!(
24            "{segment_type} range length {len} for start {start} and end {end} exceeds usize::MAX"
25        ))
26    })
27}
28
29fn validate_packed_array_length(array_type: &str, byte_len: usize, width: usize) -> Result<()> {
30    if !byte_len.is_multiple_of(width) {
31        return Err(corrupt_row_id_metadata(format!(
32            "encoded {array_type} array byte length {byte_len} is not a multiple of element width {width}"
33        )));
34    }
35    Ok(())
36}
37
38fn first_descending_pair(array: &EncodedU64Array) -> Option<(usize, u64, u64)> {
39    match array {
40        EncodedU64Array::U16 { offsets, .. } => offsets
41            .windows(2)
42            .position(|pair| pair[0] > pair[1])
43            .map(|index| (index, offsets[index] as u64, offsets[index + 1] as u64)),
44        EncodedU64Array::U32 { offsets, .. } => offsets
45            .windows(2)
46            .position(|pair| pair[0] > pair[1])
47            .map(|index| (index, offsets[index] as u64, offsets[index + 1] as u64)),
48        EncodedU64Array::U64(values) => values
49            .windows(2)
50            .position(|pair| pair[0] > pair[1])
51            .map(|index| (index, values[index], values[index + 1])),
52    }
53}
54
55fn first_non_increasing_pair(array: &EncodedU64Array) -> Option<(usize, u64, u64)> {
56    let mut values = array.iter();
57    let previous = values.next()?;
58    values
59        .scan(previous, |previous, value| {
60            let pair = (*previous, value);
61            *previous = value;
62            Some(pair)
63        })
64        .enumerate()
65        .find_map(|(index, (previous, next))| (previous >= next).then_some((index, previous, next)))
66}
67
68impl TryFrom<pb::RowIdSequence> for RowIdSequence {
69    type Error = Error;
70
71    fn try_from(pb: pb::RowIdSequence) -> Result<Self> {
72        let segments = pb
73            .segments
74            .into_iter()
75            .map(U64Segment::try_from)
76            .collect::<Result<Vec<_>>>()?;
77        // Each segment length fits a usize on its own, but the total need not fit a u64.
78        // Reject that here so `RowIdSequence::len()` stays total for anything decoded.
79        segments
80            .iter()
81            .try_fold(0_u64, |total, segment| {
82                total.checked_add(segment.len() as u64)
83            })
84            .ok_or_else(|| {
85                corrupt_row_id_metadata(format!(
86                    "row ID sequence of {} segments has a total length exceeding u64::MAX",
87                    segments.len()
88                ))
89            })?;
90        Ok(Self(segments))
91    }
92}
93
94impl TryFrom<pb::U64Segment> for U64Segment {
95    type Error = Error;
96
97    fn try_from(pb: pb::U64Segment) -> Result<Self> {
98        use pb::u64_segment as pb_seg;
99        use pb::u64_segment::Segment::*;
100        match pb.segment {
101            Some(Range(pb_seg::Range { start, end })) => {
102                validate_range("Range", start, end)?;
103                Ok(Self::Range(start..end))
104            }
105            Some(RangeWithHoles(pb_seg::RangeWithHoles { start, end, holes })) => {
106                validate_range("RangeWithHoles", start, end)?;
107                let holes = holes
108                    .ok_or_else(|| {
109                        corrupt_row_id_metadata("RangeWithHoles is missing its holes array")
110                    })?
111                    .try_into()?;
112                if let Some((index, previous, next)) = first_non_increasing_pair(&holes) {
113                    return Err(corrupt_row_id_metadata(format!(
114                        "RangeWithHoles values are not strictly increasing at indices {index} and {}: {previous} is not less than {next}",
115                        index + 1
116                    )));
117                }
118                if let Some(hole) = holes.iter().find(|hole| *hole < start || *hole >= end) {
119                    return Err(corrupt_row_id_metadata(format!(
120                        "RangeWithHoles hole {hole} is outside the range {start}..{end}"
121                    )));
122                }
123                Ok(Self::RangeWithHoles {
124                    range: start..end,
125                    holes,
126                })
127            }
128            Some(RangeWithBitmap(pb_seg::RangeWithBitmap { start, end, bitmap })) => {
129                let range_len = validate_range("RangeWithBitmap", start, end)?;
130                let expected_bitmap_len = range_len.div_ceil(8);
131                if bitmap.len() != expected_bitmap_len {
132                    return Err(corrupt_row_id_metadata(format!(
133                        "RangeWithBitmap byte length {} does not match expected {expected_bitmap_len} for range start {start}, end {end}, and length {range_len}",
134                        bitmap.len()
135                    )));
136                }
137                let remainder = range_len % 8;
138                if remainder != 0 {
139                    let padding_mask = !((1_u8 << remainder) - 1);
140                    let last_byte = bitmap[expected_bitmap_len - 1];
141                    if last_byte & padding_mask != 0 {
142                        return Err(corrupt_row_id_metadata(format!(
143                            "RangeWithBitmap padding bits must be zero for range start {start}, end {end}, and length {range_len}: last byte {last_byte:#04x} has padding mask {padding_mask:#04x} set"
144                        )));
145                    }
146                }
147                Ok(Self::RangeWithBitmap {
148                    range: start..end,
149                    bitmap: Bitmap::from_parts(bitmap, range_len),
150                })
151            }
152            Some(SortedArray(array)) => {
153                let array = EncodedU64Array::try_from(array)?;
154                if let Some((index, previous, next)) = first_descending_pair(&array) {
155                    return Err(corrupt_row_id_metadata(format!(
156                        "SortedArray values are not sorted at indices {index} and {}: {previous} exceeds {next}",
157                        index + 1
158                    )));
159                }
160                Ok(Self::SortedArray(array))
161            }
162            Some(Array(array)) => Ok(Self::Array(EncodedU64Array::try_from(array)?)),
163            // TODO: why non-exhaustive?
164            // Some(_) => Err(Error::invalid_input("unknown segment type")),
165            None => Err(corrupt_row_id_metadata("missing row ID segment type")),
166        }
167    }
168}
169
170impl TryFrom<pb::EncodedU64Array> for EncodedU64Array {
171    type Error = Error;
172
173    fn try_from(pb: pb::EncodedU64Array) -> Result<Self> {
174        use pb::encoded_u64_array as pb_arr;
175        use pb::encoded_u64_array::Array::*;
176        match pb.array {
177            Some(U16Array(pb_arr::U16Array { base, offsets })) => {
178                validate_packed_array_length("u16", offsets.len(), 2)?;
179                let offsets = offsets
180                    .chunks_exact(2)
181                    .map(|chunk| u16::from_le_bytes([chunk[0], chunk[1]]))
182                    .collect::<Vec<_>>();
183                if let Some(max_offset) = offsets.iter().copied().max()
184                    && base.checked_add(u64::from(max_offset)).is_none()
185                {
186                    return Err(corrupt_row_id_metadata(format!(
187                        "U16Array base {base} plus maximum offset {max_offset} overflows u64"
188                    )));
189                }
190                Ok(Self::U16 { base, offsets })
191            }
192            Some(U32Array(pb_arr::U32Array { base, offsets })) => {
193                validate_packed_array_length("u32", offsets.len(), 4)?;
194                let offsets = offsets
195                    .chunks_exact(4)
196                    .map(|chunk| u32::from_le_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]))
197                    .collect::<Vec<_>>();
198                if let Some(max_offset) = offsets.iter().copied().max()
199                    && base.checked_add(u64::from(max_offset)).is_none()
200                {
201                    return Err(corrupt_row_id_metadata(format!(
202                        "U32Array base {base} plus maximum offset {max_offset} overflows u64"
203                    )));
204                }
205                Ok(Self::U32 { base, offsets })
206            }
207            Some(U64Array(pb_arr::U64Array { values })) => {
208                validate_packed_array_length("u64", values.len(), 8)?;
209                let values = values
210                    .chunks_exact(8)
211                    .map(|chunk| {
212                        u64::from_le_bytes([
213                            chunk[0], chunk[1], chunk[2], chunk[3], chunk[4], chunk[5], chunk[6],
214                            chunk[7],
215                        ])
216                    })
217                    .collect();
218                Ok(Self::U64(values))
219            }
220            // TODO: shouldn't this enum be non-exhaustive?
221            // Some(_) => Err(Error::invalid_input("unknown array type")),
222            None => Err(corrupt_row_id_metadata("missing encoded row ID array type")),
223        }
224    }
225}
226
227impl From<RowIdSequence> for pb::RowIdSequence {
228    fn from(sequence: RowIdSequence) -> Self {
229        Self {
230            segments: sequence.0.into_iter().map(pb::U64Segment::from).collect(),
231        }
232    }
233}
234
235impl From<U64Segment> for pb::U64Segment {
236    fn from(segment: U64Segment) -> Self {
237        match segment {
238            U64Segment::Range(range) => Self {
239                segment: Some(pb::u64_segment::Segment::Range(pb::u64_segment::Range {
240                    start: range.start,
241                    end: range.end,
242                })),
243            },
244            U64Segment::RangeWithHoles { range, holes } => Self {
245                segment: Some(pb::u64_segment::Segment::RangeWithHoles(
246                    pb::u64_segment::RangeWithHoles {
247                        start: range.start,
248                        end: range.end,
249                        holes: Some(holes.into()),
250                    },
251                )),
252            },
253            U64Segment::RangeWithBitmap { range, bitmap } => Self {
254                segment: Some(pb::u64_segment::Segment::RangeWithBitmap(
255                    pb::u64_segment::RangeWithBitmap {
256                        start: range.start,
257                        end: range.end,
258                        bitmap: bitmap.into_bytes(),
259                    },
260                )),
261            },
262            U64Segment::SortedArray(array) => Self {
263                segment: Some(pb::u64_segment::Segment::SortedArray(array.into())),
264            },
265            U64Segment::Array(array) => Self {
266                segment: Some(pb::u64_segment::Segment::Array(array.into())),
267            },
268        }
269    }
270}
271
272impl From<EncodedU64Array> for pb::EncodedU64Array {
273    fn from(array: EncodedU64Array) -> Self {
274        match array {
275            EncodedU64Array::U16 { base, offsets } => Self {
276                array: Some(pb::encoded_u64_array::Array::U16Array(
277                    pb::encoded_u64_array::U16Array {
278                        base,
279                        offsets: offsets
280                            .iter()
281                            .flat_map(|&offset| offset.to_le_bytes().to_vec())
282                            .collect(),
283                    },
284                )),
285            },
286            EncodedU64Array::U32 { base, offsets } => Self {
287                array: Some(pb::encoded_u64_array::Array::U32Array(
288                    pb::encoded_u64_array::U32Array {
289                        base,
290                        offsets: offsets
291                            .iter()
292                            .flat_map(|&offset| offset.to_le_bytes().to_vec())
293                            .collect(),
294                    },
295                )),
296            },
297            EncodedU64Array::U64(values) => Self {
298                array: Some(pb::encoded_u64_array::Array::U64Array(
299                    pb::encoded_u64_array::U64Array {
300                        values: values
301                            .iter()
302                            .flat_map(|&value| value.to_le_bytes().to_vec())
303                            .collect(),
304                    },
305                )),
306            },
307        }
308    }
309}
310
311/// Serialize a rowid sequence to a buffer.
312pub fn write_row_ids(sequence: &RowIdSequence) -> Vec<u8> {
313    let pb_sequence = pb::RowIdSequence::from(sequence.clone());
314    pb_sequence.encode_to_vec()
315}
316
317/// Deserialize a rowid sequence from some bytes.
318pub fn read_row_ids(reader: &[u8]) -> Result<RowIdSequence> {
319    let pb_sequence = pb::RowIdSequence::decode(reader).map_err(|error| {
320        corrupt_row_id_metadata(format!("failed to decode row ID sequence: {error}"))
321    })?;
322    RowIdSequence::try_from(pb_sequence)
323}
324
325#[cfg(test)]
326mod test {
327    use pretty_assertions::assert_eq;
328    use proptest::prelude::*;
329    use rstest::rstest;
330
331    use super::*;
332
333    #[test]
334    fn test_bitmap_serialization_is_byte_exact() {
335        let mut bitmap = Bitmap::new_full(10);
336        bitmap.clear(2);
337        let segment = U64Segment::RangeWithBitmap {
338            range: 100..110,
339            bitmap,
340        };
341        assert_eq!(segment.len(), 9);
342
343        let serialized = pb::U64Segment::from(segment.clone());
344        let Some(pb::u64_segment::Segment::RangeWithBitmap(encoded)) = &serialized.segment else {
345            panic!("expected bitmap segment");
346        };
347        assert_eq!(encoded.bitmap, vec![0xfb, 0x03]);
348        assert_eq!(U64Segment::try_from(serialized).unwrap(), segment);
349    }
350    fn read_segment(segment: pb::u64_segment::Segment) -> Result<RowIdSequence> {
351        let sequence = pb::RowIdSequence {
352            segments: vec![pb::U64Segment {
353                segment: Some(segment),
354            }],
355        };
356        read_row_ids(&sequence.encode_to_vec())
357    }
358
359    fn assert_corrupt_segment(segment: pb::u64_segment::Segment, expected_message: &str) {
360        let error = read_segment(segment).unwrap_err();
361        assert!(matches!(&error, Error::CorruptFile { .. }));
362        assert!(
363            error.to_string().contains(expected_message),
364            "expected error containing {expected_message:?}, got {error}"
365        );
366    }
367
368    /// Each segment length fits a usize, but the aggregate does not fit a u64. Accepting
369    /// this would leave `RowIdSequence::len()` overflowing for callers such as
370    /// `Dataset::validate()`.
371    #[test]
372    fn test_reject_sequence_length_overflow() {
373        let segment = || pb::U64Segment {
374            segment: Some(pb::u64_segment::Segment::Range(pb::u64_segment::Range {
375                start: 0,
376                end: u64::MAX,
377            })),
378        };
379        let sequence = pb::RowIdSequence {
380            segments: vec![segment(), segment()],
381        };
382
383        let error = read_row_ids(&sequence.encode_to_vec()).unwrap_err();
384        assert!(matches!(&error, Error::CorruptFile { .. }));
385        assert!(
386            error
387                .to_string()
388                .contains("total length exceeding u64::MAX"),
389            "got {error}"
390        );
391    }
392
393    #[test]
394    fn test_write_read_row_ids() {
395        let mut sequence = RowIdSequence::from(0..20);
396        sequence.0.push(U64Segment::Range(30..100));
397        sequence.0.push(U64Segment::RangeWithHoles {
398            range: 100..200,
399            holes: EncodedU64Array::U64(vec![104, 108, 150]),
400        });
401        let mut bitmap = Bitmap::new_empty(100);
402        bitmap.set(99);
403        sequence.0.push(U64Segment::RangeWithBitmap {
404            range: 200..300,
405            bitmap,
406        });
407        sequence
408            .0
409            .push(U64Segment::SortedArray(EncodedU64Array::U16 {
410                base: 200,
411                offsets: vec![1, 2, 3],
412            }));
413        sequence
414            .0
415            .push(U64Segment::Array(EncodedU64Array::U64(vec![3, 1, 2])));
416
417        let serialized = write_row_ids(&sequence);
418
419        let sequence2 = read_row_ids(&serialized).unwrap();
420
421        assert_eq!(sequence.0, sequence2.0);
422    }
423
424    proptest! {
425        #[test]
426        fn test_row_id_sequence_len_round_trips(
427            values in proptest::collection::btree_set(any::<u64>(), 0..128)
428        ) {
429            let values = values.into_iter().collect::<Vec<_>>();
430            let sequence = RowIdSequence::from(values.as_slice());
431            let deserialized = read_row_ids(&write_row_ids(&sequence)).unwrap();
432
433            prop_assert_eq!(deserialized.len(), sequence.len());
434            prop_assert_eq!(deserialized.iter().collect::<Vec<_>>(), values);
435        }
436
437        #[test]
438        fn test_rejects_wrong_range_with_bitmap_length(
439            range_len in 1usize..512,
440        ) {
441            let expected_len = range_len.div_ceil(8);
442            for actual_len in [expected_len - 1, expected_len + 1] {
443                let segment = pb::u64_segment::Segment::RangeWithBitmap(
444                    pb::u64_segment::RangeWithBitmap {
445                        start: 0,
446                        end: range_len as u64,
447                        bitmap: vec![0; actual_len],
448                    },
449                );
450
451                let error = read_segment(segment).unwrap_err();
452                let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
453                prop_assert!(is_corrupt_file);
454                prop_assert!(error.to_string().contains("byte length"));
455            }
456        }
457
458        #[test]
459        fn test_rejects_range_with_bitmap_padding_bits(
460            full_bytes in 0usize..64,
461            valid_bits in 1usize..8,
462        ) {
463            let range_len = full_bytes * 8 + valid_bits;
464            let mut bitmap = vec![0; full_bytes + 1];
465            bitmap[full_bytes] = 1 << valid_bits;
466            let segment = pb::u64_segment::Segment::RangeWithBitmap(
467                pb::u64_segment::RangeWithBitmap {
468                    start: 0,
469                    end: range_len as u64,
470                    bitmap,
471                },
472            );
473
474            let error = read_segment(segment).unwrap_err();
475            let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
476            prop_assert!(is_corrupt_file);
477            prop_assert!(error.to_string().contains("padding bits must be zero"));
478        }
479
480        #[test]
481        fn test_rejects_reversed_range_with_bitmap(
482            start in 1u64..u64::MAX,
483        ) {
484            let segment = pb::u64_segment::Segment::RangeWithBitmap(
485                pb::u64_segment::RangeWithBitmap {
486                    start,
487                    end: start - 1,
488                    bitmap: Vec::new(),
489                },
490            );
491
492            let error = read_segment(segment).unwrap_err();
493            let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
494            prop_assert!(is_corrupt_file);
495            prop_assert!(error.to_string().contains("range start"));
496        }
497
498        #[test]
499        fn test_rejects_misaligned_encoded_array_bytes(
500            encoding in 0u8..3,
501            element_count in 0usize..16,
502        ) {
503            let width = match encoding {
504                0 => 2,
505                1 => 4,
506                _ => 8,
507            };
508            let bytes = vec![0; element_count * width + 1];
509            let array = match encoding {
510                0 => pb::encoded_u64_array::Array::U16Array(
511                    pb::encoded_u64_array::U16Array { base: 0, offsets: bytes },
512                ),
513                1 => pb::encoded_u64_array::Array::U32Array(
514                    pb::encoded_u64_array::U32Array { base: 0, offsets: bytes },
515                ),
516                _ => pb::encoded_u64_array::Array::U64Array(
517                    pb::encoded_u64_array::U64Array { values: bytes },
518                ),
519            };
520            let segment = pb::u64_segment::Segment::Array(pb::EncodedU64Array {
521                array: Some(array),
522            });
523
524            let error = read_segment(segment).unwrap_err();
525            let is_corrupt_file = matches!(&error, Error::CorruptFile { .. });
526            prop_assert!(is_corrupt_file);
527            prop_assert!(error.to_string().contains("byte length"));
528        }
529    }
530
531    #[test]
532    fn test_rejects_encoded_offset_overflow() {
533        use pb::encoded_u64_array as pb_array;
534
535        let arrays = [
536            pb_array::Array::U16Array(pb_array::U16Array {
537                base: u64::MAX,
538                offsets: 1u16.to_le_bytes().to_vec(),
539            }),
540            pb_array::Array::U32Array(pb_array::U32Array {
541                base: u64::MAX,
542                offsets: 1u32.to_le_bytes().to_vec(),
543            }),
544        ];
545        for array in arrays {
546            assert_corrupt_segment(
547                pb::u64_segment::Segment::Array(pb::EncodedU64Array { array: Some(array) }),
548                "overflows u64",
549            );
550        }
551    }
552
553    #[rstest]
554    #[case::descending(vec![6, 5], "not strictly increasing")]
555    #[case::duplicate(vec![5, 5], "not strictly increasing")]
556    #[case::below_range(vec![4], "outside the range")]
557    #[case::at_end(vec![7], "outside the range")]
558    fn test_rejects_invalid_range_with_holes(#[case] values: Vec<u64>, #[case] message: &str) {
559        let values = values.into_iter().flat_map(u64::to_le_bytes).collect();
560        assert_corrupt_segment(
561            pb::u64_segment::Segment::RangeWithHoles(pb::u64_segment::RangeWithHoles {
562                start: 5,
563                end: 7,
564                holes: Some(pb::EncodedU64Array {
565                    array: Some(pb::encoded_u64_array::Array::U64Array(
566                        pb::encoded_u64_array::U64Array { values },
567                    )),
568                }),
569            }),
570            message,
571        );
572    }
573
574    #[test]
575    fn test_rejects_missing_range_with_holes_array() {
576        assert_corrupt_segment(
577            pb::u64_segment::Segment::RangeWithHoles(pb::u64_segment::RangeWithHoles {
578                start: 5,
579                end: 7,
580                holes: None,
581            }),
582            "missing its holes array",
583        );
584    }
585
586    #[rstest]
587    #[case::u16(
588        pb::encoded_u64_array::Array::U16Array(pb::encoded_u64_array::U16Array {
589            base: 0,
590            offsets: vec![1],
591        }),
592        "encoded u16 array byte length 1 is not a multiple of element width 2"
593    )]
594    #[case::u32(
595        pb::encoded_u64_array::Array::U32Array(pb::encoded_u64_array::U32Array {
596            base: 0,
597            offsets: vec![1, 2, 3],
598        }),
599        "encoded u32 array byte length 3 is not a multiple of element width 4"
600    )]
601    #[case::u64(
602        pb::encoded_u64_array::Array::U64Array(pb::encoded_u64_array::U64Array {
603            values: vec![1, 2, 3, 4, 5, 6, 7],
604        }),
605        "encoded u64 array byte length 7 is not a multiple of element width 8"
606    )]
607    fn test_rejects_misaligned_encoded_array(
608        #[case] array: pb::encoded_u64_array::Array,
609        #[case] message: &str,
610    ) {
611        assert_corrupt_segment(
612            pb::u64_segment::Segment::Array(pb::EncodedU64Array { array: Some(array) }),
613            message,
614        );
615    }
616
617    #[rstest]
618    #[case::range(pb::u64_segment::Segment::Range(pb::u64_segment::Range {
619        start: 10,
620        end: 9,
621    }))]
622    #[case::range_with_holes(pb::u64_segment::Segment::RangeWithHoles(
623        pb::u64_segment::RangeWithHoles {
624            start: 10,
625            end: 9,
626            holes: Some(pb::EncodedU64Array {
627                array: Some(pb::encoded_u64_array::Array::U64Array(
628                    pb::encoded_u64_array::U64Array { values: Vec::new() },
629                )),
630            }),
631        }
632    ))]
633    #[case::range_with_bitmap(pb::u64_segment::Segment::RangeWithBitmap(
634        pb::u64_segment::RangeWithBitmap {
635            start: 10,
636            end: 9,
637            bitmap: Vec::new(),
638        }
639    ))]
640    fn test_rejects_reversed_range(#[case] segment: pb::u64_segment::Segment) {
641        assert_corrupt_segment(segment, "range start 10 exceeds end 9");
642    }
643
644    #[rstest]
645    #[case::short(vec![0])]
646    #[case::long(vec![0, 0, 0])]
647    fn test_rejects_incorrect_bitmap_length(#[case] bitmap: Vec<u8>) {
648        assert_corrupt_segment(
649            pb::u64_segment::Segment::RangeWithBitmap(pb::u64_segment::RangeWithBitmap {
650                start: 5,
651                end: 14,
652                bitmap,
653            }),
654            "does not match expected 2 for range start 5, end 14, and length 9",
655        );
656    }
657
658    #[test]
659    fn test_rejects_set_bitmap_padding_bits() {
660        assert_corrupt_segment(
661            pb::u64_segment::Segment::RangeWithBitmap(pb::u64_segment::RangeWithBitmap {
662                start: 5,
663                end: 14,
664                bitmap: vec![0xff, 0x03],
665            }),
666            "padding bits must be zero",
667        );
668    }
669
670    #[rstest]
671    #[case::u16(pb::encoded_u64_array::Array::U16Array(
672        pb::encoded_u64_array::U16Array {
673            base: 100,
674            offsets: vec![2, 0, 1, 0],
675        }
676    ))]
677    #[case::u32(pb::encoded_u64_array::Array::U32Array(
678        pb::encoded_u64_array::U32Array {
679            base: 100,
680            offsets: vec![2, 0, 0, 0, 1, 0, 0, 0],
681        }
682    ))]
683    #[case::u64(pb::encoded_u64_array::Array::U64Array(
684        pb::encoded_u64_array::U64Array {
685            values: vec![2, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0],
686        }
687    ))]
688    fn test_rejects_unsorted_sorted_array(#[case] array: pb::encoded_u64_array::Array) {
689        assert_corrupt_segment(
690            pb::u64_segment::Segment::SortedArray(pb::EncodedU64Array { array: Some(array) }),
691            "SortedArray values are not sorted at indices 0 and 1: 2 exceeds 1",
692        );
693    }
694}