ktx2/
lib.rs

1//! Parser for the [ktx2](https://github.khronos.org/KTX-Specification/ktxspec.v2.html) texture container format.
2//!
3//! ## Features
4//! - [x] Async reading
5//! - [x] Parsing
6//! - [x] Validating
7//! - [x] [Data format description](https://github.khronos.org/KTX-Specification/ktxspec.v2.html#_data_format_descriptor)
8//! - [x] [Key/value data](https://github.khronos.org/KTX-Specification/ktxspec.v2.html#_keyvalue_data)
9//!
10//! ## Example
11//! ```rust
12//! // Crate instance of reader. This validates the header
13//! # let file = include_bytes!("../data/test_tex.ktx2");
14//! let mut reader = ktx2::Reader::new(file).expect("Can't create reader"); // Crate instance of reader.
15//!
16//! // Get general texture information.
17//! let header = reader.header();
18//!
19//! // Read iterator over slices of each mipmap level.
20//! let levels = reader.levels().collect::<Vec<_>>();
21//! # let _ = (header, levels);
22//! ```
23//!
24//! ## MSRV
25//!
26//! The minimum supported Rust version is 1.56. MSRV bumps are treated as breaking changes.
27
28#![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
43/// Decodes KTX2 texture data
44pub struct Reader<Data: AsRef<[u8]>> {
45    input: Data,
46    header: Header,
47}
48
49impl<Data: AsRef<[u8]>> Reader<Data> {
50    /// Decode KTX2 data from `input`
51    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        // Check DFD bounds
59        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        // Check SGD bounds
74        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        // Check KVD bounds
85        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()?; // Check index integrity
97
98        // Check level data bounds
99        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    /// Access underlying raw bytes
135    pub fn data(&self) -> &[u8] {
136        self.input.as_ref()
137    }
138
139    /// Container-level metadata
140    pub fn header(&self) -> Header {
141        self.header
142    }
143
144    /// Iterator over the texture's mip levels
145    pub fn levels(&self) -> impl ExactSizeIterator<Item = Level> + '_ {
146        self.level_index().unwrap().map(move |level| Level {
147            // Bounds-checking previously performed in `new`
148            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        // Bounds-checking previously performed in `new`
157        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        // Bounds-checking previously performed in `new`
165        let end = (header.index.dfd_byte_offset + header.index.dfd_byte_length) as usize;
166        DfdBlockIterator {
167            // start + 4 to skip the data format descriptors total length
168            data: &self.input.as_ref()[start + 4..end],
169        }
170    }
171
172    /// Iterator over the key-value pairs
173    pub fn key_value_data(&self) -> KeyValueDataIterator {
174        let header = self.header();
175
176        let start = header.index.kvd_byte_offset as usize;
177        // Bounds-checking previously performed in `new`
178        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
206/// An iterator that parses the key-value pairs in the KTX2 file.
207pub struct KeyValueDataIterator<'data> {
208    data: &'data [u8],
209}
210
211impl<'data> KeyValueDataIterator<'data> {
212    /// Create a new iterator from the key-value data section of the KTX2 file.
213    ///
214    /// From the start of the file, this is a slice between [`Index::kvd_byte_offset`]
215    /// and [`Index::kvd_byte_offset`] + [`Index::kvd_byte_length`].
216    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            // Ensure that we're 4-byte aligned
237            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            // The key is terminated with a NUL character.
247            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
267/// Identifier, expected in start of input texture data.
268const KTX2_MAGIC: [u8; 12] = [0xAB, 0x4B, 0x54, 0x58, 0x20, 0x32, 0x30, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A];
269
270/// Result of parsing data operation.
271type ParseResult<T> = Result<T, ParseError>;
272
273/// Container-level metadata
274#[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/// An index giving the byte offsets from the start of the file and byte sizes of the various sections of the KTX2 file.
289#[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,       //: 17;
421    pub descriptor_type: u32, //: 15;
422    pub version_number: u16,  //: 16;
423}
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    /// None means Unspecified
474    pub color_model: Option<ColorModel>, //: 8;
475    /// None means Unspecified
476    pub color_primaries: Option<ColorPrimaries>, //: 8;
477    /// None means Unspecified
478    pub transfer_function: Option<TransferFunction>, //: 8;
479    pub flags: DataFormatFlags,                 //: 8;
480    pub texel_block_dimensions: [NonZeroU8; 4], //: 8 x 4;
481    pub bytes_planes: [u8; 8],                  //: 8 x 8;
482}
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,                                //: 16;
570    pub bit_length: NonZeroU8,                          //: 8;
571    pub channel_type: u8,                               //: 4;
572    pub channel_type_qualifiers: ChannelTypeQualifiers, //: 4;
573    pub sample_positions: [u8; 4],                      //: 8 x 4;
574    pub lower: u32,                                     //: 32;
575    pub upper: u32,                                     //: 32;
576}
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,   // bit_offset
701            255, // bit_length
702            1,   // channel_type | channel_type_qualifiers
703            0, 0, 0, 0, // sample_positions
704            0, 0, 0, 0, // lower
705            255, 255, 255, 255, // upper
706        ];
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            // Regular key-value pair
720            &7_u32.to_le_bytes()[..],
721            b"xyz\0123 ",
722            // Malformed key-value pair with missing NUL byte
723            &11_u32.to_le_bytes()[..],
724            b"abcdefghi!! ",
725            // Regular key-value pair again
726            &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}