1extern crate alloc;
6
7use alloc::vec::Vec;
8use core::fmt;
9
10use crate::{MAX_J2K_IMAGE_DIMENSION, MAX_J2K_SPEC_COMPONENTS, MAX_J2K_TILE_COUNT};
11
12const MARKER_SOC: u8 = 0x4F;
13const MARKER_CAP: u8 = 0x50;
14const MARKER_SIZ: u8 = 0x51;
15const MARKER_COD: u8 = 0x52;
16const MARKER_SOT: u8 = 0x90;
17const MARKER_SOD: u8 = 0x93;
18const MARKER_EOC: u8 = 0xD9;
19
20#[derive(Debug, Clone, PartialEq, Eq)]
22pub struct J2kCodestreamHeaderMetadata {
23 pub dimensions: (u32, u32),
25 pub components: u16,
27 pub bit_depth: u8,
29 pub tile_size: (u32, u32),
31 pub tile_count: (u32, u32),
33 pub component_info: Vec<J2kCodestreamComponentHeader>,
35 pub resolution_levels: u8,
37 pub has_mct: bool,
39 pub reversible: bool,
41 pub high_throughput: bool,
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
47pub struct J2kCodestreamComponentHeader {
48 pub bit_depth: u8,
50 pub signed: bool,
52 pub x_rsiz: u8,
54 pub y_rsiz: u8,
56}
57
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
60#[non_exhaustive]
61pub enum J2kCodestreamHeaderError {
62 TooShort {
64 need: usize,
66 have: usize,
68 },
69 TruncatedAt {
71 offset: usize,
73 segment: &'static str,
75 },
76 InvalidMarker {
78 offset: usize,
80 marker: u8,
82 },
83 MissingRequiredMarker {
85 marker: &'static str,
87 },
88 InvalidSegment {
90 offset: usize,
92 what: &'static str,
94 },
95 InvalidSiz {
97 what: &'static str,
99 },
100 InvalidCod {
102 what: &'static str,
104 },
105 Unsupported {
107 what: &'static str,
109 },
110 HostAllocationFailed {
112 bytes: usize,
114 },
115}
116
117impl fmt::Display for J2kCodestreamHeaderError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 match self {
120 Self::TooShort { need, have } => {
121 write!(f, "input too short: need {need} bytes, have {have}")
122 }
123 Self::TruncatedAt { offset, segment } => {
124 write!(f, "truncated {segment} at offset {offset}")
125 }
126 Self::InvalidMarker { offset, marker } => {
127 write!(
128 f,
129 "invalid codestream marker FF{marker:02X} at offset {offset}"
130 )
131 }
132 Self::MissingRequiredMarker { marker } => {
133 write!(f, "missing required codestream marker {marker}")
134 }
135 Self::InvalidSegment { what, .. } => write!(f, "invalid marker segment: {what}"),
136 Self::InvalidSiz { what } => write!(f, "invalid SIZ segment: {what}"),
137 Self::InvalidCod { what } => write!(f, "invalid COD segment: {what}"),
138 Self::Unsupported { what } => write!(f, "unsupported codestream header: {what}"),
139 Self::HostAllocationFailed { bytes } => {
140 write!(f, "codestream header allocation failed for {bytes} bytes")
141 }
142 }
143 }
144}
145
146impl core::error::Error for J2kCodestreamHeaderError {}
147
148pub fn inspect_j2k_codestream_header(
158 input: &[u8],
159) -> Result<J2kCodestreamHeaderMetadata, J2kCodestreamHeaderError> {
160 if input.len() < 2 {
161 return Err(J2kCodestreamHeaderError::TooShort {
162 need: 2,
163 have: input.len(),
164 });
165 }
166 if !looks_like_j2k_codestream(input) {
167 return Err(J2kCodestreamHeaderError::InvalidMarker {
168 offset: 0,
169 marker: input[1],
170 });
171 }
172
173 let mut offset = 2usize;
174 let mut siz = None;
175 let mut cod = None;
176 let mut high_throughput_cap = false;
177 let mut terminated = false;
178
179 while offset < input.len() {
180 let marker = read_marker(input, &mut offset)?;
181 match marker {
182 MARKER_SOT | MARKER_SOD | MARKER_EOC => {
183 terminated = true;
184 break;
185 }
186 MARKER_SIZ => {
187 let payload = read_segment_payload(input, &mut offset, "SIZ")?;
188 siz = Some(parse_siz(payload)?);
189 }
190 MARKER_COD => {
191 let payload = read_segment_payload(input, &mut offset, "COD")?;
192 cod = Some(parse_cod(payload)?);
193 }
194 MARKER_CAP => {
195 let _ = read_segment_payload(input, &mut offset, "CAP")?;
196 high_throughput_cap = true;
197 }
198 _ => {
199 let _ = read_segment_payload(input, &mut offset, "segment")?;
200 }
201 }
202 }
203
204 if !terminated {
205 return Err(J2kCodestreamHeaderError::TruncatedAt {
206 offset,
207 segment: "main header terminator",
208 });
209 }
210
211 let siz = siz.ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "SIZ" })?;
212 let cod = cod
213 .ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "COD" })?
214 .with_high_throughput_cap(high_throughput_cap);
215
216 Ok(J2kCodestreamHeaderMetadata {
217 dimensions: siz.dimensions,
218 components: siz.components,
219 bit_depth: siz.bit_depth,
220 tile_size: siz.tile_size,
221 tile_count: siz.tile_count,
222 component_info: siz.component_info,
223 resolution_levels: cod.resolution_levels,
224 has_mct: cod.has_mct,
225 reversible: cod.reversible,
226 high_throughput: cod.high_throughput,
227 })
228}
229
230#[must_use]
232pub fn looks_like_j2k_codestream(input: &[u8]) -> bool {
233 input.len() >= 2 && input[0] == 0xFF && input[1] == MARKER_SOC
234}
235
236#[derive(Debug, Clone)]
237struct ParsedSiz {
238 dimensions: (u32, u32),
239 components: u16,
240 bit_depth: u8,
241 tile_size: (u32, u32),
242 tile_count: (u32, u32),
243 component_info: Vec<J2kCodestreamComponentHeader>,
244}
245
246#[derive(Debug, Clone, Copy)]
247struct ParsedCod {
248 resolution_levels: u8,
249 has_mct: bool,
250 reversible: bool,
251 high_throughput: bool,
252}
253
254impl ParsedCod {
255 const fn with_high_throughput_cap(mut self, high_throughput_cap: bool) -> Self {
256 self.high_throughput |= high_throughput_cap;
257 self
258 }
259}
260
261fn read_marker(input: &[u8], offset: &mut usize) -> Result<u8, J2kCodestreamHeaderError> {
262 if *offset + 2 > input.len() {
263 return Err(J2kCodestreamHeaderError::TruncatedAt {
264 offset: *offset,
265 segment: "marker",
266 });
267 }
268 if input[*offset] != 0xFF {
269 return Err(J2kCodestreamHeaderError::InvalidMarker {
270 offset: *offset,
271 marker: input[*offset],
272 });
273 }
274 let marker = input[*offset + 1];
275 *offset += 2;
276 Ok(marker)
277}
278
279fn read_segment_payload<'a>(
280 input: &'a [u8],
281 offset: &mut usize,
282 segment: &'static str,
283) -> Result<&'a [u8], J2kCodestreamHeaderError> {
284 if *offset + 2 > input.len() {
285 return Err(J2kCodestreamHeaderError::TruncatedAt {
286 offset: *offset,
287 segment,
288 });
289 }
290 let length = u16::from_be_bytes([input[*offset], input[*offset + 1]]) as usize;
291 if length < 2 {
292 return Err(J2kCodestreamHeaderError::InvalidSegment {
293 offset: *offset,
294 what: "segment length smaller than header",
295 });
296 }
297 let start = *offset + 2;
298 let end = *offset + length;
299 if end > input.len() {
300 return Err(J2kCodestreamHeaderError::TruncatedAt {
301 offset: *offset,
302 segment,
303 });
304 }
305 *offset = end;
306 Ok(&input[start..end])
307}
308
309struct SizGeometry {
310 x_size: u32,
311 y_size: u32,
312 x_origin: u32,
313 y_origin: u32,
314 tile_width: u32,
315 tile_height: u32,
316 tile_x_origin: u32,
317 tile_y_origin: u32,
318}
319
320type SizDimensionsAndTileCount = ((u32, u32), (u32, u32));
321
322fn parse_siz(payload: &[u8]) -> Result<ParsedSiz, J2kCodestreamHeaderError> {
323 if payload.len() < 36 {
324 return Err(J2kCodestreamHeaderError::InvalidSiz {
325 what: "payload shorter than fixed SIZ header",
326 });
327 }
328 let geometry = SizGeometry {
329 x_size: read_u32(payload, 2),
330 y_size: read_u32(payload, 6),
331 x_origin: read_u32(payload, 10),
332 y_origin: read_u32(payload, 14),
333 tile_width: read_u32(payload, 18),
334 tile_height: read_u32(payload, 22),
335 tile_x_origin: read_u32(payload, 26),
336 tile_y_origin: read_u32(payload, 30),
337 };
338 let component_count = read_u16(payload, 34);
339
340 let required_len = usize::from(component_count)
341 .checked_mul(3)
342 .and_then(|component_bytes| 36usize.checked_add(component_bytes))
343 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
344 what: "component descriptor length overflows",
345 })?;
346 if payload.len() < required_len {
347 return Err(J2kCodestreamHeaderError::InvalidSiz {
348 what: "component descriptors truncated",
349 });
350 }
351 let ((width, height), (tiles_x, tiles_y)) = validate_siz_geometry(&geometry, component_count)?;
352
353 let mut bit_depth = 0u8;
354 let component_len = usize::from(component_count);
355 let component_bytes = component_len
356 .checked_mul(core::mem::size_of::<J2kCodestreamComponentHeader>())
357 .ok_or(J2kCodestreamHeaderError::HostAllocationFailed { bytes: usize::MAX })?;
358 let mut component_info = Vec::new();
359 component_info
360 .try_reserve_exact(component_len)
361 .map_err(|_| J2kCodestreamHeaderError::HostAllocationFailed {
362 bytes: component_bytes,
363 })?;
364 for idx in 0..component_len {
365 let ssiz = payload[36 + idx * 3];
366 let precision = (ssiz & 0x7F) + 1;
367 let x_rsiz = payload[36 + idx * 3 + 1];
368 let y_rsiz = payload[36 + idx * 3 + 2];
369 if x_rsiz == 0 || y_rsiz == 0 {
370 return Err(J2kCodestreamHeaderError::InvalidSiz {
371 what: "component sampling factors must be non-zero",
372 });
373 }
374 bit_depth = bit_depth.max(precision);
375 component_info.push(J2kCodestreamComponentHeader {
376 bit_depth: precision,
377 signed: ssiz & 0x80 != 0,
378 x_rsiz,
379 y_rsiz,
380 });
381 }
382
383 Ok(ParsedSiz {
384 dimensions: (width, height),
385 components: component_count,
386 bit_depth,
387 tile_size: (geometry.tile_width, geometry.tile_height),
388 tile_count: (tiles_x, tiles_y),
389 component_info,
390 })
391}
392
393fn validate_siz_geometry(
394 geometry: &SizGeometry,
395 component_count: u16,
396) -> Result<SizDimensionsAndTileCount, J2kCodestreamHeaderError> {
397 if component_count == 0 {
398 return Err(J2kCodestreamHeaderError::InvalidSiz {
399 what: "component count must be non-zero",
400 });
401 }
402 if component_count > MAX_J2K_SPEC_COMPONENTS {
403 return Err(J2kCodestreamHeaderError::InvalidSiz {
404 what: "component count exceeds JPEG 2000 limit",
405 });
406 }
407 if geometry.x_size <= geometry.x_origin || geometry.y_size <= geometry.y_origin {
408 return Err(J2kCodestreamHeaderError::InvalidSiz {
409 what: "image origin must be smaller than image size",
410 });
411 }
412 if geometry.tile_width == 0 || geometry.tile_height == 0 {
413 return Err(J2kCodestreamHeaderError::InvalidSiz {
414 what: "tile size must be non-zero",
415 });
416 }
417 if geometry.tile_x_origin >= geometry.x_size || geometry.tile_y_origin >= geometry.y_size {
418 return Err(J2kCodestreamHeaderError::InvalidSiz {
419 what: "tile origin must be within image bounds",
420 });
421 }
422 if geometry.tile_x_origin > geometry.x_origin || geometry.tile_y_origin > geometry.y_origin {
423 return Err(J2kCodestreamHeaderError::InvalidSiz {
424 what: "tile origin must not exceed image origin",
425 });
426 }
427 if geometry
428 .tile_x_origin
429 .checked_add(geometry.tile_width)
430 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
431 what: "tile extent overflows",
432 })?
433 <= geometry.x_origin
434 || geometry
435 .tile_y_origin
436 .checked_add(geometry.tile_height)
437 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
438 what: "tile extent overflows",
439 })?
440 <= geometry.y_origin
441 {
442 return Err(J2kCodestreamHeaderError::InvalidSiz {
443 what: "first tile must overlap image area",
444 });
445 }
446
447 let width = geometry.x_size - geometry.x_origin;
448 let height = geometry.y_size - geometry.y_origin;
449 if width > MAX_J2K_IMAGE_DIMENSION || height > MAX_J2K_IMAGE_DIMENSION {
450 return Err(J2kCodestreamHeaderError::InvalidSiz {
451 what: "image dimensions exceed JPEG 2000 inspect limit",
452 });
453 }
454 let tiles_x = (geometry.x_size - geometry.tile_x_origin).div_ceil(geometry.tile_width);
455 let tiles_y = (geometry.y_size - geometry.tile_y_origin).div_ceil(geometry.tile_height);
456 let tile_count = u64::from(tiles_x) * u64::from(tiles_y);
457 if tile_count > MAX_J2K_TILE_COUNT {
458 return Err(J2kCodestreamHeaderError::InvalidSiz {
459 what: "image has too many tiles",
460 });
461 }
462 Ok(((width, height), (tiles_x, tiles_y)))
463}
464
465fn parse_cod(payload: &[u8]) -> Result<ParsedCod, J2kCodestreamHeaderError> {
466 if payload.len() < 10 {
467 return Err(J2kCodestreamHeaderError::InvalidCod {
468 what: "payload shorter than fixed COD header",
469 });
470 }
471 Ok(ParsedCod {
472 resolution_levels: payload[5].saturating_add(1),
473 has_mct: payload[4] != 0,
474 reversible: payload[9] == 1,
475 high_throughput: payload[8] & 0x40 != 0,
476 })
477}
478
479fn read_u16(bytes: &[u8], offset: usize) -> u16 {
480 u16::from_be_bytes([bytes[offset], bytes[offset + 1]])
481}
482
483fn read_u32(bytes: &[u8], offset: usize) -> u32 {
484 u32::from_be_bytes([
485 bytes[offset],
486 bytes[offset + 1],
487 bytes[offset + 2],
488 bytes[offset + 3],
489 ])
490}
491
492#[cfg(test)]
493mod tests {
494 use super::{inspect_j2k_codestream_header, J2kCodestreamHeaderError};
495 use alloc::{vec, vec::Vec};
496
497 #[test]
498 fn inspect_j2k_codestream_header_accepts_minimal_main_header() {
499 let header = inspect_j2k_codestream_header(&minimal_codestream()).expect("header");
500
501 assert_eq!(header.dimensions, (128, 64));
502 assert_eq!(header.components, 3);
503 assert_eq!(header.bit_depth, 8);
504 assert_eq!(header.tile_size, (64, 64));
505 assert_eq!(header.tile_count, (2, 1));
506 assert_eq!(header.resolution_levels, 6);
507 assert!(header.reversible);
508 }
509
510 #[test]
511 fn inspect_rejects_zero_component_sampling() {
512 let mut bytes = minimal_codestream();
513 rewrite_component_sampling(&mut bytes, 0, 0, 1);
514
515 let err = inspect_j2k_codestream_header(&bytes).expect_err("zero sampling must reject");
516
517 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
518 }
519
520 #[test]
521 fn inspect_rejects_oversized_dimensions() {
522 let mut bytes = minimal_codestream();
523 rewrite_siz_u32(&mut bytes, 2, 60_001);
524
525 let err = inspect_j2k_codestream_header(&bytes).expect_err("oversized width must reject");
526
527 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
528 }
529
530 #[test]
531 fn inspect_rejects_tile_origin_after_image_origin() {
532 let mut bytes = minimal_codestream();
533 rewrite_siz_u32(&mut bytes, 26, 1);
534
535 let err = inspect_j2k_codestream_header(&bytes).expect_err("bad tile origin must reject");
536
537 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
538 }
539
540 #[test]
541 fn inspect_rejects_tile_extent_overflow() {
542 let mut bytes = minimal_codestream();
543 rewrite_siz_u32(&mut bytes, 2, u32::MAX);
544 rewrite_siz_u32(&mut bytes, 10, u32::MAX - 1);
545 rewrite_siz_u32(&mut bytes, 18, 10);
546 rewrite_siz_u32(&mut bytes, 26, u32::MAX - 2);
547
548 let err = inspect_j2k_codestream_header(&bytes).expect_err("overflow must reject");
549
550 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
551 }
552
553 #[test]
554 fn inspect_rejects_excessive_tile_count() {
555 let mut bytes = minimal_codestream();
556 rewrite_siz_u32(&mut bytes, 2, 257);
557 rewrite_siz_u32(&mut bytes, 6, 257);
558 rewrite_siz_u32(&mut bytes, 18, 1);
559 rewrite_siz_u32(&mut bytes, 22, 1);
560
561 let err = inspect_j2k_codestream_header(&bytes).expect_err("tile count must reject");
562
563 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
564 }
565
566 #[test]
567 fn inspect_accepts_legal_38_bit_component_metadata() {
568 let mut bytes = minimal_codestream();
569 rewrite_component_descriptor(&mut bytes, 0, 0x25);
570 rewrite_component_descriptor(&mut bytes, 1, 0x80 | 0x25);
571
572 let header = inspect_j2k_codestream_header(&bytes).expect("legal 38-bit SIZ inspect");
573
574 assert_eq!(header.bit_depth, 38);
575 assert_eq!(header.component_info[0].bit_depth, 38);
576 assert!(!header.component_info[0].signed);
577 assert_eq!(header.component_info[1].bit_depth, 38);
578 assert!(header.component_info[1].signed);
579 }
580
581 fn minimal_codestream() -> Vec<u8> {
582 let mut bytes = vec![0xFF, 0x4F];
583 let mut siz = Vec::new();
584 push_u16(&mut siz, 0);
585 push_u32(&mut siz, 128);
586 push_u32(&mut siz, 64);
587 push_u32(&mut siz, 0);
588 push_u32(&mut siz, 0);
589 push_u32(&mut siz, 64);
590 push_u32(&mut siz, 64);
591 push_u32(&mut siz, 0);
592 push_u32(&mut siz, 0);
593 push_u16(&mut siz, 3);
594 for _ in 0..3 {
595 siz.extend_from_slice(&[0x07, 0x01, 0x01]);
596 }
597 bytes.extend_from_slice(&[0xFF, 0x51]);
598 push_u16(
599 &mut bytes,
600 u16::try_from(siz.len() + 2).expect("test SIZ segment length fits u16"),
601 );
602 bytes.extend_from_slice(&siz);
603
604 let cod = [0x00, 0x00, 0x00, 0x01, 0x01, 0x05, 0x04, 0x04, 0x00, 0x01];
605 bytes.extend_from_slice(&[0xFF, 0x52]);
606 push_u16(
607 &mut bytes,
608 u16::try_from(cod.len() + 2).expect("test COD segment length fits u16"),
609 );
610 bytes.extend_from_slice(&cod);
611 bytes.extend_from_slice(&[0xFF, 0x90, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
612 bytes
613 }
614
615 fn push_u16(out: &mut Vec<u8>, value: u16) {
616 out.extend_from_slice(&value.to_be_bytes());
617 }
618
619 fn push_u32(out: &mut Vec<u8>, value: u32) {
620 out.extend_from_slice(&value.to_be_bytes());
621 }
622
623 fn rewrite_siz_u32(bytes: &mut [u8], payload_offset: usize, value: u32) {
624 let siz = bytes
625 .windows(2)
626 .position(|marker| marker == [0xFF, 0x51])
627 .expect("SIZ marker");
628 let offset = siz + 4 + payload_offset;
629 bytes[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
630 }
631
632 fn rewrite_component_sampling(bytes: &mut [u8], component: usize, x_rsiz: u8, y_rsiz: u8) {
633 let siz = bytes
634 .windows(2)
635 .position(|marker| marker == [0xFF, 0x51])
636 .expect("SIZ marker");
637 let component_offset = siz + 40 + component * 3;
638 bytes[component_offset + 1] = x_rsiz;
639 bytes[component_offset + 2] = y_rsiz;
640 }
641
642 fn rewrite_component_descriptor(bytes: &mut [u8], component: usize, descriptor: u8) {
643 let siz = bytes
644 .windows(2)
645 .position(|marker| marker == [0xFF, 0x51])
646 .expect("SIZ marker");
647 bytes[siz + 40 + component * 3] = descriptor;
648 }
649}