1use 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 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 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 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
311pub 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
317pub 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 #[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}