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}
111
112impl fmt::Display for J2kCodestreamHeaderError {
113 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114 match self {
115 Self::TooShort { need, have } => {
116 write!(f, "input too short: need {need} bytes, have {have}")
117 }
118 Self::TruncatedAt { offset, segment } => {
119 write!(f, "truncated {segment} at offset {offset}")
120 }
121 Self::InvalidMarker { offset, marker } => {
122 write!(
123 f,
124 "invalid codestream marker FF{marker:02X} at offset {offset}"
125 )
126 }
127 Self::MissingRequiredMarker { marker } => {
128 write!(f, "missing required codestream marker {marker}")
129 }
130 Self::InvalidSegment { what, .. } => write!(f, "invalid marker segment: {what}"),
131 Self::InvalidSiz { what } => write!(f, "invalid SIZ segment: {what}"),
132 Self::InvalidCod { what } => write!(f, "invalid COD segment: {what}"),
133 Self::Unsupported { what } => write!(f, "unsupported codestream header: {what}"),
134 }
135 }
136}
137
138pub fn inspect_j2k_codestream_header(
144 input: &[u8],
145) -> Result<J2kCodestreamHeaderMetadata, J2kCodestreamHeaderError> {
146 if input.len() < 2 {
147 return Err(J2kCodestreamHeaderError::TooShort {
148 need: 2,
149 have: input.len(),
150 });
151 }
152 if !looks_like_j2k_codestream(input) {
153 return Err(J2kCodestreamHeaderError::InvalidMarker {
154 offset: 0,
155 marker: input[1],
156 });
157 }
158
159 let mut offset = 2usize;
160 let mut siz = None;
161 let mut cod = None;
162 let mut high_throughput_cap = false;
163 let mut terminated = false;
164
165 while offset < input.len() {
166 let marker = read_marker(input, &mut offset)?;
167 match marker {
168 MARKER_SOT | MARKER_SOD | MARKER_EOC => {
169 terminated = true;
170 break;
171 }
172 MARKER_SIZ => {
173 let payload = read_segment_payload(input, &mut offset, "SIZ")?;
174 siz = Some(parse_siz(payload)?);
175 }
176 MARKER_COD => {
177 let payload = read_segment_payload(input, &mut offset, "COD")?;
178 cod = Some(parse_cod(payload)?);
179 }
180 MARKER_CAP => {
181 let _ = read_segment_payload(input, &mut offset, "CAP")?;
182 high_throughput_cap = true;
183 }
184 _ => {
185 let _ = read_segment_payload(input, &mut offset, "segment")?;
186 }
187 }
188 }
189
190 if !terminated {
191 return Err(J2kCodestreamHeaderError::TruncatedAt {
192 offset,
193 segment: "main header terminator",
194 });
195 }
196
197 let siz = siz.ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "SIZ" })?;
198 let cod = cod
199 .ok_or(J2kCodestreamHeaderError::MissingRequiredMarker { marker: "COD" })?
200 .with_high_throughput_cap(high_throughput_cap);
201
202 Ok(J2kCodestreamHeaderMetadata {
203 dimensions: siz.dimensions,
204 components: siz.components,
205 bit_depth: siz.bit_depth,
206 tile_size: siz.tile_size,
207 tile_count: siz.tile_count,
208 component_info: siz.component_info,
209 resolution_levels: cod.resolution_levels,
210 has_mct: cod.has_mct,
211 reversible: cod.reversible,
212 high_throughput: cod.high_throughput,
213 })
214}
215
216#[must_use]
218pub fn looks_like_j2k_codestream(input: &[u8]) -> bool {
219 input.len() >= 2 && input[0] == 0xFF && input[1] == MARKER_SOC
220}
221
222#[derive(Debug, Clone)]
223struct ParsedSiz {
224 dimensions: (u32, u32),
225 components: u16,
226 bit_depth: u8,
227 tile_size: (u32, u32),
228 tile_count: (u32, u32),
229 component_info: Vec<J2kCodestreamComponentHeader>,
230}
231
232#[derive(Debug, Clone, Copy)]
233struct ParsedCod {
234 resolution_levels: u8,
235 has_mct: bool,
236 reversible: bool,
237 high_throughput: bool,
238}
239
240impl ParsedCod {
241 const fn with_high_throughput_cap(mut self, high_throughput_cap: bool) -> Self {
242 self.high_throughput |= high_throughput_cap;
243 self
244 }
245}
246
247fn read_marker(input: &[u8], offset: &mut usize) -> Result<u8, J2kCodestreamHeaderError> {
248 if *offset + 2 > input.len() {
249 return Err(J2kCodestreamHeaderError::TruncatedAt {
250 offset: *offset,
251 segment: "marker",
252 });
253 }
254 if input[*offset] != 0xFF {
255 return Err(J2kCodestreamHeaderError::InvalidMarker {
256 offset: *offset,
257 marker: input[*offset],
258 });
259 }
260 let marker = input[*offset + 1];
261 *offset += 2;
262 Ok(marker)
263}
264
265fn read_segment_payload<'a>(
266 input: &'a [u8],
267 offset: &mut usize,
268 segment: &'static str,
269) -> Result<&'a [u8], J2kCodestreamHeaderError> {
270 if *offset + 2 > input.len() {
271 return Err(J2kCodestreamHeaderError::TruncatedAt {
272 offset: *offset,
273 segment,
274 });
275 }
276 let length = u16::from_be_bytes([input[*offset], input[*offset + 1]]) as usize;
277 if length < 2 {
278 return Err(J2kCodestreamHeaderError::InvalidSegment {
279 offset: *offset,
280 what: "segment length smaller than header",
281 });
282 }
283 let start = *offset + 2;
284 let end = *offset + length;
285 if end > input.len() {
286 return Err(J2kCodestreamHeaderError::TruncatedAt {
287 offset: *offset,
288 segment,
289 });
290 }
291 *offset = end;
292 Ok(&input[start..end])
293}
294
295#[allow(clippy::similar_names)]
296fn parse_siz(payload: &[u8]) -> Result<ParsedSiz, J2kCodestreamHeaderError> {
297 if payload.len() < 36 {
298 return Err(J2kCodestreamHeaderError::InvalidSiz {
299 what: "payload shorter than fixed SIZ header",
300 });
301 }
302 let x_size = read_u32(payload, 2);
303 let y_size = read_u32(payload, 6);
304 let x_origin = read_u32(payload, 10);
305 let y_origin = read_u32(payload, 14);
306 let tile_width = read_u32(payload, 18);
307 let tile_height = read_u32(payload, 22);
308 let tile_x_origin = read_u32(payload, 26);
309 let tile_y_origin = read_u32(payload, 30);
310 let component_count = read_u16(payload, 34);
311
312 let component_bytes = usize::from(component_count) * 3;
313 if payload.len() < 36 + component_bytes {
314 return Err(J2kCodestreamHeaderError::InvalidSiz {
315 what: "component descriptors truncated",
316 });
317 }
318 if component_count == 0 {
319 return Err(J2kCodestreamHeaderError::InvalidSiz {
320 what: "component count must be non-zero",
321 });
322 }
323 if component_count > MAX_J2K_SPEC_COMPONENTS {
324 return Err(J2kCodestreamHeaderError::InvalidSiz {
325 what: "component count exceeds JPEG 2000 limit",
326 });
327 }
328 if x_size <= x_origin || y_size <= y_origin {
329 return Err(J2kCodestreamHeaderError::InvalidSiz {
330 what: "image origin must be smaller than image size",
331 });
332 }
333 if tile_width == 0 || tile_height == 0 {
334 return Err(J2kCodestreamHeaderError::InvalidSiz {
335 what: "tile size must be non-zero",
336 });
337 }
338 if tile_x_origin >= x_size || tile_y_origin >= y_size {
339 return Err(J2kCodestreamHeaderError::InvalidSiz {
340 what: "tile origin must be within image bounds",
341 });
342 }
343 if tile_x_origin > x_origin || tile_y_origin > y_origin {
344 return Err(J2kCodestreamHeaderError::InvalidSiz {
345 what: "tile origin must not exceed image origin",
346 });
347 }
348 if tile_x_origin
349 .checked_add(tile_width)
350 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
351 what: "tile extent overflows",
352 })?
353 <= x_origin
354 || tile_y_origin
355 .checked_add(tile_height)
356 .ok_or(J2kCodestreamHeaderError::InvalidSiz {
357 what: "tile extent overflows",
358 })?
359 <= y_origin
360 {
361 return Err(J2kCodestreamHeaderError::InvalidSiz {
362 what: "first tile must overlap image area",
363 });
364 }
365
366 let width = x_size - x_origin;
367 let height = y_size - y_origin;
368 if width > MAX_J2K_IMAGE_DIMENSION || height > MAX_J2K_IMAGE_DIMENSION {
369 return Err(J2kCodestreamHeaderError::InvalidSiz {
370 what: "image dimensions exceed JPEG 2000 inspect limit",
371 });
372 }
373 let tiles_x = (x_size - tile_x_origin).div_ceil(tile_width);
374 let tiles_y = (y_size - tile_y_origin).div_ceil(tile_height);
375 let tile_count = u64::from(tiles_x) * u64::from(tiles_y);
376 if tile_count > MAX_J2K_TILE_COUNT {
377 return Err(J2kCodestreamHeaderError::InvalidSiz {
378 what: "image has too many tiles",
379 });
380 }
381 let mut bit_depth = 0u8;
382 let mut component_info = Vec::with_capacity(usize::from(component_count));
383 for idx in 0..usize::from(component_count) {
384 let ssiz = payload[36 + idx * 3];
385 let precision = (ssiz & 0x7F) + 1;
386 let x_rsiz = payload[36 + idx * 3 + 1];
387 let y_rsiz = payload[36 + idx * 3 + 2];
388 if x_rsiz == 0 || y_rsiz == 0 {
389 return Err(J2kCodestreamHeaderError::InvalidSiz {
390 what: "component sampling factors must be non-zero",
391 });
392 }
393 bit_depth = bit_depth.max(precision);
394 component_info.push(J2kCodestreamComponentHeader {
395 bit_depth: precision,
396 signed: ssiz & 0x80 != 0,
397 x_rsiz,
398 y_rsiz,
399 });
400 }
401
402 Ok(ParsedSiz {
403 dimensions: (width, height),
404 components: component_count,
405 bit_depth,
406 tile_size: (tile_width, tile_height),
407 tile_count: (tiles_x, tiles_y),
408 component_info,
409 })
410}
411
412fn parse_cod(payload: &[u8]) -> Result<ParsedCod, J2kCodestreamHeaderError> {
413 if payload.len() < 10 {
414 return Err(J2kCodestreamHeaderError::InvalidCod {
415 what: "payload shorter than fixed COD header",
416 });
417 }
418 Ok(ParsedCod {
419 resolution_levels: payload[5].saturating_add(1),
420 has_mct: payload[4] != 0,
421 reversible: payload[9] == 1,
422 high_throughput: payload[8] & 0x40 != 0,
423 })
424}
425
426fn read_u16(bytes: &[u8], offset: usize) -> u16 {
427 u16::from_be_bytes([bytes[offset], bytes[offset + 1]])
428}
429
430fn read_u32(bytes: &[u8], offset: usize) -> u32 {
431 u32::from_be_bytes([
432 bytes[offset],
433 bytes[offset + 1],
434 bytes[offset + 2],
435 bytes[offset + 3],
436 ])
437}
438
439#[cfg(test)]
440mod tests {
441 use super::{inspect_j2k_codestream_header, J2kCodestreamHeaderError};
442 use alloc::{vec, vec::Vec};
443
444 #[test]
445 fn inspect_j2k_codestream_header_accepts_minimal_main_header() {
446 let header = inspect_j2k_codestream_header(&minimal_codestream()).expect("header");
447
448 assert_eq!(header.dimensions, (128, 64));
449 assert_eq!(header.components, 3);
450 assert_eq!(header.bit_depth, 8);
451 assert_eq!(header.tile_size, (64, 64));
452 assert_eq!(header.tile_count, (2, 1));
453 assert_eq!(header.resolution_levels, 6);
454 assert!(header.reversible);
455 }
456
457 #[test]
458 fn inspect_rejects_zero_component_sampling() {
459 let mut bytes = minimal_codestream();
460 rewrite_component_sampling(&mut bytes, 0, 0, 1);
461
462 let err = inspect_j2k_codestream_header(&bytes).expect_err("zero sampling must reject");
463
464 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
465 }
466
467 #[test]
468 fn inspect_rejects_oversized_dimensions() {
469 let mut bytes = minimal_codestream();
470 rewrite_siz_u32(&mut bytes, 2, 60_001);
471
472 let err = inspect_j2k_codestream_header(&bytes).expect_err("oversized width must reject");
473
474 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
475 }
476
477 #[test]
478 fn inspect_rejects_tile_origin_after_image_origin() {
479 let mut bytes = minimal_codestream();
480 rewrite_siz_u32(&mut bytes, 26, 1);
481
482 let err = inspect_j2k_codestream_header(&bytes).expect_err("bad tile origin must reject");
483
484 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
485 }
486
487 #[test]
488 fn inspect_rejects_tile_extent_overflow() {
489 let mut bytes = minimal_codestream();
490 rewrite_siz_u32(&mut bytes, 2, u32::MAX);
491 rewrite_siz_u32(&mut bytes, 10, u32::MAX - 1);
492 rewrite_siz_u32(&mut bytes, 18, 10);
493 rewrite_siz_u32(&mut bytes, 26, u32::MAX - 2);
494
495 let err = inspect_j2k_codestream_header(&bytes).expect_err("overflow must reject");
496
497 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
498 }
499
500 #[test]
501 fn inspect_rejects_excessive_tile_count() {
502 let mut bytes = minimal_codestream();
503 rewrite_siz_u32(&mut bytes, 2, 257);
504 rewrite_siz_u32(&mut bytes, 6, 257);
505 rewrite_siz_u32(&mut bytes, 18, 1);
506 rewrite_siz_u32(&mut bytes, 22, 1);
507
508 let err = inspect_j2k_codestream_header(&bytes).expect_err("tile count must reject");
509
510 assert!(matches!(err, J2kCodestreamHeaderError::InvalidSiz { .. }));
511 }
512
513 fn minimal_codestream() -> Vec<u8> {
514 let mut bytes = vec![0xFF, 0x4F];
515 let mut siz = Vec::new();
516 push_u16(&mut siz, 0);
517 push_u32(&mut siz, 128);
518 push_u32(&mut siz, 64);
519 push_u32(&mut siz, 0);
520 push_u32(&mut siz, 0);
521 push_u32(&mut siz, 64);
522 push_u32(&mut siz, 64);
523 push_u32(&mut siz, 0);
524 push_u32(&mut siz, 0);
525 push_u16(&mut siz, 3);
526 for _ in 0..3 {
527 siz.extend_from_slice(&[0x07, 0x01, 0x01]);
528 }
529 bytes.extend_from_slice(&[0xFF, 0x51]);
530 push_u16(&mut bytes, (siz.len() + 2) as u16);
531 bytes.extend_from_slice(&siz);
532
533 let cod = [0x00, 0x00, 0x00, 0x01, 0x01, 0x05, 0x04, 0x04, 0x00, 0x01];
534 bytes.extend_from_slice(&[0xFF, 0x52]);
535 push_u16(&mut bytes, (cod.len() + 2) as u16);
536 bytes.extend_from_slice(&cod);
537 bytes.extend_from_slice(&[0xFF, 0x90, 0x00, 0x0A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]);
538 bytes
539 }
540
541 fn push_u16(out: &mut Vec<u8>, value: u16) {
542 out.extend_from_slice(&value.to_be_bytes());
543 }
544
545 fn push_u32(out: &mut Vec<u8>, value: u32) {
546 out.extend_from_slice(&value.to_be_bytes());
547 }
548
549 fn rewrite_siz_u32(bytes: &mut [u8], payload_offset: usize, value: u32) {
550 let siz = bytes
551 .windows(2)
552 .position(|marker| marker == [0xFF, 0x51])
553 .expect("SIZ marker");
554 let offset = siz + 4 + payload_offset;
555 bytes[offset..offset + 4].copy_from_slice(&value.to_be_bytes());
556 }
557
558 fn rewrite_component_sampling(bytes: &mut [u8], component: usize, x_rsiz: u8, y_rsiz: u8) {
559 let siz = bytes
560 .windows(2)
561 .position(|marker| marker == [0xFF, 0x51])
562 .expect("SIZ marker");
563 let component_offset = siz + 40 + component * 3;
564 bytes[component_offset + 1] = x_rsiz;
565 bytes[component_offset + 2] = y_rsiz;
566 }
567}