Skip to main content

zenjxl_decoder/api/
signature.rs

1// Copyright (c) the JPEG XL Project Authors. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6use crate::{
7    api::ProcessingResult,
8    error::{Error, Result},
9};
10
11/// The magic bytes for a bare JPEG XL codestream.
12pub(crate) const CODESTREAM_SIGNATURE: [u8; 2] = [0xff, 0x0a];
13/// The magic bytes for a file using the JPEG XL container format.
14pub(crate) const CONTAINER_SIGNATURE: [u8; 12] =
15    [0, 0, 0, 0xc, b'J', b'X', b'L', b' ', 0xd, 0xa, 0x87, 0xa];
16
17#[derive(Debug, PartialEq)]
18pub enum JxlSignatureType {
19    Codestream,
20    Container,
21}
22
23impl JxlSignatureType {
24    pub(crate) fn signature(&self) -> &[u8] {
25        match self {
26            JxlSignatureType::Container => &CONTAINER_SIGNATURE,
27            JxlSignatureType::Codestream => &CODESTREAM_SIGNATURE,
28        }
29    }
30}
31
32pub(crate) fn check_signature_internal(file_prefix: &[u8]) -> Result<Option<JxlSignatureType>> {
33    let prefix_len = file_prefix.len();
34
35    for st in [JxlSignatureType::Codestream, JxlSignatureType::Container] {
36        let len = st.signature().len();
37        // Determine the number of bytes to compare (the length of the shorter slice)
38        let len_to_check = prefix_len.min(len);
39
40        if file_prefix[..len_to_check] == st.signature()[..len_to_check] {
41            // The prefix is a valid start. Now, is it complete?
42            return if prefix_len >= len {
43                Ok(Some(st))
44            } else {
45                Err(Error::OutOfBounds(len - prefix_len))
46            };
47        }
48    }
49    // The prefix doesn't match the start of any known signature.
50    Ok(None)
51}
52
53/// Checks if the given buffer starts with a valid JPEG XL signature.
54///
55/// # Returns
56///
57/// A `ProcessingResult` which is:
58/// - `Complete(Some(_))` if a full container or codestream signature is found.
59/// - `Complete(None)` if the prefix is definitively not a JXL signature.
60/// - `NeedsMoreInput` if the prefix matches a signature but is too short.
61pub fn check_signature(file_prefix: &[u8]) -> ProcessingResult<Option<JxlSignatureType>, ()> {
62    ProcessingResult::new(check_signature_internal(file_prefix)).unwrap()
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::api::{
68        CODESTREAM_SIGNATURE, CONTAINER_SIGNATURE, JxlSignatureType, ProcessingResult,
69        check_signature,
70    };
71
72    macro_rules! signature_test {
73        ($test_name:ident, $bytes:expr, Complete(Some($expected_type:expr))) => {
74            #[test]
75            fn $test_name() {
76                let result = check_signature($bytes);
77                match result {
78                    ProcessingResult::Complete { result } => {
79                        assert_eq!(result, Some($expected_type));
80                    }
81                    _ => panic!("Expected Complete(Some(_)), but got {:?}", result),
82                }
83            }
84        };
85        ($test_name:ident, $bytes:expr, Complete(None)) => {
86            #[test]
87            fn $test_name() {
88                let result = check_signature($bytes);
89                match result {
90                    ProcessingResult::Complete { result } => {
91                        assert_eq!(result, None);
92                    }
93                    _ => panic!("Expected Complete(None), but got {:?}", result),
94                }
95            }
96        };
97        ($test_name:ident, $bytes:expr, NeedsMoreInput($expected_hint:expr)) => {
98            #[test]
99            fn $test_name() {
100                let result = check_signature($bytes);
101                match result {
102                    ProcessingResult::NeedsMoreInput { size_hint, .. } => {
103                        assert_eq!(size_hint, $expected_hint);
104                    }
105                    _ => panic!("Expected NeedsMoreInput, but got {:?}", result),
106                }
107            }
108        };
109    }
110
111    signature_test!(
112        full_container_sig,
113        &CONTAINER_SIGNATURE,
114        Complete(Some(JxlSignatureType::Container))
115    );
116
117    signature_test!(
118        partial_container_sig,
119        &CONTAINER_SIGNATURE[..5],
120        NeedsMoreInput(CONTAINER_SIGNATURE.len() - 5)
121    );
122
123    signature_test!(
124        full_codestream_sig,
125        &CODESTREAM_SIGNATURE,
126        Complete(Some(JxlSignatureType::Codestream))
127    );
128
129    signature_test!(
130        partial_codestream_sig,
131        &CODESTREAM_SIGNATURE[..1],
132        NeedsMoreInput(CODESTREAM_SIGNATURE.len() - 1)
133    );
134
135    signature_test!(
136        empty_prefix,
137        &[],
138        NeedsMoreInput(CODESTREAM_SIGNATURE.len())
139    );
140
141    signature_test!(invalid_sig, &[0x12, 0x34, 0x56, 0x77], Complete(None));
142
143    signature_test!(
144        container_with_extra_data,
145        &{
146            let mut data = CONTAINER_SIGNATURE.to_vec();
147            data.extend_from_slice(&[0x11, 0x22, 0x33]);
148            data
149        },
150        Complete(Some(JxlSignatureType::Container))
151    );
152
153    signature_test!(
154        codestream_with_extra_data,
155        &{
156            let mut data = CODESTREAM_SIGNATURE.to_vec();
157            data.extend_from_slice(&[0x44, 0x55, 0x66]);
158            data
159        },
160        Complete(Some(JxlSignatureType::Codestream))
161    );
162}