1#![no_std]
29
30#[cfg(feature = "std")]
31extern crate std;
32
33mod enums;
34mod error;
35
36pub use crate::{
37 enums::{ColorModel, ColorPrimaries, Format, SupercompressionScheme, TransferFunction},
38 error::ParseError,
39};
40
41use core::{convert::TryInto, num::NonZeroU8};
42
43pub struct Reader<Data: AsRef<[u8]>> {
45 input: Data,
46 header: Header,
47}
48
49impl<Data: AsRef<[u8]>> Reader<Data> {
50 pub fn new(input: Data) -> Result<Self, ParseError> {
52 if input.as_ref().len() < Header::LENGTH {
53 return Err(ParseError::UnexpectedEnd);
54 }
55 let header_data = input.as_ref()[0..Header::LENGTH].try_into().unwrap();
56 let header = Header::from_bytes(header_data)?;
57
58 let dfd_start = header
60 .index
61 .dfd_byte_offset
62 .checked_add(4)
63 .ok_or(ParseError::UnexpectedEnd)?;
64 let dfd_end = header
65 .index
66 .dfd_byte_offset
67 .checked_add(header.index.dfd_byte_length)
68 .ok_or(ParseError::UnexpectedEnd)?;
69 if dfd_end < dfd_start || dfd_end as usize >= input.as_ref().len() {
70 return Err(ParseError::UnexpectedEnd);
71 }
72
73 if header
75 .index
76 .sgd_byte_offset
77 .checked_add(header.index.sgd_byte_length)
78 .ok_or(ParseError::UnexpectedEnd)?
79 >= input.as_ref().len() as u64
80 {
81 return Err(ParseError::UnexpectedEnd);
82 }
83
84 if header
86 .index
87 .kvd_byte_offset
88 .checked_add(header.index.kvd_byte_length)
89 .ok_or(ParseError::UnexpectedEnd)? as usize
90 >= input.as_ref().len()
91 {
92 return Err(ParseError::UnexpectedEnd);
93 }
94
95 let result = Self { input, header };
96 let index = result.level_index()?; for level in index {
100 if level
101 .byte_offset
102 .checked_add(level.byte_length)
103 .ok_or(ParseError::UnexpectedEnd)?
104 > result.input.as_ref().len() as u64
105 {
106 return Err(ParseError::UnexpectedEnd);
107 }
108 }
109
110 Ok(result)
111 }
112
113 fn level_index(&self) -> ParseResult<impl ExactSizeIterator<Item = LevelIndex> + '_> {
114 let level_count = self.header().level_count.max(1) as usize;
115
116 let level_index_end_byte = Header::LENGTH
117 .checked_add(
118 level_count
119 .checked_mul(LevelIndex::LENGTH)
120 .ok_or(ParseError::UnexpectedEnd)?,
121 )
122 .ok_or(ParseError::UnexpectedEnd)?;
123 let level_index_bytes = self
124 .input
125 .as_ref()
126 .get(Header::LENGTH..level_index_end_byte)
127 .ok_or(ParseError::UnexpectedEnd)?;
128 Ok(level_index_bytes.chunks_exact(LevelIndex::LENGTH).map(|data| {
129 let level_data = data.try_into().unwrap();
130 LevelIndex::from_bytes(&level_data)
131 }))
132 }
133
134 pub fn data(&self) -> &[u8] {
136 self.input.as_ref()
137 }
138
139 pub fn header(&self) -> Header {
141 self.header
142 }
143
144 pub fn levels(&self) -> impl ExactSizeIterator<Item = Level> + '_ {
146 self.level_index().unwrap().map(move |level| Level {
147 data: &self.input.as_ref()[level.byte_offset as usize..(level.byte_offset + level.byte_length) as usize],
149 uncompressed_byte_length: level.uncompressed_byte_length,
150 })
151 }
152
153 pub fn supercompression_global_data(&self) -> &[u8] {
154 let header = self.header();
155 let start = header.index.sgd_byte_offset as usize;
156 let end = (header.index.sgd_byte_offset + header.index.sgd_byte_length) as usize;
158 &self.input.as_ref()[start..end]
159 }
160
161 pub fn dfd_blocks(&self) -> impl Iterator<Item = DfdBlock> {
162 let header = self.header();
163 let start = header.index.dfd_byte_offset as usize;
164 let end = (header.index.dfd_byte_offset + header.index.dfd_byte_length) as usize;
166 DfdBlockIterator {
167 data: &self.input.as_ref()[start + 4..end],
169 }
170 }
171
172 pub fn key_value_data(&self) -> KeyValueDataIterator {
174 let header = self.header();
175
176 let start = header.index.kvd_byte_offset as usize;
177 let end = (header.index.kvd_byte_offset + header.index.kvd_byte_length) as usize;
179
180 KeyValueDataIterator::new(&self.input.as_ref()[start..end])
181 }
182}
183
184struct DfdBlockIterator<'data> {
185 data: &'data [u8],
186}
187
188impl<'data> Iterator for DfdBlockIterator<'data> {
189 type Item = DfdBlock<'data>;
190
191 fn next(&mut self) -> Option<Self::Item> {
192 if self.data.len() < DfdHeader::LENGTH {
193 return None;
194 }
195 DfdHeader::parse(&self.data[..DfdHeader::LENGTH]).map_or(None, |(header, descriptor_block_size)| {
196 if descriptor_block_size == 0 || self.data.len() < descriptor_block_size {
197 return None;
198 }
199 let data = &self.data[DfdHeader::LENGTH..descriptor_block_size];
200 self.data = &self.data[descriptor_block_size..];
201 Some(DfdBlock { header, data })
202 })
203 }
204}
205
206pub struct KeyValueDataIterator<'data> {
208 data: &'data [u8],
209}
210
211impl<'data> KeyValueDataIterator<'data> {
212 pub fn new(data: &'data [u8]) -> Self {
217 Self { data }
218 }
219}
220
221impl<'data> Iterator for KeyValueDataIterator<'data> {
222 type Item = (&'data str, &'data [u8]);
223
224 fn next(&mut self) -> Option<Self::Item> {
225 let mut offset = 0;
226
227 loop {
228 let length = bytes_to_u32(self.data, &mut offset).ok()?;
229
230 let start_offset = offset;
231
232 offset = offset.checked_add(length as usize)?;
233
234 let end_offset = offset;
235
236 if offset % 4 != 0 {
238 offset += 4 - (offset % 4);
239 }
240
241 let key_and_value = match self.data.get(start_offset..end_offset) {
242 Some(key_and_value) => key_and_value,
243 None => continue,
244 };
245
246 let key_end_index = match key_and_value.iter().position(|&c| c == b'\0') {
248 Some(index) => index,
249 None => continue,
250 };
251
252 let key = &key_and_value[..key_end_index];
253 let value = &key_and_value[key_end_index + 1..];
254
255 let key = match core::str::from_utf8(key) {
256 Ok(key) => key,
257 Err(_) => continue,
258 };
259
260 self.data = self.data.get(offset..).unwrap_or_default();
261
262 return Some((key, value));
263 }
264 }
265}
266
267const KTX2_MAGIC: [u8; 12] = [0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A];
269
270type ParseResult<T> = Result<T, ParseError>;
272
273#[derive(Copy, Clone, Eq, PartialEq, Debug)]
275pub struct Header {
276 pub format: Option<Format>,
277 pub type_size: u32,
278 pub pixel_width: u32,
279 pub pixel_height: u32,
280 pub pixel_depth: u32,
281 pub layer_count: u32,
282 pub face_count: u32,
283 pub level_count: u32,
284 pub supercompression_scheme: Option<SupercompressionScheme>,
285 pub index: Index,
286}
287
288#[derive(Copy, Clone, Eq, PartialEq, Debug)]
290pub struct Index {
291 pub dfd_byte_offset: u32,
292 pub dfd_byte_length: u32,
293 pub kvd_byte_offset: u32,
294 pub kvd_byte_length: u32,
295 pub sgd_byte_offset: u64,
296 pub sgd_byte_length: u64,
297}
298
299impl Header {
300 pub const LENGTH: usize = 80;
301
302 pub fn from_bytes(data: &[u8; Self::LENGTH]) -> ParseResult<Self> {
303 if !data.starts_with(&KTX2_MAGIC) {
304 return Err(ParseError::BadMagic);
305 }
306
307 let header = Self {
308 format: Format::new(u32::from_le_bytes(data[12..16].try_into().unwrap())),
309 type_size: u32::from_le_bytes(data[16..20].try_into().unwrap()),
310 pixel_width: u32::from_le_bytes(data[20..24].try_into().unwrap()),
311 pixel_height: u32::from_le_bytes(data[24..28].try_into().unwrap()),
312 pixel_depth: u32::from_le_bytes(data[28..32].try_into().unwrap()),
313 layer_count: u32::from_le_bytes(data[32..36].try_into().unwrap()),
314 face_count: u32::from_le_bytes(data[36..40].try_into().unwrap()),
315 level_count: u32::from_le_bytes(data[40..44].try_into().unwrap()),
316 supercompression_scheme: SupercompressionScheme::new(u32::from_le_bytes(data[44..48].try_into().unwrap())),
317 index: Index {
318 dfd_byte_offset: u32::from_le_bytes(data[48..52].try_into().unwrap()),
319 dfd_byte_length: u32::from_le_bytes(data[52..56].try_into().unwrap()),
320 kvd_byte_offset: u32::from_le_bytes(data[56..60].try_into().unwrap()),
321 kvd_byte_length: u32::from_le_bytes(data[60..64].try_into().unwrap()),
322 sgd_byte_offset: u64::from_le_bytes(data[64..72].try_into().unwrap()),
323 sgd_byte_length: u64::from_le_bytes(data[72..80].try_into().unwrap()),
324 },
325 };
326
327 if header.pixel_width == 0 {
328 return Err(ParseError::ZeroWidth);
329 }
330 if header.face_count == 0 {
331 return Err(ParseError::ZeroFaceCount);
332 }
333
334 Ok(header)
335 }
336
337 pub fn as_bytes(&self) -> [u8; Self::LENGTH] {
338 let mut bytes = [0; Self::LENGTH];
339
340 let format = self.format.map(|format| format.value()).unwrap_or(0);
341 let supercompression_scheme = self.supercompression_scheme.map(|scheme| scheme.value()).unwrap_or(0);
342
343 bytes[0..12].copy_from_slice(&KTX2_MAGIC);
344 bytes[12..16].copy_from_slice(&format.to_le_bytes()[..]);
345 bytes[16..20].copy_from_slice(&self.type_size.to_le_bytes()[..]);
346 bytes[20..24].copy_from_slice(&self.pixel_width.to_le_bytes()[..]);
347 bytes[24..28].copy_from_slice(&self.pixel_height.to_le_bytes()[..]);
348 bytes[28..32].copy_from_slice(&self.pixel_depth.to_le_bytes()[..]);
349 bytes[32..36].copy_from_slice(&self.layer_count.to_le_bytes()[..]);
350 bytes[36..40].copy_from_slice(&self.face_count.to_le_bytes()[..]);
351 bytes[40..44].copy_from_slice(&self.level_count.to_le_bytes()[..]);
352 bytes[44..48].copy_from_slice(&supercompression_scheme.to_le_bytes()[..]);
353 bytes[48..52].copy_from_slice(&self.index.dfd_byte_offset.to_le_bytes()[..]);
354 bytes[52..56].copy_from_slice(&self.index.dfd_byte_length.to_le_bytes()[..]);
355 bytes[56..60].copy_from_slice(&self.index.kvd_byte_offset.to_le_bytes()[..]);
356 bytes[60..64].copy_from_slice(&self.index.kvd_byte_length.to_le_bytes()[..]);
357 bytes[64..72].copy_from_slice(&self.index.sgd_byte_offset.to_le_bytes()[..]);
358 bytes[72..80].copy_from_slice(&self.index.sgd_byte_length.to_le_bytes()[..]);
359
360 bytes
361 }
362}
363
364pub struct Level<'a> {
365 pub data: &'a [u8],
366 pub uncompressed_byte_length: u64,
367}
368
369#[derive(Debug, Eq, PartialEq, Copy, Clone)]
370pub struct LevelIndex {
371 pub byte_offset: u64,
372 pub byte_length: u64,
373 pub uncompressed_byte_length: u64,
374}
375
376impl LevelIndex {
377 pub const LENGTH: usize = 24;
378
379 pub fn from_bytes(data: &[u8; Self::LENGTH]) -> Self {
380 Self {
381 byte_offset: u64::from_le_bytes(data[0..8].try_into().unwrap()),
382 byte_length: u64::from_le_bytes(data[8..16].try_into().unwrap()),
383 uncompressed_byte_length: u64::from_le_bytes(data[16..24].try_into().unwrap()),
384 }
385 }
386
387 pub fn as_bytes(&self) -> [u8; Self::LENGTH] {
388 let mut bytes = [0; Self::LENGTH];
389
390 bytes[0..8].copy_from_slice(&self.byte_offset.to_le_bytes()[..]);
391 bytes[8..16].copy_from_slice(&self.byte_length.to_le_bytes()[..]);
392 bytes[16..24].copy_from_slice(&self.uncompressed_byte_length.to_le_bytes()[..]);
393
394 bytes
395 }
396}
397
398bitflags::bitflags! {
399 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
400 #[repr(transparent)]
401 pub struct ChannelTypeQualifiers: u8 {
402 const LINEAR = (1 << 0);
403 const EXPONENT = (1 << 1);
404 const SIGNED = (1 << 2);
405 const FLOAT = (1 << 3);
406 }
407}
408
409bitflags::bitflags! {
410 #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
411 #[repr(transparent)]
412 pub struct DataFormatFlags: u8 {
413 const STRAIGHT_ALPHA = 0;
414 const ALPHA_PREMULTIPLIED = (1 << 0);
415 }
416}
417
418#[derive(Debug, PartialEq, Eq)]
419pub struct DfdHeader {
420 pub vendor_id: u32, pub descriptor_type: u32, pub version_number: u16, }
424
425impl DfdHeader {
426 pub const LENGTH: usize = 8;
427
428 pub const BASIC: Self = Self {
429 vendor_id: 0,
430 descriptor_type: 0,
431 version_number: 2,
432 };
433
434 pub fn as_bytes(&self, descriptor_block_size: u16) -> [u8; Self::LENGTH] {
435 let mut output = [0u8; Self::LENGTH];
436
437 let first_word = (self.vendor_id & ((1 << 17) - 1)) | (self.descriptor_type << 17);
438 output[0..4].copy_from_slice(&first_word.to_le_bytes());
439 output[4..6].copy_from_slice(&self.version_number.to_le_bytes());
440 output[6..8].copy_from_slice(&descriptor_block_size.to_le_bytes());
441
442 output
443 }
444
445 fn parse(bytes: &[u8]) -> Result<(Self, usize), ParseError> {
446 let mut offset = 0;
447
448 let v = bytes_to_u32(bytes, &mut offset)?;
449 let vendor_id = shift_and_mask_lower(0, 17, v);
450 let descriptor_type = shift_and_mask_lower(17, 15, v);
451
452 let version_number = read_u16(bytes, &mut offset)?;
453 let descriptor_block_size = read_u16(bytes, &mut offset)?;
454
455 Ok((
456 Self {
457 vendor_id,
458 descriptor_type,
459 version_number,
460 },
461 descriptor_block_size as usize,
462 ))
463 }
464}
465
466pub struct DfdBlock<'data> {
467 pub header: DfdHeader,
468 pub data: &'data [u8],
469}
470
471#[derive(Debug, Copy, Clone, PartialEq, Eq)]
472pub struct DfdBlockHeaderBasic {
473 pub color_model: Option<ColorModel>, pub color_primaries: Option<ColorPrimaries>, pub transfer_function: Option<TransferFunction>, pub flags: DataFormatFlags, pub texel_block_dimensions: [NonZeroU8; 4], pub bytes_planes: [u8; 8], }
483
484impl DfdBlockHeaderBasic {
485 pub const LENGTH: usize = 16;
486
487 pub fn as_bytes(&self) -> [u8; Self::LENGTH] {
488 let mut bytes = [0u8; Self::LENGTH];
489
490 let color_model = self.color_model.map(|c| c.value()).unwrap_or(0);
491 let color_primaries = self.color_primaries.map(|c| c.value()).unwrap_or(0);
492 let transfer_function = self.transfer_function.map(|t| t.value()).unwrap_or(0);
493
494 let texel_block_dimensions = self.texel_block_dimensions.map(|dim| dim.get() - 1);
495
496 bytes[0] = color_model;
497 bytes[1] = color_primaries;
498 bytes[2] = transfer_function;
499 bytes[3] = self.flags.bits();
500 bytes[4..8].copy_from_slice(&texel_block_dimensions);
501 bytes[8..16].copy_from_slice(&self.bytes_planes);
502
503 bytes
504 }
505
506 pub fn from_bytes(bytes: &[u8; Self::LENGTH]) -> Result<Self, ParseError> {
507 let mut offset = 0;
508
509 let [model, primaries, transfer, flags] = read_bytes(bytes, &mut offset)?;
510 let texel_block_dimensions = read_bytes(bytes, &mut offset)?.map(|dim| NonZeroU8::new(dim + 1).unwrap());
511 let bytes_planes = read_bytes(bytes, &mut offset)?;
512
513 Ok(Self {
514 color_model: ColorModel::new(model),
515 color_primaries: ColorPrimaries::new(primaries),
516 transfer_function: TransferFunction::new(transfer),
517 flags: DataFormatFlags::from_bits_truncate(flags),
518 texel_block_dimensions,
519 bytes_planes,
520 })
521 }
522}
523
524pub struct DfdBlockBasic<'data> {
525 pub header: DfdBlockHeaderBasic,
526 sample_information: &'data [u8],
527}
528
529impl<'data> DfdBlockBasic<'data> {
530 pub fn parse(bytes: &'data [u8]) -> Result<Self, ParseError> {
531 let header_data = bytes
532 .get(0..DfdBlockHeaderBasic::LENGTH)
533 .ok_or(ParseError::UnexpectedEnd)?
534 .try_into()
535 .unwrap();
536 let header = DfdBlockHeaderBasic::from_bytes(header_data)?;
537
538 Ok(Self {
539 header,
540 sample_information: &bytes[DfdBlockHeaderBasic::LENGTH..],
541 })
542 }
543
544 pub fn sample_information(&self) -> impl Iterator<Item = SampleInformation> + 'data {
545 SampleInformationIterator {
546 data: self.sample_information,
547 }
548 }
549}
550
551struct SampleInformationIterator<'data> {
552 data: &'data [u8],
553}
554
555impl Iterator for SampleInformationIterator<'_> {
556 type Item = SampleInformation;
557
558 fn next(&mut self) -> Option<Self::Item> {
559 let bytes = self.data.get(0..SampleInformation::LENGTH)?.try_into().unwrap();
560 SampleInformation::from_bytes(&bytes).map_or(None, |sample_information| {
561 self.data = &self.data[SampleInformation::LENGTH..];
562 Some(sample_information)
563 })
564 }
565}
566
567#[derive(Debug, Copy, Clone, PartialEq, Eq)]
568pub struct SampleInformation {
569 pub bit_offset: u16, pub bit_length: NonZeroU8, pub channel_type: u8, pub channel_type_qualifiers: ChannelTypeQualifiers, pub sample_positions: [u8; 4], pub lower: u32, pub upper: u32, }
577
578impl SampleInformation {
579 pub const LENGTH: usize = 16;
580
581 pub fn as_bytes(&self) -> [u8; Self::LENGTH] {
582 let mut bytes = [0u8; Self::LENGTH];
583
584 let channel_info = self.channel_type | (self.channel_type_qualifiers.bits() << 4);
585
586 bytes[0..2].copy_from_slice(&self.bit_offset.to_le_bytes());
587 bytes[2] = self.bit_length.get() - 1;
588 bytes[3] = channel_info;
589 bytes[4..8].copy_from_slice(&self.sample_positions);
590 bytes[8..12].copy_from_slice(&self.lower.to_le_bytes());
591 bytes[12..16].copy_from_slice(&self.upper.to_le_bytes());
592
593 bytes
594 }
595
596 pub fn from_bytes(bytes: &[u8; Self::LENGTH]) -> Result<Self, ParseError> {
597 let mut offset = 0;
598
599 let v = bytes_to_u32(bytes, &mut offset)?;
600 let bit_offset = shift_and_mask_lower(0, 16, v) as u16;
601 let bit_length = (shift_and_mask_lower(16, 8, v) as u8)
602 .checked_add(1)
603 .and_then(NonZeroU8::new)
604 .ok_or(ParseError::InvalidSampleBitLength)?;
605 let channel_type = shift_and_mask_lower(24, 4, v) as u8;
606 let channel_type_qualifiers = ChannelTypeQualifiers::from_bits_truncate(shift_and_mask_lower(28, 4, v) as u8);
607
608 let sample_positions = read_bytes(bytes, &mut offset)?;
609 let lower = bytes_to_u32(bytes, &mut offset)?;
610 let upper = bytes_to_u32(bytes, &mut offset)?;
611
612 Ok(Self {
613 bit_offset,
614 bit_length,
615 channel_type,
616 channel_type_qualifiers,
617 sample_positions,
618 lower,
619 upper,
620 })
621 }
622}
623
624fn read_bytes<const N: usize>(bytes: &[u8], offset: &mut usize) -> Result<[u8; N], ParseError> {
625 let v = bytes
626 .get(*offset..*offset + N)
627 .ok_or(ParseError::UnexpectedEnd)?
628 .try_into()
629 .unwrap();
630 *offset += N;
631 Ok(v)
632}
633
634fn read_u16(bytes: &[u8], offset: &mut usize) -> Result<u16, ParseError> {
635 let v = u16::from_le_bytes(read_bytes(bytes, offset)?);
636 Ok(v)
637}
638
639fn bytes_to_u32(bytes: &[u8], offset: &mut usize) -> Result<u32, ParseError> {
640 let v = u32::from_le_bytes(
641 bytes
642 .get(*offset..*offset + 4)
643 .ok_or(ParseError::UnexpectedEnd)?
644 .try_into()
645 .unwrap(),
646 );
647 *offset += 4;
648 Ok(v)
649}
650
651fn shift_and_mask_lower(shift: u32, mask: u32, value: u32) -> u32 {
652 (value >> shift) & ((1 << mask) - 1)
653}
654
655#[cfg(test)]
656mod test {
657 use super::*;
658
659 fn to_nonzero<const N: usize>(input: [u8; N]) -> [NonZeroU8; N] {
660 input.map(|n| NonZeroU8::new(n).unwrap())
661 }
662
663 #[test]
664 fn basic_dfd_header_roundtrip() {
665 let header = DfdBlockHeaderBasic {
666 color_model: Some(ColorModel::LabSDA),
667 color_primaries: Some(ColorPrimaries::ACES),
668 transfer_function: Some(TransferFunction::ITU),
669 flags: DataFormatFlags::STRAIGHT_ALPHA,
670 texel_block_dimensions: to_nonzero([1, 2, 3, 4]),
671 bytes_planes: [5, 6, 7, 8, 9, 10, 11, 12],
672 };
673
674 let bytes = header.as_bytes();
675 let decoded = DfdBlockHeaderBasic::from_bytes(&bytes).unwrap();
676 assert_eq!(header, decoded);
677 }
678
679 #[test]
680 fn sample_information_roundtrip() {
681 let info = SampleInformation {
682 bit_offset: 234,
683 bit_length: NonZeroU8::new(123).unwrap(),
684 channel_type: 2,
685 channel_type_qualifiers: ChannelTypeQualifiers::LINEAR,
686 sample_positions: [1, 2, 3, 4],
687 lower: 1234,
688 upper: 4567,
689 };
690
691 let bytes = info.as_bytes();
692 let decoded = SampleInformation::from_bytes(&bytes).unwrap();
693
694 assert_eq!(info, decoded);
695 }
696
697 #[test]
698 fn sample_info_invalid_bit_length() {
699 let bytes = &[
700 0u8, 0, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, ];
707
708 assert!(matches!(
709 SampleInformation::from_bytes(bytes),
710 Err(ParseError::InvalidSampleBitLength)
711 ));
712 }
713
714 #[test]
715 #[allow(clippy::octal_escapes)]
716 fn test_malformed_key_value_data_handling() {
717 let data = [
718 &0_u32.to_le_bytes()[..],
719 &7_u32.to_le_bytes()[..],
721 b"xyz\0123 ",
722 &11_u32.to_le_bytes()[..],
724 b"abcdefghi!! ",
725 &7_u32.to_le_bytes()[..],
727 b"abc\0987",
728 &1000_u32.to_le_bytes()[..],
729 &[1; 1000],
730 &u32::MAX.to_le_bytes()[..],
731 ];
732
733 let mut iterator = KeyValueDataIterator { data: &data.concat() };
734
735 assert_eq!(iterator.next(), Some(("xyz", &b"123"[..])));
736 assert_eq!(iterator.next(), Some(("abc", &b"987"[..])));
737 assert_eq!(iterator.next(), None);
738 }
739}