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 0x30..=0x3F => {}
199 _ => {
200 let _ = read_segment_payload(input, &mut offset, "segment")?;
201 }
202 }
203 }
204
205 if !terminated {
206 return Err(J2kCodestreamHeaderError::TruncatedAt {
207 offset,
208 segment: "main header terminator",
209 });
210 }
211
212 let siz = siz.ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "SIZ" })?;
213 let cod = cod
214 .ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "COD" })?
215 .with_high_throughput_cap(high_throughput_cap);
216
217 Ok(J2kCodestreamHeaderMetadata {
218 dimensions: siz.dimensions,
219 components: siz.components,
220 bit_depth: siz.bit_depth,
221 tile_size: siz.tile_size,
222 tile_count: siz.tile_count,
223 component_info: siz.component_info,
224 resolution_levels: cod.resolution_levels,
225 has_mct: cod.has_mct,
226 reversible: cod.reversible,
227 high_throughput: cod.high_throughput,
228 })
229}
230
231#[must_use]
233pub fn looks_like_j2k_codestream(input: &[u8]) -> bool {
234 input.len() >= 2 && input[0] == 0xFF && input[1] == MARKER_SOC
235}
236
237#[derive(Debug, Clone)]
238struct ParsedSiz {
239 dimensions: (u32, u32),
240 components: u16,
241 bit_depth: u8,
242 tile_size: (u32, u32),
243 tile_count: (u32, u32),
244 component_info: Vec<J2kCodestreamComponentHeader>,
245}
246
247#[derive(Debug, Clone, Copy)]
248struct ParsedCod {
249 resolution_levels: u8,
250 has_mct: bool,
251 reversible: bool,
252 high_throughput: bool,
253}
254
255impl ParsedCod {
256 const fn with_high_throughput_cap(mut self, high_throughput_cap: bool) -> Self {
257 self.high_throughput |= high_throughput_cap;
258 self
259 }
260}
261
262fn read_marker(input: &[u8], offset: &mut usize) -> Result<u8, J2kCodestreamHeaderError> {
263 if *offset + 2 > input.len() {
264 return Err(J2kCodestreamHeaderError::TruncatedAt {
265 offset: *offset,
266 segment: "marker",
267 });
268 }
269 if input[*offset] != 0xFF {
270 return Err(J2kCodestreamHeaderError::InvalidMarker {
271 offset: *offset,
272 marker: input[*offset],
273 });
274 }
275 let marker = input[*offset + 1];
276 *offset += 2;
277 Ok(marker)
278}
279
280fn read_segment_payload<'a>(
281 input: &'a [u8],
282 offset: &mut usize,
283 segment: &'static str,
284) -> Result<&'a [u8], J2kCodestreamHeaderError> {
285 if *offset + 2 > input.len() {
286 return Err(J2kCodestreamHeaderError::TruncatedAt {
287 offset: *offset,
288 segment,
289 });
290 }
291 let length = u16::from_be_bytes([input[*offset], input[*offset + 1]]) as usize;
292 if length < 2 {
293 return Err(J2kCodestreamHeaderError::InvalidSegment {
294 offset: *offset,
295 what: "segment length smaller than header",
296 });
297 }
298 let start = *offset + 2;
299 let end = *offset + length;
300 if end > input.len() {
301 return Err(J2kCodestreamHeaderError::TruncatedAt {
302 offset: *offset,
303 segment,
304 });
305 }
306 *offset = end;
307 Ok(&input[start..end])
308}
309
310struct SizGeometry {
311 x_size: u32,
312 y_size: u32,
313 x_origin: u32,
314 y_origin: u32,
315 tile_width: u32,
316 tile_height: u32,
317 tile_x_origin: u32,
318 tile_y_origin: u32,
319}
320
321type SizDimensionsAndTileCount = ((u32, u32), (u32, u32));
322
323fn parse_siz(payload: &[u8]) -> Result<ParsedSiz, J2kCodestreamHeaderError> {
324 if payload.len() < 36 {
325 return Err(J2kCodestreamHeaderError::InvalidSiz {
326 what: "payload shorter than fixed SIZ header",
327 });
328 }
329 let geometry = SizGeometry {
330 x_size: read_u32(payload, 2),
331 y_size: read_u32(payload, 6),
332 x_origin: read_u32(payload, 10),
333 y_origin: read_u32(payload, 14),
334 tile_width: read_u32(payload, 18),
335 tile_height: read_u32(payload, 22),
336 tile_x_origin: read_u32(payload, 26),
337 tile_y_origin: read_u32(payload, 30),
338 };
339 let component_count = read_u16(payload, 34);
340
341 let required_len = usize::from(component_count)
342 .checked_mul(3)
343 .and_then(|component_bytes| 36usize.checked_add(component_bytes))
344 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
345 what: "component descriptor length overflows",
346 })?;
347 if payload.len() < required_len {
348 return Err(J2kCodestreamHeaderError::InvalidSiz {
349 what: "component descriptors truncated",
350 });
351 }
352 let ((width, height), (tiles_x, tiles_y)) = validate_siz_geometry(&geometry, component_count)?;
353
354 let mut bit_depth = 0u8;
355 let component_len = usize::from(component_count);
356 let component_bytes = component_len
357 .checked_mul(core::mem::size_of::<J2kCodestreamComponentHeader>())
358 .ok_or(J2kCodestreamHeaderError::HostAllocationFailed { bytes: usize::MAX })?;
359 let mut component_info = Vec::new();
360 component_info
361 .try_reserve_exact(component_len)
362 .map_err(|_| J2kCodestreamHeaderError::HostAllocationFailed {
363 bytes: component_bytes,
364 })?;
365 for idx in 0..component_len {
366 let ssiz = payload[36 + idx * 3];
367 let precision = (ssiz & 0x7F) + 1;
368 let x_rsiz = payload[36 + idx * 3 + 1];
369 let y_rsiz = payload[36 + idx * 3 + 2];
370 if x_rsiz == 0 || y_rsiz == 0 {
371 return Err(J2kCodestreamHeaderError::InvalidSiz {
372 what: "component sampling factors must be non-zero",
373 });
374 }
375 bit_depth = bit_depth.max(precision);
376 component_info.push(J2kCodestreamComponentHeader {
377 bit_depth: precision,
378 signed: ssiz & 0x80 != 0,
379 x_rsiz,
380 y_rsiz,
381 });
382 }
383
384 Ok(ParsedSiz {
385 dimensions: (width, height),
386 components: component_count,
387 bit_depth,
388 tile_size: (geometry.tile_width, geometry.tile_height),
389 tile_count: (tiles_x, tiles_y),
390 component_info,
391 })
392}
393
394fn validate_siz_geometry(
395 geometry: &SizGeometry,
396 component_count: u16,
397) -> Result<SizDimensionsAndTileCount, J2kCodestreamHeaderError> {
398 if component_count == 0 {
399 return Err(J2kCodestreamHeaderError::InvalidSiz {
400 what: "component count must be non-zero",
401 });
402 }
403 if component_count > MAX_J2K_SPEC_COMPONENTS {
404 return Err(J2kCodestreamHeaderError::InvalidSiz {
405 what: "component count exceeds JPEG 2000 limit",
406 });
407 }
408 if geometry.x_size <= geometry.x_origin || geometry.y_size <= geometry.y_origin {
409 return Err(J2kCodestreamHeaderError::InvalidSiz {
410 what: "image origin must be smaller than image size",
411 });
412 }
413 if geometry.tile_width == 0 || geometry.tile_height == 0 {
414 return Err(J2kCodestreamHeaderError::InvalidSiz {
415 what: "tile size must be non-zero",
416 });
417 }
418 if geometry.tile_x_origin >= geometry.x_size || geometry.tile_y_origin >= geometry.y_size {
419 return Err(J2kCodestreamHeaderError::InvalidSiz {
420 what: "tile origin must be within image bounds",
421 });
422 }
423 if geometry.tile_x_origin > geometry.x_origin || geometry.tile_y_origin > geometry.y_origin {
424 return Err(J2kCodestreamHeaderError::InvalidSiz {
425 what: "tile origin must not exceed image origin",
426 });
427 }
428 if geometry
429 .tile_x_origin
430 .checked_add(geometry.tile_width)
431 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
432 what: "tile extent overflows",
433 })?
434 <= geometry.x_origin
435 || geometry
436 .tile_y_origin
437 .checked_add(geometry.tile_height)
438 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
439 what: "tile extent overflows",
440 })?
441 <= geometry.y_origin
442 {
443 return Err(J2kCodestreamHeaderError::InvalidSiz {
444 what: "first tile must overlap image area",
445 });
446 }
447
448 let width = geometry.x_size - geometry.x_origin;
449 let height = geometry.y_size - geometry.y_origin;
450 if width > MAX_J2K_IMAGE_DIMENSION || height > MAX_J2K_IMAGE_DIMENSION {
451 return Err(J2kCodestreamHeaderError::InvalidSiz {
452 what: "image dimensions exceed JPEG 2000 inspect limit",
453 });
454 }
455 let tiles_x = (geometry.x_size - geometry.tile_x_origin).div_ceil(geometry.tile_width);
456 let tiles_y = (geometry.y_size - geometry.tile_y_origin).div_ceil(geometry.tile_height);
457 let tile_count = u64::from(tiles_x) * u64::from(tiles_y);
458 if tile_count > MAX_J2K_TILE_COUNT {
459 return Err(J2kCodestreamHeaderError::InvalidSiz {
460 what: "image has too many tiles",
461 });
462 }
463 Ok(((width, height), (tiles_x, tiles_y)))
464}
465
466fn parse_cod(payload: &[u8]) -> Result<ParsedCod, J2kCodestreamHeaderError> {
467 if payload.len() < 10 {
468 return Err(J2kCodestreamHeaderError::InvalidCod {
469 what: "payload shorter than fixed COD header",
470 });
471 }
472 Ok(ParsedCod {
473 resolution_levels: payload[5].saturating_add(1),
474 has_mct: payload[4] != 0,
475 reversible: payload[9] == 1,
476 high_throughput: payload[8] & 0x40 != 0,
477 })
478}
479
480fn read_u16(bytes: &[u8], offset: usize) -> u16 {
481 u16::from_be_bytes([bytes[offset], bytes[offset + 1]])
482}
483
484fn read_u32(bytes: &[u8], offset: usize) -> u32 {
485 u32::from_be_bytes([
486 bytes[offset],
487 bytes[offset + 1],
488 bytes[offset + 2],
489 bytes[offset + 3],
490 ])
491}
492
493#[cfg(test)]
494mod tests {
495 use super::{inspect_j2k_codestream_header, J2kCodestreamHeaderError};
496 use alloc::{vec, vec::Vec};
497
498 #[test]
499 fn inspect_j2k_codestream_header_accepts_minimal_main_header() {
500 let header = inspect_j2k_codestream_header(&minimal_codestream()).expect("header");
501
502 assert_eq!(header.dimensions, (128, 64));
503 assert_eq!(header.components, 3);
504 assert_eq!(header.bit_depth, 8);
505 assert_eq!(header.tile_size, (64, 64));
506 assert_eq!(header.tile_count, (2, 1));
507 assert_eq!(header.resolution_levels, 6);
508 assert!(header.reversible);
509 }
510
511 #[test]
512 fn inspect_skips_parameterless_reserved_main_header_markers() {
513 let mut bytes = minimal_codestream();
514 let sot = bytes
515 .windows(2)
516 .position(|marker| marker == [0xFF, 0x90])
517 .expect("SOT marker");
518 bytes.splice(sot..sot, [0xFF, 0x30]);
519
520 let header = inspect_j2k_codestream_header(&bytes).expect("header with reserved marker");
521
522 assert_eq!(header.dimensions, (128, 64));
523 }
524
525 #[test]
526 fn inspect_rejects_zero_component_sampling() {
527 let mut bytes = minimal_codestream();
528 rewrite_component_sampling(&mut bytes, 0, 0, 1);
529
530 let err = inspect_j2k_codestream_header(&bytes).expect_err("zero sampling must reject");
531
532 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
533 }
534
535 #[test]
536 fn inspect_rejects_oversized_dimensions() {
537 let mut bytes = minimal_codestream();
538 rewrite_siz_u32(&mut bytes, 2, 60_001);
539
540 let err = inspect_j2k_codestream_header(&bytes).expect_err("oversized width must reject");
541
542 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
543 }
544
545 #[test]
546 fn inspect_rejects_tile_origin_after_image_origin() {
547 let mut bytes = minimal_codestream();
548 rewrite_siz_u32(&mut bytes, 26, 1);
549
550 let err = inspect_j2k_codestream_header(&bytes).expect_err("bad tile origin must reject");
551
552 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
553 }
554
555 #[test]
556 fn inspect_rejects_tile_extent_overflow() {
557 let mut bytes = minimal_codestream();
558 rewrite_siz_u32(&mut bytes, 2, u32::MAX);
559 rewrite_siz_u32(&mut bytes, 10, u32::MAX - 1);
560 rewrite_siz_u32(&mut bytes, 18, 10);
561 rewrite_siz_u32(&mut bytes, 26, u32::MAX - 2);
562
563 let err = inspect_j2k_codestream_header(&bytes).expect_err("overflow must reject");
564
565 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
566 }
567
568 #[test]
569 fn inspect_rejects_excessive_tile_count() {
570 let mut bytes = minimal_codestream();
571 rewrite_siz_u32(&mut bytes, 2, 257);
572 rewrite_siz_u32(&mut bytes, 6, 257);
573 rewrite_siz_u32(&mut bytes, 18, 1);
574 rewrite_siz_u32(&mut bytes, 22, 1);
575
576 let err = inspect_j2k_codestream_header(&bytes).expect_err("tile count must reject");
577
578 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
579 }
580
581 #[test]
582 fn inspect_accepts_legal_38_bit_component_metadata() {
583 let mut bytes = minimal_codestream();
584 rewrite_component_descriptor(&mut bytes, 0, 0x25);
585 rewrite_component_descriptor(&mut bytes, 1, 0x80 | 0x25);
586
587 let header = inspect_j2k_codestream_header(&bytes).expect("legal 38-bit SIZ inspect");
588
589 assert_eq!(header.bit_depth, 38);
590 assert_eq!(header.component_info[0].bit_depth, 38);
591 assert!(!header.component_info[0].signed);
592 assert_eq!(header.component_info[1].bit_depth, 38);
593 assert!(header.component_info[1].signed);
594 }
595
596 fn minimal_codestream() -> Vec<u8> {
597 let mut bytes = vec![0xFF, 0x4F];
598 let mut siz = Vec::new();
599 push_u16(&mut siz, 0);
600 push_u32(&mut siz, 128);
601 push_u32(&mut siz, 64);
602 push_u32(&mut siz, 0);
603 push_u32(&mut siz, 0);
604 push_u32(&mut siz, 64);
605 push_u32(&mut siz, 64);
606 push_u32(&mut siz, 0);
607 push_u32(&mut siz, 0);
608 push_u16(&mut siz, 3);
609 for _ in 0..3 {
610 siz.extend_from_slice(&[0x07, 0x01, 0x01]);
611 }
612 bytes.extend_from_slice(&[0xFF, 0x51]);
613 push_u16(
614 &mut bytes,
615 u16::try_from(siz.len() + 2).expect("test SIZ segment length fits u16"),
616 );
617 bytes.extend_from_slice(&siz);
618
619 let cod = [0x00, 0x00, 0x00, 0x01, 0x01, 0x05, 0x04, 0x04, 0x00, 0x01];
620 bytes.extend_from_slice(&[0xFF, 0x52]);
621 push_u16(
622 &mut bytes,
623 u16::try_from(cod.len() + 2).expect("test COD segment length fits u16"),
624 );
625 bytes.extend_from_slice(&cod);
626 bytes.extend_from_slice(&[0xFF, 0x90, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
627 bytes
628 }
629
630 fn push_u16(out: &mut Vec<u8>, value: u16) {
631 out.extend_from_slice(&value.to_be_bytes());
632 }
633
634 fn push_u32(out: &mut Vec<u8>, value: u32) {
635 out.extend_from_slice(&value.to_be_bytes());
636 }
637
638 fn rewrite_siz_u32(bytes: &mut [u8], payload_offset: usize, value: u32) {
639 let siz = bytes
640 .windows(2)
641 .position(|marker| marker == [0xFF, 0x51])
642 .expect("SIZ marker");
643 let offset = siz + 4 + payload_offset;
644 bytes[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
645 }
646
647 fn rewrite_component_sampling(bytes: &mut [u8], component: usize, x_rsiz: u8, y_rsiz: u8) {
648 let siz = bytes
649 .windows(2)
650 .position(|marker| marker == [0xFF, 0x51])
651 .expect("SIZ marker");
652 let component_offset = siz + 40 + component * 3;
653 bytes[component_offset + 1] = x_rsiz;
654 bytes[component_offset + 2] = y_rsiz;
655 }
656
657 fn rewrite_component_descriptor(bytes: &mut [u8], component: usize, descriptor: u8) {
658 let siz = bytes
659 .windows(2)
660 .position(|marker| marker == [0xFF, 0x51])
661 .expect("SIZ marker");
662 bytes[siz + 40 + component * 3] = descriptor;
663 }
664}