Skip to content
File

Blob: firmware/vendor/str0m/src/packet/h266.rs

rust2581 lines
1//! H266 (VVC) RTP packetization per RFC 9328.
2//!
3//! Wire formats implemented (RFC 9328 §4.3):
4//! - Single NAL unit packets (§4.3.1).
5//! - Aggregation Packets, NAL type 28 (§4.3.2).
6//! - Fragmentation Units, NAL type 29 (§4.3.3), FU header
7//! `S(1) | E(1) | P(1) | FuType(5)`.
8//!
9//! The two-byte NAL unit header is `F(1) | Z(1) | LayerId(6) || Type(5) |
10//! TID(3)` (§1.1.4) — the NAL type lives in the SECOND byte, upper 5 bits.
11//! Parameter sets: VPS=14, SPS=15, PPS=16. Skipped on send: AUD=20,
12//! Filler(FD)=25. IRAP (keyframe) NAL types: IDR_W_RADL=7, IDR_N_LP=8, CRA=9.
13//!
14//! Profile/tier/level fmtp negotiation (RFC 9328 §7.2) is implemented in
15//! `h266_profile.rs` and the format layer. DONL (`sprop-max-don-diff`,
16//! §7.2) is supported for send and receive via `with_donl`, wired from SDP
17//! negotiation.
18 
19use super::{CodecExtra, Depacketizer, PacketError, Packetizer};
20use tracing::warn;
21 
22pub static ANNEXB_NALUSTART_CODE: &[u8] = &[0x00, 0x00, 0x00, 0x01];
23 
24/// H266 NAL unit header size (2 bytes).
25const H266NALU_HEADER_SIZE: usize = 2;
26/// FU header size (1 byte).
27const H266FRAGMENTATION_UNIT_HEADER_SIZE: usize = 1;
28 
29// H266 NAL unit types (ITU-T H.266 Table 5).
30const H266NALU_IDR_W_RADL: u8 = 7;
31const H266NALU_IDR_N_LP: u8 = 8;
32const H266NALU_CRA: u8 = 9;
33#[allow(dead_code)] // RFC 9328 NAL type table completeness; referenced by tests.
34const H266NALU_GDR: u8 = 10;
35const H266NALU_VPS_NALU_TYPE: u8 = 14;
36const H266NALU_SPS_NALU_TYPE: u8 = 15;
37const H266NALU_PPS_NALU_TYPE: u8 = 16;
38const H266NALU_AUD_NALU_TYPE: u8 = 20;
39const H266NALU_FILLER_NALU_TYPE: u8 = 25; // FD_NUT
40 
41// RFC 9328 RTP packet types.
42const H266NALU_AGGREGATION_PACKET_TYPE: u8 = 28;
43const H266NALU_FRAGMENTATION_UNIT_TYPE: u8 = 29;
44 
45const MAX_PACKET_SIZE: usize = 1200;
46const MIN_FU_PAYLOAD: usize = 1;
47const MIN_MTU: usize = H266NALU_HEADER_SIZE + H266FRAGMENTATION_UNIT_HEADER_SIZE + MIN_FU_PAYLOAD;
48 
49/// Codec extra reported alongside depacketized H266 data.
50#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
51pub struct H266CodecExtra {
52 /// Whether the depacketized data contains an IRAP NAL (keyframe).
53 pub is_keyframe: bool,
54}
55 
56/// H266 NAL unit header (RFC 9328 §1.1.4).
57///
58/// ```text
59/// +---------------+---------------+
60/// |0|1|2|3|4|5|6|7|0|1|2|3|4|5|6|7|
61/// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
62/// |F|Z| LayerID | Type | TID |
63/// +---------------+---------------+
64/// ```
65#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
66pub struct H266NALUHeader(pub u16);
67 
68impl H266NALUHeader {
69 pub fn new(high_byte: u8, low_byte: u8) -> Self {
70 H266NALUHeader(((high_byte as u16) << 8) | low_byte as u16)
71 }
72 
73 /// Construct a header from NAL type, layer id and temporal id.
74 pub fn new_with_type(typ: u8, layer_id: u8, tid: u8) -> Self {
75 let b0 = layer_id & 0b0011_1111;
76 let b1 = ((typ & 0b1_1111) << 3) | (tid & 0b111);
77 Self::new(b0, b1)
78 }
79 
80 /// Forbidden zero bit.
81 pub fn f(&self) -> bool {
82 (self.0 >> 15) & 0b1 != 0
83 }
84 
85 /// nuh_reserved_zero_bit (Z).
86 pub fn z(&self) -> bool {
87 (self.0 >> 14) & 0b1 != 0
88 }
89 
90 /// nuh_layer_id (6 bits of the first byte).
91 pub fn layer_id(&self) -> u8 {
92 ((self.0 >> 8) & 0b0011_1111) as u8
93 }
94 
95 /// NAL unit type — upper 5 bits of the SECOND byte.
96 pub fn nalu_type(&self) -> u8 {
97 ((self.0 >> 3) & 0b1_1111) as u8
98 }
99 
100 /// nuh_temporal_id_plus1 — lower 3 bits of the second byte.
101 pub fn tid(&self) -> u8 {
102 (self.0 & 0b111) as u8
103 }
104 
105 /// IRAP picture (keyframe): IDR_W_RADL, IDR_N_LP, CRA.
106 /// Note: GDR (gradual decoding refresh) is a recovery point but not an
107 /// instantaneous keyframe, so it is deliberately excluded.
108 pub fn is_irap(&self) -> bool {
109 matches!(
110 self.nalu_type(),
111 H266NALU_IDR_W_RADL | H266NALU_IDR_N_LP | H266NALU_CRA
112 )
113 }
114 
115 pub fn is_aggregation_packet(&self) -> bool {
116 self.nalu_type() == H266NALU_AGGREGATION_PACKET_TYPE
117 }
118 
119 pub fn is_fragmentation_unit(&self) -> bool {
120 self.nalu_type() == H266NALU_FRAGMENTATION_UNIT_TYPE
121 }
122}
123 
124/// H266 FU header: `S(1) | E(1) | P(1) | FuType(5)` (RFC 9328 §4.3.3).
125#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
126pub struct H266FragmentationUnitHeader(pub u8);
127 
128impl H266FragmentationUnitHeader {
129 pub fn new(start: bool, end: bool, last_of_picture: bool, fu_type: u8) -> Self {
130 let mut b = fu_type & 0b1_1111;
131 if start {
132 b |= 0b1000_0000;
133 }
134 if end {
135 b |= 0b0100_0000;
136 }
137 if last_of_picture {
138 b |= 0b0010_0000;
139 }
140 H266FragmentationUnitHeader(b)
141 }
142 
143 /// Start of fragmented NAL unit.
144 pub fn s(&self) -> bool {
145 self.0 & 0b1000_0000 != 0
146 }
147 
148 /// End of fragmented NAL unit.
149 pub fn e(&self) -> bool {
150 self.0 & 0b0100_0000 != 0
151 }
152 
153 /// Last FU of the last VCL NAL of the picture.
154 #[allow(dead_code)] // RFC 9328 FU header accessor; exercised by tests.
155 pub fn p(&self) -> bool {
156 self.0 & 0b0010_0000 != 0
157 }
158 
159 /// Original NAL unit type.
160 pub fn fu_type(&self) -> u8 {
161 self.0 & 0b1_1111
162 }
163}
164 
165/// Detect whether an RTP payload contains an H266 keyframe (IRAP).
166///
167/// Note: assumes payloads without DONL fields. When `sprop-max-don-diff > 0`
168/// is negotiated, Aggregation Packet payloads carry a 16-bit DONL before the
169/// first unit, which this scan does not account for (single NAL and FU
170/// detection are unaffected — the inspected bytes precede the DONL).
171pub fn detect_h266_keyframe(payload: &[u8]) -> bool {
172 if payload.len() < H266NALU_HEADER_SIZE {
173 return false;
174 }
175 
176 let header = H266NALUHeader::new(payload[0], payload[1]);
177 match header.nalu_type() {
178 H266NALU_AGGREGATION_PACKET_TYPE => {
179 // Check all aggregated NAL units.
180 let mut offset = H266NALU_HEADER_SIZE;
181 while offset + 2 <= payload.len() {
182 let nalu_size = ((payload[offset] as usize) << 8) | payload[offset + 1] as usize;
183 offset += 2;
184 if offset + nalu_size > payload.len() || nalu_size < H266NALU_HEADER_SIZE {
185 break;
186 }
187 let inner = H266NALUHeader::new(payload[offset], payload[offset + 1]);
188 if inner.is_irap() {
189 return true;
190 }
191 offset += nalu_size;
192 }
193 false
194 }
195 H266NALU_FRAGMENTATION_UNIT_TYPE => {
196 if payload.len() < H266NALU_HEADER_SIZE + 1 {
197 return false;
198 }
199 let fu = H266FragmentationUnitHeader(payload[2]);
200 let t = fu.fu_type();
201 fu.s() && (H266NALU_IDR_W_RADL..=H266NALU_CRA).contains(&t)
202 }
203 _ => header.is_irap(),
204 }
205}
206 
207/// H266 packetizer (RFC 9328): Single NAL / AP (28) / FU (29).
208#[derive(Debug)]
209pub struct H266Packetizer {
210 /// Cached parameter sets, sent as an AP before the next non-param NAL.
211 vps_nalu: Option<Vec<u8>>,
212 sps_nalu: Option<Vec<u8>>,
213 pps_nalu: Option<Vec<u8>>,
214 /// DONL counter (RFC 9328 §4.3): present when sprop-max-don-diff > 0.
215 donl: Option<u16>,
216 /// Reusable packet build buffer.
217 pkt_buf: Vec<u8>,
218}
219 
220impl Default for H266Packetizer {
221 fn default() -> Self {
222 H266Packetizer {
223 vps_nalu: None,
224 sps_nalu: None,
225 pps_nalu: None,
226 donl: None,
227 pkt_buf: Vec::with_capacity(MAX_PACKET_SIZE),
228 }
229 }
230}
231 
232impl H266Packetizer {
233 /// Enable/disable DONL field emission (RFC 9328 §4.3, sprop-max-don-diff > 0).
234 pub fn with_donl(&mut self, value: bool) {
235 self.donl = if value { Some(0) } else { None };
236 }
237 
238 fn increment_donl(&mut self) {
239 if let Some(ref mut donl) = self.donl {
240 *donl = donl.wrapping_add(1);
241 }
242 }
243 
244 fn increment_donl_by(&mut self, n: u16) {
245 if let Some(ref mut donl) = self.donl {
246 *donl = donl.wrapping_add(n);
247 }
248 }
249 
250 /// Build an AP (type 28) from NAL units into `pkt_buf`.
251 /// Returns false if the AP would exceed the MTU (or a unit cannot be
252 /// represented with a 16-bit length).
253 fn build_ap_packet(
254 nal_units: &[&[u8]],
255 donl: Option<u16>,
256 buf: &mut Vec<u8>,
257 mtu: usize,
258 ) -> bool {
259 // AP PayloadHdr (RFC 9328 §4.3.2): F MUST be 0 if the F bit of each
260 // aggregated NAL unit is 0 (OR otherwise); Z is reserved (0);
261 // LayerId and TID MUST be the lowest LayerId/TID of all the
262 // aggregated NAL units.
263 let mut f_bit = false;
264 let mut min_layer = 0b0011_1111u8;
265 let mut min_tid = 0b111u8;
266 for n in nal_units {
267 let h = H266NALUHeader::new(n[0], n[1]);
268 f_bit |= h.f();
269 min_layer = min_layer.min(h.layer_id());
270 min_tid = min_tid.min(h.tid());
271 }
272 // TID is nuh_temporal_id_plus1: never emit 0.
273 let tid = min_tid.max(1);
274 
275 // Aggregation unit lengths are 16-bit (RFC 9328 §4.3.2).
276 if nal_units.iter().any(|n| n.len() > u16::MAX as usize) {
277 return false;
278 }
279 
280 // DONL (2 bytes) precedes the first aggregation unit when enabled
281 // (RFC 9328 §4.3.2).
282 let donl_overhead = if donl.is_some() { 2 } else { 0 };
283 let total: usize = H266NALU_HEADER_SIZE
284 + donl_overhead
285 + nal_units.iter().map(|n| 2 + n.len()).sum::<usize>();
286 if total > mtu {
287 return false;
288 }
289 
290 buf.clear();
291 let ap_hdr =
292 H266NALUHeader::new_with_type(H266NALU_AGGREGATION_PACKET_TYPE, min_layer, tid);
293 let [b0, b1] = ap_hdr.0.to_be_bytes();
294 buf.push(b0 | ((f_bit as u8) << 7)); // F | Z=0 | LayerId
295 buf.push(b1);
296 
297 // Write DONL for the first aggregation unit if present (RFC 9328 §4.3.2).
298 if let Some(donl_value) = donl {
299 buf.extend_from_slice(&donl_value.to_be_bytes());
300 }
301 
302 for n in nal_units {
303 buf.extend_from_slice(&(n.len() as u16).to_be_bytes());
304 buf.extend_from_slice(n);
305 }
306 true
307 }
308 
309 /// Find the next Annex-B start code at/after `start`.
310 /// Returns (offset, length) or (-1, -1).
311 fn next_start_code(payload: &[u8], start: usize) -> (isize, isize) {
312 let mut i = start;
313 while i + 3 <= payload.len() {
314 if payload[i] == 0 && payload[i + 1] == 0 {
315 if payload[i + 2] == 1 {
316 return (i as isize, 3);
317 }
318 if i + 4 <= payload.len() && payload[i + 2] == 0 && payload[i + 3] == 1 {
319 return (i as isize, 4);
320 }
321 }
322 i += 1;
323 }
324 (-1, -1)
325 }
326 
327 /// Emit one NAL unit as RTP payload(s):
328 /// param sets -> cached -> AP(28); small -> Single NAL; large -> FU(29).
329 fn emit_nalu(&mut self, nalu: &[u8], mtu: usize, out: &mut Vec<Vec<u8>>) {
330 if mtu == 0 || nalu.len() < H266NALU_HEADER_SIZE {
331 return;
332 }
333 
334 let original_hdr = H266NALUHeader::new(nalu[0], nalu[1]);
335 let original_type = original_hdr.nalu_type();
336 
337 // Ignore AUD/filler.
338 if original_type == H266NALU_AUD_NALU_TYPE || original_type == H266NALU_FILLER_NALU_TYPE {
339 return;
340 }
341 
342 // NAL types 28 (AP) and 29 (FU) are reserved for RTP packetization
343 // (RFC 9328 §4.3) and must never appear in an input bitstream.
344 if original_type == H266NALU_AGGREGATION_PACKET_TYPE
345 || original_type == H266NALU_FRAGMENTATION_UNIT_TYPE
346 {
347 warn!(
348 "H266-PKT skipping reserved NAL type {} in input bitstream",
349 original_type
350 );
351 return;
352 }
353 
354 // Cache parameter sets; send them as AP before the next other NALU.
355 match original_type {
356 H266NALU_VPS_NALU_TYPE => {
357 self.vps_nalu = Some(nalu.to_vec());
358 return;
359 }
360 H266NALU_SPS_NALU_TYPE => {
361 self.sps_nalu = Some(nalu.to_vec());
362 return;
363 }
364 H266NALU_PPS_NALU_TYPE => {
365 self.pps_nalu = Some(nalu.to_vec());
366 return;
367 }
368 _ => {}
369 }
370 
371 // Emit cached parameter sets as an AP (or singles fallback).
372 // Note: VPS is optional in VVC streams — SPS+PPS is enough to emit.
373 if self.sps_nalu.is_some() && self.pps_nalu.is_some() {
374 let mut nal_units_arr: [&[u8]; 3] = [&[], &[], &[]];
375 let mut count = 0;
376 if let Some(vps) = &self.vps_nalu {
377 nal_units_arr[count] = vps;
378 count += 1;
379 }
380 if let Some(sps) = &self.sps_nalu {
381 nal_units_arr[count] = sps;
382 count += 1;
383 }
384 if let Some(pps) = &self.pps_nalu {
385 nal_units_arr[count] = pps;
386 count += 1;
387 }
388 let nal_units = &nal_units_arr[..count];
389 
390 if count >= 2 && Self::build_ap_packet(nal_units, self.donl, &mut self.pkt_buf, mtu) {
391 out.push(self.pkt_buf.clone());
392 // One DONL per aggregated NAL unit.
393 self.increment_donl_by(count as u16);
394 } else {
395 // Fall back to individual Single NAL packets.
396 let fallback_donl_overhead = if self.donl.is_some() { 2 } else { 0 };
397 for nal_unit in nal_units {
398 if nal_unit.len() + fallback_donl_overhead <= mtu {
399 if let Some(ref mut donl_value) = self.donl {
400 self.pkt_buf.clear();
401 self.pkt_buf
402 .extend_from_slice(&nal_unit[..H266NALU_HEADER_SIZE]);
403 self.pkt_buf.extend_from_slice(&donl_value.to_be_bytes());
404 self.pkt_buf
405 .extend_from_slice(&nal_unit[H266NALU_HEADER_SIZE..]);
406 out.push(self.pkt_buf.clone());
407 *donl_value = donl_value.wrapping_add(1);
408 } else {
409 out.push(nal_unit.to_vec());
410 }
411 }
412 }
413 }
414 
415 self.vps_nalu = None;
416 self.sps_nalu = None;
417 self.pps_nalu = None;
418 }
419 
420 // Single NAL unit packet (RFC 9328 §4.3.1).
421 // With DONL enabled, 2 extra bytes follow the NAL header.
422 let donl_overhead = if self.donl.is_some() { 2 } else { 0 };
423 if nalu.len() + donl_overhead <= mtu {
424 if let Some(donl_value) = self.donl {
425 self.pkt_buf.clear();
426 self.pkt_buf
427 .extend_from_slice(&nalu[..H266NALU_HEADER_SIZE]);
428 self.pkt_buf.extend_from_slice(&donl_value.to_be_bytes());
429 self.pkt_buf
430 .extend_from_slice(&nalu[H266NALU_HEADER_SIZE..]);
431 out.push(self.pkt_buf.clone());
432 self.increment_donl();
433 } else {
434 out.push(nalu.to_vec());
435 }
436 return;
437 }
438 
439 // Fragmentation Units (RFC 9328 §4.3.3).
440 const FU_OVERHEAD: usize = H266NALU_HEADER_SIZE + H266FRAGMENTATION_UNIT_HEADER_SIZE;
441 if nalu.len() <= H266NALU_HEADER_SIZE {
442 return;
443 }
444 
445 // FU payload header: copy of the original header with Type replaced
446 // by 29. Type lives in the SECOND byte, bits 7..3.
447 let fu_b0 = nalu[0];
448 let fu_b1 = (H266NALU_FRAGMENTATION_UNIT_TYPE << 3) | (nalu[1] & 0b111);
449 
450 let payload = &nalu[H266NALU_HEADER_SIZE..];
451 let effective_mtu = mtu.min(MAX_PACKET_SIZE);
452 // The first fragment also carries the DONL field when enabled
453 // (RFC 9328 §4.3.3).
454 if effective_mtu <= FU_OVERHEAD + donl_overhead {
455 return;
456 }
457 let first_max = effective_mtu - FU_OVERHEAD - donl_overhead;
458 let max_fragment = effective_mtu - FU_OVERHEAD;
459 let donl_bytes = self.donl.map(u16::to_be_bytes);
460 
461 let mut offset = 0;
462 while offset < payload.len() {
463 let first = offset == 0;
464 let remaining = payload.len() - offset;
465 let budget = if first { first_max } else { max_fragment };
466 let take = remaining.min(budget);
467 let end = offset + take == payload.len();
468 
469 // P bit (last FU of the last VCL NAL of a picture): RFC 9328
470 // §4.3.3 only mandates P=0 when the FU is NOT that last
471 // fragment; always sending 0 is compliant. Not tracked here.
472 let fu_hdr = H266FragmentationUnitHeader::new(first, end, false, original_type);
473 
474 self.pkt_buf.clear();
475 self.pkt_buf.push(fu_b0);
476 self.pkt_buf.push(fu_b1);
477 self.pkt_buf.push(fu_hdr.0);
478 if first {
479 if let Some(ref b) = donl_bytes {
480 self.pkt_buf.extend_from_slice(b);
481 }
482 }
483 self.pkt_buf
484 .extend_from_slice(&payload[offset..offset + take]);
485 out.push(self.pkt_buf.clone());
486 offset += take;
487 }
488 
489 // One DONL per NAL unit (not per fragment).
490 self.increment_donl();
491 }
492}
493 
494impl Packetizer for H266Packetizer {
495 fn packetize(&mut self, mtu: usize, payload: &[u8]) -> Result<Vec<Vec<u8>>, PacketError> {
496 if payload.is_empty() {
497 return Ok(vec![]);
498 }
499 
500 let mtu = match mtu {
501 0 => {
502 warn!("MTU is 0, cannot packetize H.266");
503 return Ok(vec![]);
504 }
505 mtu if mtu > MAX_PACKET_SIZE => {
506 warn!(
507 "MTU {} exceeds MAX_PACKET_SIZE {}, clamping",
508 mtu, MAX_PACKET_SIZE
509 );
510 MAX_PACKET_SIZE
511 }
512 mtu if mtu < MIN_MTU => {
513 warn!("MTU {} too small for H.266 fragmentation", mtu);
514 return Ok(vec![]);
515 }
516 mtu => mtu,
517 };
518 
519 // Pre-allocate with estimated capacity to avoid reallocations
520 // in the hot path (one entry per expected RTP packet).
521 let estimated_packets = payload
522 .len()
523 .checked_div(mtu.saturating_sub(3))
524 .unwrap_or(1)
525 .saturating_add(4);
526 let mut packets = Vec::with_capacity(estimated_packets);
527 
528 // No start codes: treat the whole payload as one NAL unit.
529 let (mut next_start, mut next_len) = Self::next_start_code(payload, 0);
530 if next_start == -1 {
531 self.emit_nalu(payload, mtu, &mut packets);
532 return Ok(packets);
533 }
534 
535 // Walk the Annex-B bytestream.
536 while next_start != -1 {
537 let nalu_start = (next_start + next_len) as usize;
538 let (s2, l2) = Self::next_start_code(payload, nalu_start);
539 next_start = s2;
540 next_len = l2;
541 
542 if next_start != -1 {
543 self.emit_nalu(&payload[nalu_start..next_start as usize], mtu, &mut packets);
544 } else {
545 self.emit_nalu(&payload[nalu_start..], mtu, &mut packets);
546 }
547 }
548 
549 Ok(packets)
550 }
551 
552 fn is_marker(&mut self, _data: &[u8], _previous: Option<&[u8]>, last: bool) -> bool {
553 last
554 }
555}
556 
557/// H266 depacketizer (RFC 9328): Single NAL / AP (28) / FU (29).
558/// Emits Annex-B (start-code prefixed) NAL units.
559#[derive(Debug, Default)]
560pub struct H266Depacketizer {
561 /// Reassembly buffer for fragmentation units.
562 fu_buffer: Option<Vec<u8>>,
563 /// Whether DONL fields are present (sprop-max-don-diff > 0).
564 might_need_donl: bool,
565}
566 
567impl H266Depacketizer {
568 /// Enable/disable DONL field parsing (RFC 9328 §4.3, sprop-max-don-diff > 0).
569 pub fn with_donl(&mut self, value: bool) {
570 self.might_need_donl = value;
571 }
572}
573 
574impl Depacketizer for H266Depacketizer {
575 fn out_size_hint(&self, packets_size: usize) -> Option<usize> {
576 let estimated_packets = (packets_size / 1200).saturating_add(1);
577 Some(packets_size.saturating_add(4usize.saturating_mul(estimated_packets)))
578 }
579 
580 fn depacketize(
581 &mut self,
582 packet: &[u8],
583 out: &mut Vec<u8>,
584 codec_extra: &mut CodecExtra,
585 ) -> Result<(), PacketError> {
586 if packet.len() <= H266NALU_HEADER_SIZE {
587 return Err(PacketError::ErrShortPacket);
588 }
589 
590 let header = H266NALUHeader::new(packet[0], packet[1]);
591 if header.f() {
592 return Err(PacketError::ErrH266CorruptedPacket);
593 }
594 if header.z() {
595 // nuh_reserved_zero_bit: required to be 0 (RFC 9328 §1.1.4,
596 // reserved for future extensions). Tolerated and ignored here.
597 warn!("H266-DEPKT NAL with reserved Z bit set (ignored)");
598 }
599 
600 let mark_keyframe = |codec_extra: &mut CodecExtra, irap: bool| {
601 let is_keyframe = if let CodecExtra::H266(e) = codec_extra {
602 irap | e.is_keyframe
603 } else {
604 irap
605 };
606 *codec_extra = CodecExtra::H266(H266CodecExtra { is_keyframe });
607 };
608 
609 if header.is_fragmentation_unit() {
610 if packet.len() < H266NALU_HEADER_SIZE + 1 {
611 return Err(PacketError::ErrShortPacket);
612 }
613 let fu_header = H266FragmentationUnitHeader(packet[2]);
614 
615 // The first FU of a series carries the DONL field when enabled
616 // (RFC 9328 §4.3.3) — skip it.
617 let payload_start = if fu_header.s() && self.might_need_donl {
618 if packet.len() < H266NALU_HEADER_SIZE + 1 + 2 {
619 return Err(PacketError::ErrShortPacket);
620 }
621 H266NALU_HEADER_SIZE + 1 + 2
622 } else {
623 H266NALU_HEADER_SIZE + 1
624 };
625 let fu_payload = &packet[payload_start..];
626 
627 if fu_header.s() {
628 match &mut self.fu_buffer {
629 Some(buf) => buf.clear(),
630 None => self.fu_buffer = Some(Vec::with_capacity(128 * 1024)),
631 }
632 }
633 
634 if let Some(ref mut buf) = self.fu_buffer {
635 buf.extend_from_slice(fu_payload);
636 }
637 
638 if fu_header.e() {
639 if let Some(ref buf) = self.fu_buffer {
640 // Rebuild the original NAL header: byte0 unchanged,
641 // byte1 = orig type in bits 7..3 + original TID.
642 let orig_type = fu_header.fu_type();
643 let orig_b0 = packet[0];
644 let orig_b1 = (orig_type << 3) | (packet[1] & 0b111);
645 let irap = matches!(
646 orig_type,
647 H266NALU_IDR_W_RADL | H266NALU_IDR_N_LP | H266NALU_CRA
648 );
649 mark_keyframe(codec_extra, irap);
650 
651 out.extend_from_slice(ANNEXB_NALUSTART_CODE);
652 out.push(orig_b0);
653 out.push(orig_b1);
654 out.extend_from_slice(buf);
655 }
656 }
657 Ok(())
658 } else if header.is_aggregation_packet() {
659 let mut offset = H266NALU_HEADER_SIZE;
660 let mut unit_count = 0;
661 
662 // DONL precedes the first aggregation unit when enabled
663 // (RFC 9328 §4.3.2).
664 if self.might_need_donl {
665 if packet.len() < offset + 2 {
666 return Err(PacketError::ErrShortPacket);
667 }
668 offset += 2;
669 }
670 
671 // Remember where this AP's output started so a malformed
672 // packet doesn't leave partial NAL units in `out`.
673 let out_start = out.len();
674 
675 while offset < packet.len() {
676 if offset + 2 > packet.len() {
677 out.truncate(out_start);
678 return Err(PacketError::ErrShortPacket);
679 }
680 let nalu_size = ((packet[offset] as usize) << 8) | (packet[offset + 1] as usize);
681 offset += 2;
682 if offset + nalu_size > packet.len() || nalu_size < H266NALU_HEADER_SIZE {
683 // Length field inconsistent with the remaining bytes.
684 out.truncate(out_start);
685 return Err(PacketError::ErrShortPacket);
686 }
687 let nalu = &packet[offset..offset + nalu_size];
688 offset += nalu_size;
689 unit_count += 1;
690 
691 let inner = H266NALUHeader::new(nalu[0], nalu[1]);
692 
693 // NAL types 28 (AP) and 29 (FU) are RTP-only constructs and
694 // must never appear inside an aggregation packet
695 // (RFC 9328 §4.3.2).
696 if inner.is_aggregation_packet() || inner.is_fragmentation_unit() {
697 out.truncate(out_start);
698 return Err(PacketError::ErrH266CorruptedPacket);
699 }
700 
701 mark_keyframe(codec_extra, inner.is_irap());
702 
703 out.extend_from_slice(ANNEXB_NALUSTART_CODE);
704 out.extend_from_slice(nalu);
705 }
706 
707 if unit_count < 2 {
708 return Err(PacketError::ErrShortPacket);
709 }
710 
711 Ok(())
712 } else {
713 // Single NAL unit packet.
714 mark_keyframe(codec_extra, header.is_irap());
715 
716 out.extend_from_slice(ANNEXB_NALUSTART_CODE);
717 if self.might_need_donl {
718 // DONL (2 bytes) sits between the NAL header and the payload
719 // (RFC 9328 §4.3.1) — strip it.
720 if packet.len() < H266NALU_HEADER_SIZE + 2 + 1 {
721 return Err(PacketError::ErrShortPacket);
722 }
723 out.extend_from_slice(&packet[..H266NALU_HEADER_SIZE]);
724 out.extend_from_slice(&packet[H266NALU_HEADER_SIZE + 2..]);
725 } else {
726 out.extend_from_slice(packet);
727 }
728 Ok(())
729 }
730 }
731 
732 fn is_partition_head(&self, payload: &[u8]) -> bool {
733 if payload.len() < H266NALU_HEADER_SIZE {
734 return false;
735 }
736 let header = H266NALUHeader::new(payload[0], payload[1]);
737 if header.f() {
738 return true;
739 }
740 // Single NAL and AP packets are partition heads.
741 if !header.is_fragmentation_unit() {
742 return true;
743 }
744 // FU: head only when the S bit is set.
745 if payload.len() < H266NALU_HEADER_SIZE + 1 {
746 return false;
747 }
748 H266FragmentationUnitHeader(payload[2]).s()
749 }
750 
751 fn is_partition_tail(&self, marker: bool, payload: &[u8]) -> bool {
752 if payload.len() < H266NALU_HEADER_SIZE {
753 return false;
754 }
755 let header = H266NALUHeader::new(payload[0], payload[1]);
756 if header.is_fragmentation_unit() {
757 if payload.len() < H266NALU_HEADER_SIZE + 1 {
758 return false;
759 }
760 return H266FragmentationUnitHeader(payload[2]).e();
761 }
762 marker
763 }
764}
765 
766#[cfg(test)]
767mod test {
768 use super::*;
769 
770 type Result<T> = std::result::Result<T, PacketError>;
771 
772 // ========== Shared Test Utilities ==========
773 
774 /// Build a 2-byte H.266 NAL header: type in byte 1 bits 7..3.
775 fn hdr(typ: u8, layer: u8, tid: u8) -> [u8; 2] {
776 let h = H266NALUHeader::new_with_type(typ, layer, tid);
777 h.0.to_be_bytes()
778 }
779 
780 /// Annex-B encode a list of NAL units with 4-byte start codes.
781 fn annexb(nals: &[&[u8]]) -> Vec<u8> {
782 let mut out = Vec::new();
783 for n in nals {
784 out.extend_from_slice(&[0, 0, 0, 1]);
785 out.extend_from_slice(n);
786 }
787 out
788 }
789 
790 /// Make a NAL unit of `len` total bytes with the given type.
791 fn make_nal(typ: u8, len: usize) -> Vec<u8> {
792 assert!(len >= 2);
793 let mut n = hdr(typ, 0, 1).to_vec();
794 for i in 0..len - 2 {
795 n.push((i % 251) as u8);
796 }
797 n
798 }
799 
800 /// Make a NAL unit of `len` total bytes with type, layer id and tid.
801 fn make_nal_lt(typ: u8, len: usize, layer: u8, tid: u8) -> Vec<u8> {
802 assert!(len >= 2);
803 let mut n = hdr(typ, layer, tid).to_vec();
804 for i in 0..len - 2 {
805 n.push((i % 251) as u8);
806 }
807 n
808 }
809 
810 /// Reconstruct the original NAL unit from a sequence of FU packets.
811 /// Assumes all packets are FU (type 29) and are consecutive.
812 fn reconstruct_from_fu_packets(packets: &[Vec<u8>]) -> Vec<u8> {
813 let mut out = Vec::new();
814 let mut started = false;
815 
816 for pkt in packets {
817 assert!(pkt.len() >= 3);
818 let h = H266NALUHeader::new(pkt[0], pkt[1]);
819 assert!(h.is_fragmentation_unit());
820 
821 let fu = H266FragmentationUnitHeader(pkt[2]);
822 
823 if fu.s() {
824 // Rebuild the original 2-byte NAL header: byte 0 unchanged,
825 // byte 1 = fu_type in bits 7..3 + original TID.
826 out.push(pkt[0]);
827 out.push((fu.fu_type() << 3) | (pkt[1] & 0b111));
828 started = true;
829 }
830 
831 assert!(started, "FU sequence must start with S=1");
832 out.extend_from_slice(&pkt[3..]);
833 }
834 
835 out
836 }
837 
838 /// Depacketize a list of RTP payloads, returning the Annex-B output
839 /// and whether any keyframe was flagged.
840 fn depacketize_all(packets: &[Vec<u8>]) -> Result<(Vec<u8>, bool)> {
841 let mut depack = H266Depacketizer::default();
842 let mut out = Vec::new();
843 let mut keyframe = false;
844 for p in packets {
845 let mut extra = CodecExtra::None;
846 depack.depacketize(p, &mut out, &mut extra)?;
847 if let CodecExtra::H266(e) = extra {
848 keyframe |= e.is_keyframe;
849 }
850 }
851 Ok((out, keyframe))
852 }
853 
854 /// RFC 9328 bitfield correctness tests.
855 mod header_tests {
856 use super::*;
857 
858 /// Test H.266 NAL Unit header parsing and field extraction.
859 /// Verifies F bit, NAL type, layer_id, tid, and packet type
860 /// detection (AP/FU).
861 #[test]
862 fn test_h266_nalu_header() -> Result<()> {
863 #[derive(Default)]
864 struct TestType {
865 raw_header: &'static [u8],
866 
867 fbit: bool,
868 typ: u8,
869 layer_id: u8,
870 tid: u8,
871 
872 is_ap: bool,
873 is_fu: bool,
874 }
875 
876 let tests = vec![
877 // fbit (bit 7 of byte 0)
878 TestType {
879 raw_header: &[0x80, 0x00],
880 typ: 0,
881 layer_id: 0,
882 tid: 0,
883 fbit: true,
884 ..Default::default()
885 },
886 // TRAIL_NUT (type 0), tid 1
887 TestType {
888 raw_header: &[0x00, 0x01],
889 typ: 0,
890 layer_id: 0,
891 tid: 1,
892 ..Default::default()
893 },
894 // VPS_NUT (type 14)
895 TestType {
896 raw_header: &[0x00, 0x71],
897 typ: 14,
898 layer_id: 0,
899 tid: 1,
900 ..Default::default()
901 },
902 // SPS_NUT (type 15)
903 TestType {
904 raw_header: &[0x00, 0x79],
905 typ: 15,
906 layer_id: 0,
907 tid: 1,
908 ..Default::default()
909 },
910 // PPS_NUT (type 16)
911 TestType {
912 raw_header: &[0x00, 0x81],
913 typ: 16,
914 layer_id: 0,
915 tid: 1,
916 ..Default::default()
917 },
918 // PREFIX_SEI_NUT (type 23)
919 TestType {
920 raw_header: &[0x00, 0xB9],
921 typ: 23,
922 layer_id: 0,
923 tid: 1,
924 ..Default::default()
925 },
926 // Fragmentation Unit (type 29)
927 TestType {
928 raw_header: &[0x00, 0xE9],
929 typ: H266NALU_FRAGMENTATION_UNIT_TYPE,
930 layer_id: 0,
931 tid: 1,
932 is_fu: true,
933 ..Default::default()
934 },
935 // Aggregation Packet (type 28)
936 TestType {
937 raw_header: &[0x00, 0xE1],
938 typ: H266NALU_AGGREGATION_PACKET_TYPE,
939 layer_id: 0,
940 tid: 1,
941 is_ap: true,
942 ..Default::default()
943 },
944 // layer_id = 5 (byte 0 low 6 bits), IDR_W_RADL, tid 1
945 TestType {
946 raw_header: &[0x05, 0x39],
947 typ: 7,
948 layer_id: 5,
949 tid: 1,
950 ..Default::default()
951 },
952 // tid = 3
953 TestType {
954 raw_header: &[0x00, 0x0B],
955 typ: 1,
956 layer_id: 0,
957 tid: 3,
958 ..Default::default()
959 },
960 ];
961 
962 for (i, cur) in tests.iter().enumerate() {
963 let header = H266NALUHeader::new(cur.raw_header[0], cur.raw_header[1]);
964 
965 assert_eq!(header.f(), cur.fbit, "tc {i}: f bit");
966 assert_eq!(header.nalu_type(), cur.typ, "tc {i}: type");
967 assert_eq!(header.layer_id(), cur.layer_id, "tc {i}: layer_id");
968 assert_eq!(header.tid(), cur.tid, "tc {i}: tid");
969 assert_eq!(header.is_aggregation_packet(), cur.is_ap, "tc {i}: is_ap");
970 assert_eq!(header.is_fragmentation_unit(), cur.is_fu, "tc {i}: is_fu");
971 }
972 
973 Ok(())
974 }
975 
976 /// Test IRAP detection across all NAL types.
977 /// H.266 IRAP: IDR_W_RADL(7), IDR_N_LP(8), CRA(9). GDR(10) is not.
978 #[test]
979 fn test_h266_irap_detection() -> Result<()> {
980 for typ in 0u8..=31 {
981 let header = H266NALUHeader::new_with_type(typ, 0, 1);
982 let expected = (7..=9).contains(&typ);
983 assert_eq!(
984 header.is_irap(),
985 expected,
986 "type {typ} irap should be {expected}"
987 );
988 }
989 Ok(())
990 }
991 
992 /// Test FU header bit parsing/serialization including the H.266
993 /// specific P (last-FU-of-picture) bit.
994 #[test]
995 fn test_h266_fu_header() -> Result<()> {
996 struct TestType {
997 value: u8,
998 s: bool,
999 e: bool,
1000 p: bool,
1001 typ: u8,
1002 }
1003 
1004 let tests = vec![
1005 // S=1, type 7 (IDR_W_RADL)
1006 TestType {
1007 value: 0x87,
1008 s: true,
1009 e: false,
1010 p: false,
1011 typ: 7,
1012 },
1013 // E=1, type 7
1014 TestType {
1015 value: 0x47,
1016 s: false,
1017 e: true,
1018 p: false,
1019 typ: 7,
1020 },
1021 // P=1, type 7
1022 TestType {
1023 value: 0x27,
1024 s: false,
1025 e: false,
1026 p: true,
1027 typ: 7,
1028 },
1029 // E+P, type 0 (TRAIL)
1030 TestType {
1031 value: 0x60,
1032 s: false,
1033 e: true,
1034 p: true,
1035 typ: 0,
1036 },
1037 // S, type 31 (max 5-bit type)
1038 TestType {
1039 value: 0x9F,
1040 s: true,
1041 e: false,
1042 p: false,
1043 typ: 31,
1044 },
1045 // nothing set, type 9 (CRA)
1046 TestType {
1047 value: 0x09,
1048 s: false,
1049 e: false,
1050 p: false,
1051 typ: 9,
1052 },
1053 ];
1054 
1055 // F bit set rejects as corrupted.
1056 {
1057 let mut depack = H266Depacketizer::default();
1058 let mut out = Vec::new();
1059 let mut extra = CodecExtra::None;
1060 let res = depack.depacketize(&[0x80, 0x01, 0x93, 0xAF, 0xAF], &mut out, &mut extra);
1061 assert!(matches!(res, Err(PacketError::ErrH266CorruptedPacket)));
1062 }
1063 
1064 for (i, t) in tests.iter().enumerate() {
1065 let fu = H266FragmentationUnitHeader(t.value);
1066 assert_eq!(fu.s(), t.s, "tc {i}: s");
1067 assert_eq!(fu.e(), t.e, "tc {i}: e");
1068 assert_eq!(fu.p(), t.p, "tc {i}: p");
1069 assert_eq!(fu.fu_type(), t.typ, "tc {i}: type");
1070 
1071 // round-trip via constructor (p only via raw value)
1072 let built = H266FragmentationUnitHeader::new(t.s, t.e, t.p, t.typ);
1073 assert_eq!(built.0, t.value, "tc {i}: build");
1074 }
1075 
1076 Ok(())
1077 }
1078 }
1079 
1080 /// Depacketizer behavior tests, expressed against the depacketize() API.
1081 mod depacketizer_tests {
1082 use super::*;
1083 
1084 /// Valid/invalid single NAL unit packets.
1085 #[test]
1086 fn test_h266_single_nalunit_packet() -> Result<()> {
1087 // Valid single NAL (TRAIL, type 0) -> Annex-B out.
1088 let nal = make_nal(0, 5);
1089 let (out, keyframe) = depacketize_all(&[nal.clone()])?;
1090 assert_eq!(&out[..4], &[0, 0, 0, 1]);
1091 assert_eq!(&out[4..], &nal[..]);
1092 assert!(!keyframe);
1093 
1094 // IDR single NAL flags keyframe.
1095 let idr = make_nal(H266NALU_IDR_W_RADL, 5);
1096 let (out, keyframe) = depacketize_all(&[idr.clone()])?;
1097 assert_eq!(&out[4..], &idr[..]);
1098 assert!(keyframe);
1099 
1100 // Too short (header only, no payload beyond 2 bytes).
1101 let mut depack = H266Depacketizer::default();
1102 let mut out = Vec::new();
1103 let mut extra = CodecExtra::None;
1104 let err = depack.depacketize(&hdr(0, 0, 1), &mut out, &mut extra);
1105 assert_eq!(err, Err(PacketError::ErrShortPacket));
1106 
1107 // Empty packet.
1108 let err = depack.depacketize(&[], &mut out, &mut extra);
1109 assert_eq!(err, Err(PacketError::ErrShortPacket));
1110 
1111 // F bit set is rejected as corrupted.
1112 let err = depack.depacketize(&[0x80, 0x01, 0xAA, 0xBB], &mut out, &mut extra);
1113 assert!(matches!(err, Err(PacketError::ErrH266CorruptedPacket)));
1114 
1115 Ok(())
1116 }
1117 
1118 /// Valid/invalid aggregation packets.
1119 #[test]
1120 fn test_h266_aggregation_packet() -> Result<()> {
1121 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 6);
1122 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 4);
1123 
1124 // Build AP: [AP hdr][len][sps][len][pps]
1125 let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
1126 ap.extend_from_slice(&(sps.len() as u16).to_be_bytes());
1127 ap.extend_from_slice(&sps);
1128 ap.extend_from_slice(&(pps.len() as u16).to_be_bytes());
1129 ap.extend_from_slice(&pps);
1130 
1131 let (out, keyframe) = depacketize_all(&[ap])?;
1132 let expected = annexb(&[&sps, &pps]);
1133 assert_eq!(out, expected);
1134 assert!(!keyframe);
1135 
1136 // AP with a single unit violates RFC 9328 (>= 2 required).
1137 let mut ap1 = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
1138 ap1.extend_from_slice(&(sps.len() as u16).to_be_bytes());
1139 ap1.extend_from_slice(&sps);
1140 let mut depack = H266Depacketizer::default();
1141 let mut out = Vec::new();
1142 let mut extra = CodecExtra::None;
1143 let err = depack.depacketize(&ap1, &mut out, &mut extra);
1144 assert_eq!(err, Err(PacketError::ErrShortPacket));
1145 
1146 // Truncated size field: claims more bytes than present.
1147 let mut bad = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
1148 bad.extend_from_slice(&[0x00, 0x20]); // size 32 but nothing follows
1149 let err = depack.depacketize(&bad, &mut out, &mut extra);
1150 assert_eq!(err, Err(PacketError::ErrShortPacket));
1151 
1152 // AP containing an IRAP flags keyframe.
1153 let idr = make_nal(H266NALU_IDR_N_LP, 6);
1154 let mut ap_idr = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
1155 ap_idr.extend_from_slice(&(sps.len() as u16).to_be_bytes());
1156 ap_idr.extend_from_slice(&sps);
1157 ap_idr.extend_from_slice(&(idr.len() as u16).to_be_bytes());
1158 ap_idr.extend_from_slice(&idr);
1159 let (_, keyframe) = depacketize_all(&[ap_idr])?;
1160 assert!(keyframe);
1161 
1162 Ok(())
1163 }
1164 
1165 /// FU reassembly: start/middle/end, header reconstruction, orphan
1166 /// fragments.
1167 #[test]
1168 fn test_h266_fragmentation_unit_packet() -> Result<()> {
1169 // Original NAL: CRA (type 9), 8 bytes.
1170 let orig = make_nal(H266NALU_CRA, 8);
1171 let fu_hdr_bytes = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1);
1172 
1173 // Fragment payload (after the original 2-byte header).
1174 let payload = &orig[2..];
1175 let (a, b) = payload.split_at(payload.len() / 2);
1176 
1177 let mut fu1 = fu_hdr_bytes.to_vec();
1178 fu1.push(H266FragmentationUnitHeader::new(true, false, false, H266NALU_CRA).0);
1179 fu1.extend_from_slice(a);
1180 
1181 let mut fu2 = fu_hdr_bytes.to_vec();
1182 fu2.push(H266FragmentationUnitHeader::new(false, true, false, H266NALU_CRA).0);
1183 fu2.extend_from_slice(b);
1184 
1185 let (out, keyframe) = depacketize_all(&[fu1, fu2])?;
1186 assert_eq!(out, annexb(&[&orig]));
1187 assert!(keyframe, "CRA via FU must flag keyframe");
1188 
1189 // FU packet too short for an FU header.
1190 let mut depack = H266Depacketizer::default();
1191 let mut out = Vec::new();
1192 let mut extra = CodecExtra::None;
1193 let err = depack.depacketize(&fu_hdr_bytes, &mut out, &mut extra);
1194 assert_eq!(err, Err(PacketError::ErrShortPacket));
1195 
1196 // Orphan middle/end fragment on a fresh depacketizer: silently
1197 // consumed, no output.
1198 let mut orphan = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
1199 orphan.push(H266FragmentationUnitHeader::new(false, true, false, 0).0);
1200 orphan.extend_from_slice(&[0xAA, 0xBB]);
1201 let mut depack = H266Depacketizer::default();
1202 let mut out = Vec::new();
1203 depack.depacketize(&orphan, &mut out, &mut extra)?;
1204 assert!(out.is_empty(), "orphan FU must not produce output");
1205 
1206 Ok(())
1207 }
1208 
1209 /// General depacketizer error/dispatch table.
1210 #[test]
1211 fn test_h266_packet() -> Result<()> {
1212 struct TestType {
1213 raw: &'static [u8],
1214 expect_err: bool, // all error cases expect ErrShortPacket
1215 }
1216 
1217 let tests = vec![
1218 // empty
1219 TestType {
1220 raw: &[],
1221 expect_err: true,
1222 },
1223 // header only
1224 TestType {
1225 raw: &[0x00, 0x01],
1226 expect_err: true,
1227 },
1228 // valid single NAL (TRAIL)
1229 TestType {
1230 raw: &[0x00, 0x01, 0xAB, 0xCD, 0xEF],
1231 expect_err: false,
1232 },
1233 // FU too short (no FU header)
1234 TestType {
1235 raw: &[0x00, 0xE9],
1236 expect_err: true,
1237 },
1238 // valid FU start fragment (S=1, type 7)
1239 TestType {
1240 raw: &[0x00, 0xE9, 0x87, 0x11, 0x22],
1241 expect_err: false,
1242 },
1243 // AP with one unit only -> error
1244 TestType {
1245 raw: &[0x00, 0xE1, 0x00, 0x03, 0x00, 0x01, 0xAA],
1246 expect_err: true,
1247 },
1248 // valid AP with two units
1249 TestType {
1250 raw: &[
1251 0x00, 0xE1, // AP header
1252 0x00, 0x03, 0x00, 0x01, 0xAA, // unit 1 (TRAIL)
1253 0x00, 0x03, 0x00, 0x01, 0xBB, // unit 2 (TRAIL)
1254 ],
1255 expect_err: false,
1256 },
1257 ];
1258 
1259 for (i, t) in tests.iter().enumerate() {
1260 let mut depack = H266Depacketizer::default();
1261 let mut out = Vec::new();
1262 let mut extra = CodecExtra::None;
1263 let res = depack.depacketize(t.raw, &mut out, &mut extra);
1264 if t.expect_err {
1265 assert!(
1266 matches!(res, Err(PacketError::ErrShortPacket)),
1267 "tc {i} should error, got {res:?}"
1268 );
1269 } else {
1270 assert!(res.is_ok(), "tc {i} should succeed: {res:?}");
1271 }
1272 }
1273 
1274 Ok(())
1275 }
1276 }
1277 
1278 /// Packetizer behavior tests.
1279 mod packetizer_tests {
1280 use super::*;
1281 
1282 /// A payload without start codes is treated as one NAL unit.
1283 #[test]
1284 fn test_h266_packetizer_single_nalu() -> Result<()> {
1285 let mut pck = H266Packetizer::default();
1286 let nal = make_nal(0, 10);
1287 let packets = pck.packetize(1200, &nal)?;
1288 assert_eq!(packets.len(), 1);
1289 assert_eq!(packets[0], nal);
1290 Ok(())
1291 }
1292 
1293 /// Annex-B input is split into one packet per NAL.
1294 #[test]
1295 fn test_h266_packetizer_annexb_split() -> Result<()> {
1296 let n1 = make_nal(0, 6);
1297 let n2 = make_nal(1, 7);
1298 // mix 4-byte and 3-byte start codes
1299 let mut payload = Vec::new();
1300 payload.extend_from_slice(&[0, 0, 0, 1]);
1301 payload.extend_from_slice(&n1);
1302 payload.extend_from_slice(&[0, 0, 1]);
1303 payload.extend_from_slice(&n2);
1304 
1305 let mut pck = H266Packetizer::default();
1306 let packets = pck.packetize(1200, &payload)?;
1307 assert_eq!(packets.len(), 2);
1308 assert_eq!(packets[0], n1);
1309 assert_eq!(packets[1], n2);
1310 Ok(())
1311 }
1312 
1313 /// NAL exactly == mtu stays a single packet (no fragmentation).
1314 #[test]
1315 fn test_h266_packetizer_single_nalu_no_fragment() -> Result<()> {
1316 let mut pck = H266Packetizer::default();
1317 let nal = make_nal(0, 100);
1318 let packets = pck.packetize(100, &nal)?;
1319 assert_eq!(packets.len(), 1);
1320 assert_eq!(packets[0], nal);
1321 Ok(())
1322 }
1323 
1324 /// Large NAL is fragmented into FUs and can be reconstructed.
1325 #[test]
1326 fn test_h266_packetizer_fu_fragmentation_roundtrip_payload() -> Result<()> {
1327 let mut pck = H266Packetizer::default();
1328 let nal = make_nal(H266NALU_CRA, 3000);
1329 let mtu = 200;
1330 let packets = pck.packetize(mtu, &nal)?;
1331 assert!(packets.len() > 1);
1332 for p in &packets {
1333 assert!(p.len() <= mtu);
1334 let h = H266NALUHeader::new(p[0], p[1]);
1335 assert!(h.is_fragmentation_unit());
1336 }
1337 let rebuilt = reconstruct_from_fu_packets(&packets);
1338 assert_eq!(rebuilt, nal);
1339 Ok(())
1340 }
1341 
1342 /// VPS+SPS+PPS are aggregated into one AP before the next NAL.
1343 #[test]
1344 fn test_h266_packetizer_emits_ap_for_vps_sps_pps() -> Result<()> {
1345 let vps = make_nal(H266NALU_VPS_NALU_TYPE, 8);
1346 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 12);
1347 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
1348 let idr = make_nal(H266NALU_IDR_W_RADL, 20);
1349 let payload = annexb(&[&vps, &sps, &pps, &idr]);
1350 
1351 let mut pck = H266Packetizer::default();
1352 let packets = pck.packetize(1200, &payload)?;
1353 assert_eq!(packets.len(), 2, "AP + IDR single expected");
1354 
1355 // packets[0] is the AP with 3 units.
1356 let ap = &packets[0];
1357 let h = H266NALUHeader::new(ap[0], ap[1]);
1358 assert!(h.is_aggregation_packet());
1359 let mut units = Vec::new();
1360 let mut off = 2;
1361 while off + 2 <= ap.len() {
1362 let sz = u16::from_be_bytes([ap[off], ap[off + 1]]) as usize;
1363 off += 2;
1364 units.push(ap[off..off + sz].to_vec());
1365 off += sz;
1366 }
1367 assert_eq!(units.len(), 3);
1368 assert_eq!(units[0], vps);
1369 assert_eq!(units[1], sps);
1370 assert_eq!(units[2], pps);
1371 
1372 // packets[1] is the IDR single NAL.
1373 assert_eq!(packets[1], idr);
1374 Ok(())
1375 }
1376 
1377 /// When the AP would exceed the MTU, parameter sets fall back to
1378 /// single NAL packets.
1379 #[test]
1380 fn test_h266_packetizer_ap_exceeds_mtu_fallback() -> Result<()> {
1381 let mtu = 700;
1382 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 600);
1383 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 600);
1384 let idr = make_nal(H266NALU_IDR_W_RADL, 50);
1385 let payload = annexb(&[&sps, &pps, &idr]);
1386 
1387 let mut pck = H266Packetizer::default();
1388 let packets = pck.packetize(mtu, &payload)?;
1389 
1390 // AP would be 2 + (2+600)*2 = 1206 > 700 -> fallback singles.
1391 assert_eq!(packets.len(), 3);
1392 assert_eq!(packets[0], sps);
1393 assert_eq!(packets[1], pps);
1394 assert_eq!(packets[2], idr);
1395 Ok(())
1396 }
1397 
1398 /// AUD and filler NALs are dropped by the packetizer.
1399 #[test]
1400 fn test_h266_packetizer_drops_aud_and_filler() -> Result<()> {
1401 let aud = make_nal(H266NALU_AUD_NALU_TYPE, 4);
1402 let fd = make_nal(H266NALU_FILLER_NALU_TYPE, 10);
1403 let trail = make_nal(0, 8);
1404 let payload = annexb(&[&aud, &trail, &fd]);
1405 
1406 let mut pck = H266Packetizer::default();
1407 let packets = pck.packetize(1200, &payload)?;
1408 assert_eq!(packets.len(), 1);
1409 assert_eq!(packets[0], trail);
1410 Ok(())
1411 }
1412 
1413 /// is_marker: last packet of the AU carries the marker.
1414 #[test]
1415 fn test_h266_packetizer_marker() -> Result<()> {
1416 let mut pck = H266Packetizer::default();
1417 assert!(pck.is_marker(&[], None, true));
1418 assert!(!pck.is_marker(&[], None, false));
1419 Ok(())
1420 }
1421 
1422 /// FU S/E flags: first fragment S, last fragment E, middles neither.
1423 /// The P bit is never set (RFC 9328 only mandates P=0 for
1424 /// non-final fragments; always 0 is compliant).
1425 #[test]
1426 fn test_h266_fragmentation_start_end_flags() -> Result<()> {
1427 let mut pck = H266Packetizer::default();
1428 let nal = make_nal(0, 1000);
1429 let packets = pck.packetize(100, &nal)?;
1430 assert!(packets.len() >= 3);
1431 
1432 for (i, p) in packets.iter().enumerate() {
1433 let fu = H266FragmentationUnitHeader(p[2]);
1434 let is_first = i == 0;
1435 let is_last = i == packets.len() - 1;
1436 assert_eq!(fu.s(), is_first, "pkt {i} S");
1437 assert_eq!(fu.e(), is_last, "pkt {i} E");
1438 assert!(!fu.p(), "pkt {i} P never set");
1439 assert_eq!(fu.fu_type(), 0, "pkt {i} type");
1440 }
1441 Ok(())
1442 }
1443 
1444 /// Exact boundary: len == mtu single; len == mtu+1 fragments, with
1445 /// exact fragment sizes.
1446 #[test]
1447 fn test_h266_packetizer_exact_fu_boundary_mtu() -> Result<()> {
1448 let mtu = 100;
1449 
1450 // len == mtu -> single
1451 let mut pck = H266Packetizer::default();
1452 let nal = make_nal(0, mtu);
1453 let packets = pck.packetize(mtu, &nal)?;
1454 assert_eq!(packets.len(), 1);
1455 
1456 // len == mtu + 1 -> FU; payload 99 bytes; max fragment = mtu - 3 = 97
1457 let mut pck = H266Packetizer::default();
1458 let nal = make_nal(0, mtu + 1);
1459 let packets = pck.packetize(mtu, &nal)?;
1460 assert_eq!(packets.len(), 2);
1461 assert_eq!(packets[0].len(), 3 + 97);
1462 assert_eq!(packets[1].len(), 3 + 2);
1463 assert_eq!(reconstruct_from_fu_packets(&packets), nal);
1464 Ok(())
1465 }
1466 
1467 /// MTU sweep roundtrip.
1468 #[test]
1469 fn test_h266_mtu_variation() -> Result<()> {
1470 for mtu in [50usize, 128, 512, 1200] {
1471 let mut pck = H266Packetizer::default();
1472 let nal = make_nal(H266NALU_IDR_N_LP, 1000);
1473 let packets = pck.packetize(mtu, &nal)?;
1474 for p in &packets {
1475 assert!(p.len() <= mtu, "mtu {mtu}: packet {} too big", p.len());
1476 }
1477 if nal.len() <= mtu {
1478 assert_eq!(packets.len(), 1);
1479 } else {
1480 assert_eq!(reconstruct_from_fu_packets(&packets), nal, "mtu {mtu}");
1481 }
1482 }
1483 Ok(())
1484 }
1485 
1486 #[test]
1487 fn packetize_respects_mtu() -> Result<()> {
1488 // 2-byte NAL header (non-parameter-set type) + payload.
1489 let nal = make_nal(0, 2002);
1490 for &mtu in &[100usize, 300, 600, 1200] {
1491 let mut pck = H266Packetizer::default();
1492 let pkts = pck.packetize(mtu, &nal)?;
1493 assert!(!pkts.is_empty(), "H266 produced no packets at mtu {mtu}");
1494 for (i, pkt) in pkts.iter().enumerate() {
1495 assert!(
1496 pkt.len() <= mtu,
1497 "H266 packet {i} size {} > mtu {mtu}",
1498 pkt.len()
1499 );
1500 }
1501 }
1502 Ok(())
1503 }
1504 }
1505 
1506 /// Packetize → depacketize roundtrips.
1507 mod roundtrip_tests {
1508 use super::*;
1509 
1510 /// FU roundtrip.
1511 #[test]
1512 fn test_h266_fu_roundtrip_with_depacketizer() -> Result<()> {
1513 let mut pck = H266Packetizer::default();
1514 let nal = make_nal(H266NALU_IDR_W_RADL, 2500);
1515 let packets = pck.packetize(400, &nal)?;
1516 assert!(packets.len() > 1);
1517 
1518 let (out, keyframe) = depacketize_all(&packets)?;
1519 assert_eq!(out, annexb(&[&nal]));
1520 assert!(keyframe);
1521 Ok(())
1522 }
1523 
1524 /// AP roundtrip.
1525 #[test]
1526 fn test_h266_ap_roundtrip_with_depacketizer() -> Result<()> {
1527 let vps = make_nal(H266NALU_VPS_NALU_TYPE, 8);
1528 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 12);
1529 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
1530 let idr = make_nal(H266NALU_IDR_N_LP, 40);
1531 let payload = annexb(&[&vps, &sps, &pps, &idr]);
1532 
1533 let mut pck = H266Packetizer::default();
1534 let packets = pck.packetize(1200, &payload)?;
1535 
1536 let (out, keyframe) = depacketize_all(&packets)?;
1537 assert_eq!(out, annexb(&[&vps, &sps, &pps, &idr]));
1538 assert!(keyframe);
1539 Ok(())
1540 }
1541 
1542 /// Single NAL roundtrip.
1543 #[test]
1544 fn test_h266_single_nalu_roundtrip_with_depacketizer() -> Result<()> {
1545 let mut pck = H266Packetizer::default();
1546 let nal = make_nal(0, 50);
1547 let packets = pck.packetize(1200, &nal)?;
1548 assert_eq!(packets.len(), 1);
1549 
1550 let (out, keyframe) = depacketize_all(&packets)?;
1551 assert_eq!(out, annexb(&[&nal]));
1552 assert!(!keyframe);
1553 Ok(())
1554 }
1555 
1556 /// Mixed AU: params (AP) + small (single) + large (FU) NALs.
1557 #[test]
1558 fn test_h266_mixed_packet_types_roundtrip() -> Result<()> {
1559 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 10);
1560 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
1561 let small = make_nal(H266NALU_IDR_W_RADL, 100);
1562 let large = make_nal(0, 1500);
1563 let payload = annexb(&[&sps, &pps, &small, &large]);
1564 
1565 let mut pck = H266Packetizer::default();
1566 let mtu = 600;
1567 let packets = pck.packetize(mtu, &payload)?;
1568 
1569 // AP (sps+pps) + 1 single + >=3 FUs
1570 assert!(packets.len() >= 5, "got {}", packets.len());
1571 for p in &packets {
1572 assert!(p.len() <= mtu);
1573 }
1574 
1575 let (out, keyframe) = depacketize_all(&packets)?;
1576 assert_eq!(out, annexb(&[&sps, &pps, &small, &large]));
1577 assert!(keyframe);
1578 Ok(())
1579 }
1580 
1581 /// Multi-NAL Annex-B with mixed start-code lengths.
1582 #[test]
1583 fn test_h266_annexb_roundtrip_with_depacketizer() -> Result<()> {
1584 let n1 = make_nal(1, 30);
1585 let n2 = make_nal(2, 40);
1586 let n3 = make_nal(0, 25);
1587 
1588 let mut payload = Vec::new();
1589 payload.extend_from_slice(&[0, 0, 1]); // 3-byte start code
1590 payload.extend_from_slice(&n1);
1591 payload.extend_from_slice(&[0, 0, 0, 1]); // 4-byte
1592 payload.extend_from_slice(&n2);
1593 payload.extend_from_slice(&[0, 0, 1]);
1594 payload.extend_from_slice(&n3);
1595 
1596 let mut pck = H266Packetizer::default();
1597 let packets = pck.packetize(1200, &payload)?;
1598 assert_eq!(packets.len(), 3);
1599 
1600 let (out, _) = depacketize_all(&packets)?;
1601 assert_eq!(out, annexb(&[&n1, &n2, &n3]));
1602 Ok(())
1603 }
1604 }
1605 
1606 /// Partition head/tail classification.
1607 mod partition_tests {
1608 use super::*;
1609 #[test]
1610 fn test_h266_is_partition_head() -> Result<()> {
1611 let depack = H266Depacketizer::default();
1612 
1613 // Too short -> false.
1614 assert!(!depack.is_partition_head(&[0x00]));
1615 
1616 // Single NAL -> head.
1617 assert!(depack.is_partition_head(&make_nal(0, 5)));
1618 
1619 // AP -> head.
1620 assert!(depack.is_partition_head(&hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1)));
1621 
1622 // FU with S=1 -> head.
1623 let mut fu_s = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
1624 fu_s.push(H266FragmentationUnitHeader::new(true, false, false, 0).0);
1625 assert!(depack.is_partition_head(&fu_s));
1626 
1627 // FU middle (no S) -> not head.
1628 let mut fu_m = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
1629 fu_m.push(H266FragmentationUnitHeader::new(false, false, false, 0).0);
1630 assert!(!depack.is_partition_head(&fu_m));
1631 
1632 // FU header missing -> not head.
1633 assert!(!depack.is_partition_head(&hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1)));
1634 
1635 // F bit -> treated as head (error case).
1636 assert!(depack.is_partition_head(&[0x80, 0x01, 0x00]));
1637 
1638 Ok(())
1639 }
1640 #[test]
1641 fn test_h266_is_partition_tail() -> Result<()> {
1642 let depack = H266Depacketizer::default();
1643 
1644 // Too short -> false even with marker.
1645 assert!(!depack.is_partition_tail(true, &[0x00]));
1646 
1647 // Single NAL: marker decides.
1648 let single = make_nal(0, 5);
1649 assert!(depack.is_partition_tail(true, &single));
1650 assert!(!depack.is_partition_tail(false, &single));
1651 
1652 // FU with E=1 -> tail regardless of marker.
1653 let mut fu_e = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
1654 fu_e.push(H266FragmentationUnitHeader::new(false, true, false, 0).0);
1655 assert!(depack.is_partition_tail(false, &fu_e));
1656 
1657 // FU without E -> not tail, marker ignored for FU.
1658 let mut fu_s = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1).to_vec();
1659 fu_s.push(H266FragmentationUnitHeader::new(true, false, false, 0).0);
1660 assert!(!depack.is_partition_tail(true, &fu_s));
1661 
1662 // FU header missing -> false.
1663 assert!(!depack.is_partition_tail(true, &hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1)));
1664 
1665 Ok(())
1666 }
1667 }
1668 
1669 /// MTU and malformed-input edge cases.
1670 mod edge_tests {
1671 use super::*;
1672 #[test]
1673 fn test_h266_zero_mtu() -> Result<()> {
1674 let mut pck = H266Packetizer::default();
1675 let packets = pck.packetize(0, &make_nal(0, 10))?;
1676 assert!(packets.is_empty());
1677 Ok(())
1678 }
1679 #[test]
1680 fn test_h266_empty_nalu() -> Result<()> {
1681 let mut pck = H266Packetizer::default();
1682 let packets = pck.packetize(1200, &[])?;
1683 assert!(packets.is_empty());
1684 Ok(())
1685 }
1686 
1687 /// MTU below the minimum (header+fu+1) yields no packets.
1688 #[test]
1689 fn test_h266_mtu_smaller_than_fu_overhead() -> Result<()> {
1690 let mut pck = H266Packetizer::default();
1691 // MIN_MTU is 4; mtu 3 == FU overhead exactly -> cannot carry payload.
1692 let packets = pck.packetize(3, &make_nal(0, 100))?;
1693 assert!(packets.is_empty());
1694 Ok(())
1695 }
1696 
1697 /// Minimal viable MTU fragments 1 byte at a time and round-trips.
1698 #[test]
1699 fn test_h266_fu_minimal_mtu() -> Result<()> {
1700 let mut pck = H266Packetizer::default();
1701 let nal = make_nal(0, 10);
1702 let packets = pck.packetize(MIN_MTU, &nal)?;
1703 // payload bytes = 8, one per fragment
1704 assert_eq!(packets.len(), 8);
1705 for p in &packets {
1706 assert_eq!(p.len(), 4);
1707 }
1708 assert_eq!(reconstruct_from_fu_packets(&packets), nal);
1709 Ok(())
1710 }
1711 
1712 /// MTU above MAX_PACKET_SIZE is clamped.
1713 #[test]
1714 fn test_h266_fu_mtu_exceeds_max_packet_size() -> Result<()> {
1715 let mut pck = H266Packetizer::default();
1716 let nal = make_nal(0, 3000);
1717 let packets = pck.packetize(5000, &nal)?;
1718 assert!(packets.len() > 1, "must fragment despite huge mtu");
1719 for p in &packets {
1720 assert!(p.len() <= MAX_PACKET_SIZE);
1721 }
1722 assert_eq!(reconstruct_from_fu_packets(&packets), nal);
1723 Ok(())
1724 }
1725 
1726 /// A NAL smaller than the 2-byte header is skipped.
1727 #[test]
1728 fn test_h266_nalu_smaller_than_header() -> Result<()> {
1729 let mut pck = H266Packetizer::default();
1730 // one-byte "NAL" between start codes
1731 let mut payload = Vec::new();
1732 payload.extend_from_slice(&[0, 0, 0, 1]);
1733 payload.push(0x00);
1734 let packets = pck.packetize(1200, &payload)?;
1735 assert!(packets.is_empty());
1736 Ok(())
1737 }
1738 
1739 /// Exact AP byte layout.
1740 #[test]
1741 fn test_h266_aggregated_exact_layout() -> Result<()> {
1742 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 4);
1743 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 3);
1744 let idr = make_nal(H266NALU_IDR_W_RADL, 5);
1745 let payload = annexb(&[&sps, &pps, &idr]);
1746 
1747 let mut pck = H266Packetizer::default();
1748 let packets = pck.packetize(1200, &payload)?;
1749 assert_eq!(packets.len(), 2);
1750 
1751 // Expected AP: [0x00, (28<<3)|tid=1] [00 04 sps] [00 03 pps]
1752 let mut expected = vec![0x00, (H266NALU_AGGREGATION_PACKET_TYPE << 3) | 1];
1753 expected.extend_from_slice(&(sps.len() as u16).to_be_bytes());
1754 expected.extend_from_slice(&sps);
1755 expected.extend_from_slice(&(pps.len() as u16).to_be_bytes());
1756 expected.extend_from_slice(&pps);
1757 assert_eq!(packets[0], expected);
1758 Ok(())
1759 }
1760 
1761 /// Synthetic "real-world" AU shape: params + IDR slice, verified
1762 /// end-to-end (structure synthesized for VVC).
1763 #[test]
1764 fn test_h266_packet_synthetic_au() -> Result<()> {
1765 let sps = make_nal(H266NALU_SPS_NALU_TYPE, 45);
1766 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 12);
1767 let aps = make_nal(17, 30); // PREFIX_APS passes through as single
1768 let idr = make_nal(H266NALU_IDR_N_LP, 800);
1769 let payload = annexb(&[&sps, &pps, &aps, &idr]);
1770 
1771 let mut pck = H266Packetizer::default();
1772 let packets = pck.packetize(1200, &payload)?;
1773 
1774 // AP(sps+pps) + APS single + IDR single
1775 assert_eq!(packets.len(), 3);
1776 
1777 let (out, keyframe) = depacketize_all(&packets)?;
1778 assert_eq!(out, annexb(&[&sps, &pps, &aps, &idr]));
1779 assert!(keyframe);
1780 Ok(())
1781 }
1782 }
1783 
1784 /// Keyframe detection on raw RTP payloads.
1785 #[test]
1786 fn test_detect_h266_keyframe() {
1787 // Empty / too short payload
1788 assert!(!detect_h266_keyframe(&[]));
1789 assert!(!detect_h266_keyframe(&[0x00]));
1790 
1791 // Single IRAP NALs
1792 for typ in [H266NALU_IDR_W_RADL, H266NALU_IDR_N_LP, H266NALU_CRA] {
1793 let h = H266NALUHeader::new_with_type(typ, 0, 1);
1794 assert!(
1795 detect_h266_keyframe(&h.0.to_be_bytes()),
1796 "type {typ} must be keyframe"
1797 );
1798 }
1799 
1800 // GDR (type 10) is not treated as IRAP here.
1801 let gdr = H266NALUHeader::new_with_type(H266NALU_GDR, 0, 1);
1802 assert!(!detect_h266_keyframe(&gdr.0.to_be_bytes()));
1803 
1804 // TRAIL (type 0) is not a keyframe.
1805 let trail = H266NALUHeader::new_with_type(0, 0, 1);
1806 assert!(!detect_h266_keyframe(&trail.0.to_be_bytes()));
1807 
1808 // Aggregation packet with an IDR inside.
1809 let ap = H266NALUHeader::new_with_type(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1);
1810 let idr = H266NALUHeader::new_with_type(H266NALU_IDR_W_RADL, 0, 1);
1811 let mut ap_with_idr = Vec::new();
1812 ap_with_idr.extend_from_slice(&ap.0.to_be_bytes());
1813 ap_with_idr.extend_from_slice(&[0x00, 0x03]); // unit size 3
1814 ap_with_idr.extend_from_slice(&idr.0.to_be_bytes());
1815 ap_with_idr.push(0x00);
1816 assert!(detect_h266_keyframe(&ap_with_idr));
1817 
1818 // Aggregation packet without IRAP.
1819 let non_irap = H266NALUHeader::new_with_type(1, 0, 1);
1820 let mut ap_no_irap = Vec::new();
1821 ap_no_irap.extend_from_slice(&ap.0.to_be_bytes());
1822 ap_no_irap.extend_from_slice(&[0x00, 0x03]);
1823 ap_no_irap.extend_from_slice(&non_irap.0.to_be_bytes());
1824 ap_no_irap.push(0x00);
1825 assert!(!detect_h266_keyframe(&ap_no_irap));
1826 
1827 // FU start fragment with IDR type.
1828 let fu = H266NALUHeader::new_with_type(H266NALU_FRAGMENTATION_UNIT_TYPE, 0, 1);
1829 let mut fu_start_idr = Vec::new();
1830 fu_start_idr.extend_from_slice(&fu.0.to_be_bytes());
1831 fu_start_idr.push(0x80 | H266NALU_IDR_W_RADL); // S=1, type=7
1832 fu_start_idr.extend_from_slice(&[0x00, 0x00]);
1833 assert!(detect_h266_keyframe(&fu_start_idr));
1834 
1835 // FU continuation fragment (S=0) - cannot detect.
1836 let mut fu_cont = Vec::new();
1837 fu_cont.extend_from_slice(&fu.0.to_be_bytes());
1838 fu_cont.push(H266NALU_IDR_W_RADL); // S=0
1839 fu_cont.extend_from_slice(&[0x00, 0x00]);
1840 assert!(!detect_h266_keyframe(&fu_cont));
1841 
1842 // FU too short (no FU header byte).
1843 assert!(!detect_h266_keyframe(&fu.0.to_be_bytes()));
1844 }
1845 
1846 /// DONL tests (RFC 9328 §4.3, sprop-max-don-diff > 0).
1847 ///
1848 mod donl_tests {
1849 use super::*;
1850 #[test]
1851 fn test_h266_donl_single_nal_round_trip() -> Result<()> {
1852 let mut packetizer = H266Packetizer::default();
1853 packetizer.with_donl(true);
1854 
1855 let mut depacketizer = H266Depacketizer::default();
1856 depacketizer.with_donl(true);
1857 
1858 // Single NAL unit (TRAIL, type 1): [0x00, 0x09]
1859 let nalu = vec![0x00, 0x09, 0xDE, 0xAD, 0xBE, 0xEF];
1860 
1861 let packets = packetizer.packetize(MAX_PACKET_SIZE, &nalu)?;
1862 assert_eq!(packets.len(), 1, "Should produce 1 packet");
1863 
1864 let packet = &packets[0];
1865 
1866 // Packet structure: [NAL_HDR (2)] [DONL (2)] [PAYLOAD]
1867 assert!(packet.len() >= 6);
1868 assert_eq!(packet[0], 0x00);
1869 assert_eq!(packet[1], 0x09);
1870 
1871 let donl = u16::from_be_bytes([packet[2], packet[3]]);
1872 assert_eq!(donl, 0, "DONL should be 0 for first NAL");
1873 
1874 assert_eq!(packet[4], 0xDE);
1875 assert_eq!(packet[5], 0xAD);
1876 
1877 // Depacketize: Annex-B output without DONL.
1878 let mut out = Vec::new();
1879 let mut codec_extra = CodecExtra::None;
1880 depacketizer.depacketize(packet, &mut out, &mut codec_extra)?;
1881 
1882 assert_eq!(out.len(), 10);
1883 assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
1884 assert_eq!(&out[4..6], &[0x00, 0x09]);
1885 assert_eq!(&out[6..10], &[0xDE, 0xAD, 0xBE, 0xEF]);
1886 
1887 Ok(())
1888 }
1889 #[test]
1890 fn test_h266_donl_fragmentation_round_trip() -> Result<()> {
1891 let mut packetizer = H266Packetizer::default();
1892 packetizer.with_donl(true);
1893 
1894 let mut depacketizer = H266Depacketizer::default();
1895 depacketizer.with_donl(true);
1896 
1897 let mut nalu = vec![0x00, 0x09]; // TRAIL (type 1)
1898 nalu.extend(vec![0xAA; 3000]);
1899 
1900 let packets = packetizer.packetize(1200, &nalu)?;
1901 assert!(packets.len() >= 3);
1902 
1903 // First FU: [FU_HDR (2)] [FU_HEADER (1)] [DONL (2)] [PAYLOAD]
1904 let first_packet = &packets[0];
1905 let fu_header = H266NALUHeader::new(first_packet[0], first_packet[1]);
1906 assert_eq!(fu_header.nalu_type(), H266NALU_FRAGMENTATION_UNIT_TYPE);
1907 
1908 let fu_hdr = H266FragmentationUnitHeader(first_packet[2]);
1909 assert!(fu_hdr.s());
1910 assert!(!fu_hdr.e());
1911 
1912 let donl = u16::from_be_bytes([first_packet[3], first_packet[4]]);
1913 assert_eq!(donl, 0, "DONL should be 0 for first NAL");
1914 
1915 // Middle fragments: no S/E, payload right after FU header (no DONL).
1916 if packets.len() > 2 {
1917 let middle_fu_hdr = H266FragmentationUnitHeader(packets[1][2]);
1918 assert!(!middle_fu_hdr.s());
1919 assert!(!middle_fu_hdr.e());
1920 }
1921 
1922 let last_fu_hdr = H266FragmentationUnitHeader(packets[packets.len() - 1][2]);
1923 assert!(!last_fu_hdr.s());
1924 assert!(last_fu_hdr.e());
1925 
1926 // Depacketize all fragments.
1927 let mut out = Vec::new();
1928 let mut codec_extra = CodecExtra::None;
1929 for packet in &packets {
1930 depacketizer.depacketize(packet, &mut out, &mut codec_extra)?;
1931 }
1932 
1933 assert_eq!(out.len(), 4 + nalu.len());
1934 assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
1935 assert_eq!(&out[4..], &nalu[..]);
1936 
1937 Ok(())
1938 }
1939 #[test]
1940 fn test_h266_donl_aggregation_round_trip() -> Result<()> {
1941 let mut packetizer = H266Packetizer::default();
1942 packetizer.with_donl(true);
1943 
1944 let mut depacketizer = H266Depacketizer::default();
1945 depacketizer.with_donl(true);
1946 
1947 let vps = vec![0x00, 0x71, 0x0C, 0x01, 0xFF, 0xFF]; // VPS (14)
1948 let sps = vec![0x00, 0x79, 0x01, 0x50, 0x00, 0x00]; // SPS (15)
1949 let pps = vec![0x00, 0x81, 0xC0, 0xF3, 0xC0, 0x02]; // PPS (16)
1950 let payload = annexb(&[&vps, &sps, &pps]);
1951 
1952 let packets = packetizer.packetize(MAX_PACKET_SIZE, &payload)?;
1953 assert_eq!(packets.len(), 0, "Parameter sets should be cached");
1954 
1955 // VCL NAL triggers AP emission.
1956 let vcl = vec![0x00, 0x09, 0x11, 0x22, 0x33];
1957 let vcl_packets = packetizer.packetize(MAX_PACKET_SIZE, &annexb(&[&vcl]))?;
1958 assert_eq!(vcl_packets.len(), 2, "Should produce AP + VCL");
1959 
1960 // AP structure (RFC 9328 §4.3.2):
1961 // [AP_HDR (2)] [DONL (2)] [SIZE (2)] [NALU] [SIZE (2)] [NALU] ...
1962 let ap_packet = &vcl_packets[0];
1963 let ap_header = H266NALUHeader::new(ap_packet[0], ap_packet[1]);
1964 assert_eq!(ap_header.nalu_type(), H266NALU_AGGREGATION_PACKET_TYPE);
1965 
1966 let donl = u16::from_be_bytes([ap_packet[2], ap_packet[3]]);
1967 assert_eq!(donl, 0, "DONL should be 0 for first aggregated unit");
1968 
1969 let first_size = u16::from_be_bytes([ap_packet[4], ap_packet[5]]);
1970 assert_eq!(first_size, 6, "VPS size should be 6 bytes");
1971 
1972 // The second unit's 16-bit size follows the first unit
1973 // immediately.
1974 let first_nal_end = 6 + first_size as usize;
1975 let second_size =
1976 u16::from_be_bytes([ap_packet[first_nal_end], ap_packet[first_nal_end + 1]]);
1977 assert_eq!(second_size, 6, "SPS size should be 6 bytes");
1978 
1979 // Depacketize AP: all 3 parameter sets out, DONL stripped.
1980 let mut out = Vec::new();
1981 let mut codec_extra = CodecExtra::None;
1982 depacketizer.depacketize(ap_packet, &mut out, &mut codec_extra)?;
1983 
1984 assert_eq!(out, annexb(&[&vps, &sps, &pps]));
1985 
1986 Ok(())
1987 }
1988 #[test]
1989 fn test_h266_donl_increments_correctly() -> Result<()> {
1990 let mut packetizer = H266Packetizer::default();
1991 packetizer.with_donl(true);
1992 
1993 let nalu1 = vec![0x00, 0x09, 0xAA];
1994 let nalu2 = vec![0x00, 0x09, 0xBB];
1995 let nalu3 = vec![0x00, 0x09, 0xCC];
1996 
1997 let packets1 = packetizer.packetize(MAX_PACKET_SIZE, &nalu1)?;
1998 let packets2 = packetizer.packetize(MAX_PACKET_SIZE, &nalu2)?;
1999 let packets3 = packetizer.packetize(MAX_PACKET_SIZE, &nalu3)?;
2000 
2001 let donl1 = u16::from_be_bytes([packets1[0][2], packets1[0][3]]);
2002 let donl2 = u16::from_be_bytes([packets2[0][2], packets2[0][3]]);
2003 let donl3 = u16::from_be_bytes([packets3[0][2], packets3[0][3]]);
2004 
2005 assert_eq!(donl1, 0);
2006 assert_eq!(donl2, 1);
2007 assert_eq!(donl3, 2);
2008 
2009 Ok(())
2010 }
2011 #[test]
2012 fn test_h266_without_donl() -> Result<()> {
2013 let mut packetizer = H266Packetizer::default();
2014 // DONL disabled by default.
2015 
2016 let nalu = vec![0x00, 0x09, 0xDE, 0xAD, 0xBE, 0xEF];
2017 let packets = packetizer.packetize(MAX_PACKET_SIZE, &nalu)?;
2018 
2019 assert_eq!(packets.len(), 1);
2020 assert_eq!(packets[0].len(), nalu.len(), "No DONL field expected");
2021 assert_eq!(packets[0], nalu);
2022 
2023 Ok(())
2024 }
2025 #[test]
2026 fn test_h266_sdp_driven_donl_all_packet_types() -> Result<()> {
2027 use crate::format::FormatParams;
2028 
2029 // SDP fmtp with sprop-max-don-diff > 0 enables DONL.
2030 let fmtp = FormatParams::parse_line("sprop-max-don-diff=32");
2031 assert_eq!(fmtp.sprop_max_don_diff, Some(32));
2032 
2033 let donl_enabled = fmtp.sprop_max_don_diff.unwrap_or(0) > 0;
2034 assert!(donl_enabled);
2035 
2036 let mut packetizer = H266Packetizer::default();
2037 packetizer.with_donl(donl_enabled);
2038 
2039 let mut depacketizer = H266Depacketizer::default();
2040 depacketizer.with_donl(donl_enabled);
2041 
2042 // --- Test 1: Single NAL with DONL ---
2043 let single_nalu = vec![0x00, 0x09, 0xDE, 0xAD, 0xBE, 0xEF]; // TRAIL
2044 let packets = packetizer.packetize(MAX_PACKET_SIZE, &single_nalu)?;
2045 assert_eq!(packets.len(), 1);
2046 
2047 let pkt = &packets[0];
2048 assert_eq!(pkt.len(), single_nalu.len() + 2, "2-byte DONL on wire");
2049 let donl = u16::from_be_bytes([pkt[2], pkt[3]]);
2050 assert_eq!(donl, 0);
2051 
2052 let mut out = Vec::new();
2053 let mut extra = CodecExtra::None;
2054 depacketizer.depacketize(pkt, &mut out, &mut extra)?;
2055 assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
2056 assert_eq!(&out[4..], &single_nalu[..]);
2057 
2058 // --- Test 2: AP (VPS + SPS + PPS) with DONL ---
2059 let vps = vec![0x00, 0x71, 0xAA, 0xBB, 0xCC];
2060 let sps = vec![0x00, 0x79, 0xDD, 0xEE, 0xFF, 0x11];
2061 let pps = vec![0x00, 0x81, 0x22, 0x33];
2062 let vcl = vec![0x00, 0x39, 0x44, 0x55, 0x66]; // IDR_W_RADL (7)
2063 
2064 assert!(packetizer.packetize(MAX_PACKET_SIZE, &vps)?.is_empty());
2065 assert!(packetizer.packetize(MAX_PACKET_SIZE, &sps)?.is_empty());
2066 assert!(packetizer.packetize(MAX_PACKET_SIZE, &pps)?.is_empty());
2067 
2068 let ap_packets = packetizer.packetize(MAX_PACKET_SIZE, &vcl)?;
2069 assert_eq!(ap_packets.len(), 2, "Should produce AP + VCL");
2070 
2071 let ap = &ap_packets[0];
2072 let ap_hdr = H266NALUHeader::new(ap[0], ap[1]);
2073 assert_eq!(ap_hdr.nalu_type(), H266NALU_AGGREGATION_PACKET_TYPE);
2074 let ap_donl = u16::from_be_bytes([ap[2], ap[3]]);
2075 assert_eq!(ap_donl, 1, "AP DONL should be 1 (single consumed DON=0)");
2076 
2077 let first_size = u16::from_be_bytes([ap[4], ap[5]]) as usize;
2078 // The next unit's 16-bit size follows the first unit directly.
2079 let second_size_off = 6 + first_size;
2080 let second_size =
2081 u16::from_be_bytes([ap[second_size_off], ap[second_size_off + 1]]) as usize;
2082 assert_eq!(second_size, 6, "SPS unit size expected");
2083 
2084 out.clear();
2085 extra = CodecExtra::None;
2086 depacketizer.depacketize(ap, &mut out, &mut extra)?;
2087 
2088 let mut offset = 0;
2089 for expected in [&vps, &sps, &pps] {
2090 assert_eq!(&out[offset..offset + 4], ANNEXB_NALUSTART_CODE);
2091 offset += 4;
2092 assert_eq!(&out[offset..offset + expected.len()], &expected[..]);
2093 offset += expected.len();
2094 }
2095 assert_eq!(offset, out.len());
2096 
2097 // VCL DONL after AP consumed DONs 1..=3.
2098 let vcl_pkt = &ap_packets[1];
2099 let vcl_donl = u16::from_be_bytes([vcl_pkt[2], vcl_pkt[3]]);
2100 assert_eq!(vcl_donl, 4);
2101 
2102 out.clear();
2103 extra = CodecExtra::None;
2104 depacketizer.depacketize(vcl_pkt, &mut out, &mut extra)?;
2105 assert_eq!(&out[4..], &vcl[..]);
2106 
2107 // --- Test 3: FU with DONL ---
2108 let mut large_nalu = vec![0x00, 0x09];
2109 large_nalu.extend(vec![0xAA; 200]);
2110 
2111 let fu_packets = packetizer.packetize(100, &large_nalu)?;
2112 assert!(fu_packets.len() > 1);
2113 
2114 let first_fu = &fu_packets[0];
2115 let fu_hdr_byte = H266FragmentationUnitHeader(first_fu[2]);
2116 assert!(fu_hdr_byte.s());
2117 let fu_donl = u16::from_be_bytes([first_fu[3], first_fu[4]]);
2118 assert_eq!(fu_donl, 5, "FU DONL after VCL consumed DON=4");
2119 
2120 let last_fu = &fu_packets[fu_packets.len() - 1];
2121 let last_fu_hdr = H266FragmentationUnitHeader(last_fu[2]);
2122 assert!(last_fu_hdr.e());
2123 assert!(!last_fu_hdr.s());
2124 
2125 out.clear();
2126 extra = CodecExtra::None;
2127 for pkt in &fu_packets {
2128 depacketizer.depacketize(pkt, &mut out, &mut extra)?;
2129 }
2130 assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
2131 assert_eq!(&out[4..], &large_nalu[..]);
2132 
2133 // --- DONL counter progression ---
2134 // DON 0 (single), 1-3 (AP), 4 (VCL), 5 (FU) -> next = 6.
2135 let next_nalu = vec![0x00, 0x09, 0x77];
2136 let next_packets = packetizer.packetize(MAX_PACKET_SIZE, &next_nalu)?;
2137 let next_donl = u16::from_be_bytes([next_packets[0][2], next_packets[0][3]]);
2138 assert_eq!(next_donl, 6);
2139 
2140 Ok(())
2141 }
2142 #[test]
2143 fn test_h266_roundtrip_with_donl() -> Result<()> {
2144 let mut packetizer = H266Packetizer::default();
2145 packetizer.with_donl(true);
2146 
2147 let mut depacketizer = H266Depacketizer::default();
2148 depacketizer.with_donl(true);
2149 
2150 // Single NAL with DONL.
2151 let single_nalu = vec![0x00, 0x09, 0xFF, 0xFF, 0xFF];
2152 let packets = packetizer.packetize(MAX_PACKET_SIZE, &single_nalu)?;
2153 assert_eq!(packets.len(), 1);
2154 let donl = u16::from_be_bytes([packets[0][2], packets[0][3]]);
2155 assert_eq!(donl, 0);
2156 
2157 let mut out = Vec::new();
2158 let mut codec_extra = CodecExtra::None;
2159 depacketizer.depacketize(&packets[0], &mut out, &mut codec_extra)?;
2160 assert_eq!(&out[4..], &single_nalu[..]);
2161 
2162 // Fragmented NAL with DONL.
2163 let mut large_nalu = vec![0x00, 0x09];
2164 for i in 0..512 {
2165 large_nalu.push((i % 256) as u8);
2166 }
2167 
2168 let fu_packets = packetizer.packetize(100, &large_nalu)?;
2169 assert!(fu_packets.len() > 1);
2170 
2171 let first_donl = u16::from_be_bytes([fu_packets[0][3], fu_packets[0][4]]);
2172 assert_eq!(first_donl, 1, "Second NAL should have DONL=1");
2173 
2174 out.clear();
2175 codec_extra = CodecExtra::None;
2176 for packet in &fu_packets {
2177 depacketizer.depacketize(packet, &mut out, &mut codec_extra)?;
2178 }
2179 assert_eq!(&out[0..4], ANNEXB_NALUSTART_CODE);
2180 assert_eq!(&out[4..], &large_nalu[..]);
2181 
2182 Ok(())
2183 }
2184 #[test]
2185 fn test_h266_aggregation_with_donl_sequences() -> Result<()> {
2186 let mut packetizer = H266Packetizer::default();
2187 packetizer.with_donl(true);
2188 
2189 let vps = vec![0x00, 0x71, 0x00, 0x01, 0x02, 0x03];
2190 let sps = vec![0x00, 0x79, 0x00, 0x01, 0x02, 0x03];
2191 let pps = vec![0x00, 0x81, 0x00, 0x01, 0x02, 0x03];
2192 
2193 packetizer.packetize(MAX_PACKET_SIZE, &vps)?;
2194 packetizer.packetize(MAX_PACKET_SIZE, &sps)?;
2195 packetizer.packetize(MAX_PACKET_SIZE, &pps)?;
2196 
2197 let vcl = vec![0x00, 0x09, 0xAA, 0xBB];
2198 let packets = packetizer.packetize(MAX_PACKET_SIZE, &vcl)?;
2199 
2200 assert!(!packets.is_empty());
2201 for packet in &packets {
2202 assert!(packet.len() >= 2);
2203 }
2204 
2205 Ok(())
2206 }
2207 #[test]
2208 fn test_h266_mtu_equals_fu_overhead_plus_donl() -> Result<()> {
2209 let mut packetizer = H266Packetizer::default();
2210 packetizer.with_donl(true);
2211 
2212 let mut large_nalu = vec![0x00, 0x09];
2213 large_nalu.extend(vec![0xCC; 200]);
2214 
2215 // FU overhead (3) + DONL (2) = 5 bytes, no room for payload.
2216 let packets = packetizer.packetize(5, &large_nalu)?;
2217 assert!(packets.is_empty());
2218 
2219 Ok(())
2220 }
2221 
2222 /// Large single NAL with DONL: the DONL bytes count against the
2223 /// MTU, so NAL + DONL == MTU is the single-NAL limit and one byte
2224 /// more falls back to fragmentation.
2225 #[test]
2226 fn test_h266_single_nal_with_donl_large() -> Result<()> {
2227 let mut packetizer = H266Packetizer::default();
2228 packetizer.with_donl(true);
2229 
2230 // NAL + DONL == MTU exactly -> single packet.
2231 let mut large_nalu = vec![0x00, 0x09];
2232 large_nalu.extend(vec![0xEE; 1196]); // total 1198 + 2 DONL = 1200
2233 let packets = packetizer.packetize(MAX_PACKET_SIZE, &large_nalu)?;
2234 assert_eq!(packets.len(), 1);
2235 assert_eq!(packets[0].len(), 1200);
2236 
2237 // One byte more -> must fragment (never exceed MTU on the wire).
2238 let mut packetizer = H266Packetizer::default();
2239 packetizer.with_donl(true);
2240 let mut larger_nalu = vec![0x00, 0x09];
2241 larger_nalu.extend(vec![0xEE; 1197]); // total 1199 + 2 DONL = 1201
2242 let packets = packetizer.packetize(MAX_PACKET_SIZE, &larger_nalu)?;
2243 assert!(packets.len() > 1, "must fragment instead of exceeding MTU");
2244 for p in &packets {
2245 assert!(p.len() <= MAX_PACKET_SIZE);
2246 }
2247 
2248 Ok(())
2249 }
2250 }
2251 
2252 /// Scalability-related tests: VVC temporal sublayers (TID) and
2253 /// multilayer streams (nuh_layer_id). The RTP layer's responsibility
2254 /// is to PRESERVE these fields through fragmentation/aggregation and
2255 /// to derive AP header fields per RFC 9328 §4.3.2 (lowest LayerId/TID
2256 /// of the aggregated units). Layer SELECTION (SFU dropping sublayers)
2257 /// is application policy built on top of these fields.
2258 mod svc_tests {
2259 use super::*;
2260 
2261 /// FU fragmentation must preserve LayerId and TID end-to-end:
2262 /// the FU payload header copies byte 0 (F/Z/LayerId) verbatim and
2263 /// keeps the TID bits of byte 1; reassembly restores the original
2264 /// two-byte header exactly.
2265 #[test]
2266 fn test_h266_fu_preserves_layer_and_tid() -> Result<()> {
2267 // TRAIL (type 1) on layer 5, temporal sublayer tid=3.
2268 let mut nal = hdr(1, 5, 3).to_vec();
2269 nal.extend(std::iter::repeat(0xAB).take(998));
2270 
2271 let mut pck = H266Packetizer::default();
2272 let packets = pck.packetize(100, &nal)?;
2273 assert!(packets.len() > 1);
2274 
2275 for p in &packets {
2276 let h = H266NALUHeader::new(p[0], p[1]);
2277 assert!(h.is_fragmentation_unit());
2278 assert_eq!(h.layer_id(), 5, "FU header must keep LayerId");
2279 assert_eq!(h.tid(), 3, "FU header must keep TID");
2280 }
2281 
2282 // Reassembly restores the exact original header bytes.
2283 assert_eq!(reconstruct_from_fu_packets(&packets), nal);
2284 
2285 let (out, _) = depacketize_all(&packets)?;
2286 assert_eq!(out, annexb(&[&nal]));
2287 Ok(())
2288 }
2289 
2290 /// The Z bit (nuh_reserved_zero_bit) and F bit live in byte 0,
2291 /// which FU packets copy verbatim — a stream with Z set survives
2292 /// the FU round-trip bit-exactly.
2293 #[test]
2294 fn test_h266_fu_preserves_z_bit() -> Result<()> {
2295 let mut nal = hdr(1, 2, 1).to_vec();
2296 nal[0] |= 0b0100_0000; // set Z
2297 nal.extend(std::iter::repeat(0xCD).take(500));
2298 
2299 let mut pck = H266Packetizer::default();
2300 let packets = pck.packetize(100, &nal)?;
2301 assert!(packets.len() > 1);
2302 
2303 for p in &packets {
2304 let h = H266NALUHeader::new(p[0], p[1]);
2305 assert!(h.z(), "Z bit must be preserved in FU headers");
2306 }
2307 assert_eq!(reconstruct_from_fu_packets(&packets), nal);
2308 Ok(())
2309 }
2310 
2311 /// RFC 9328 §4.3.2: the AP PayloadHdr LayerId and TID MUST be the
2312 /// LOWEST LayerId/TID of all aggregated NAL units — and must not
2313 /// leak from the NAL unit that triggers the AP emission.
2314 #[test]
2315 fn test_h266_ap_header_uses_lowest_layer_and_tid() -> Result<()> {
2316 let vps = make_nal_lt(H266NALU_VPS_NALU_TYPE, 8, 2, 3);
2317 let sps = make_nal_lt(H266NALU_SPS_NALU_TYPE, 8, 4, 1);
2318 let pps = make_nal_lt(H266NALU_PPS_NALU_TYPE, 8, 0, 2);
2319 // Trigger NAL with HIGH layer/tid — must not influence the AP.
2320 let idr = make_nal_lt(H266NALU_IDR_W_RADL, 20, 9, 7);
2321 let payload = annexb(&[&vps, &sps, &pps, &idr]);
2322 
2323 let mut pck = H266Packetizer::default();
2324 let packets = pck.packetize(1200, &payload)?;
2325 assert_eq!(packets.len(), 2);
2326 
2327 let ap = H266NALUHeader::new(packets[0][0], packets[0][1]);
2328 assert!(ap.is_aggregation_packet());
2329 assert_eq!(
2330 ap.layer_id(),
2331 0,
2332 "lowest LayerId of units (0), not the trigger's (9)"
2333 );
2334 assert_eq!(
2335 ap.tid(),
2336 1,
2337 "lowest TID of units (1), not the trigger's (7)"
2338 );
2339 assert!(!ap.f());
2340 Ok(())
2341 }
2342 
2343 /// Aggregated units inside the AP keep their own original headers —
2344 /// depacketizing yields every unit bit-exact, mixed layers/TIDs
2345 /// included.
2346 #[test]
2347 fn test_h266_ap_units_preserve_individual_headers() -> Result<()> {
2348 let vps = make_nal_lt(H266NALU_VPS_NALU_TYPE, 8, 2, 3);
2349 let sps = make_nal_lt(H266NALU_SPS_NALU_TYPE, 8, 4, 1);
2350 let pps = make_nal_lt(H266NALU_PPS_NALU_TYPE, 8, 0, 2);
2351 let idr = make_nal_lt(H266NALU_IDR_W_RADL, 20, 1, 1);
2352 let payload = annexb(&[&vps, &sps, &pps, &idr]);
2353 
2354 let mut pck = H266Packetizer::default();
2355 let packets = pck.packetize(1200, &payload)?;
2356 
2357 let (out, keyframe) = depacketize_all(&packets)?;
2358 assert_eq!(out, annexb(&[&vps, &sps, &pps, &idr]));
2359 assert!(keyframe);
2360 Ok(())
2361 }
2362 
2363 /// Keyframe detection is layer/TID-agnostic: an IRAP on a higher
2364 /// layer or temporal sublayer is still a keyframe — as a single
2365 /// NAL packet, inside an AP, and as the first FU fragment.
2366 #[test]
2367 fn test_h266_keyframe_detection_with_layers() -> Result<()> {
2368 // Single NAL: CRA on layer 3, tid 2.
2369 let cra = hdr(H266NALU_CRA, 3, 2);
2370 assert!(detect_h266_keyframe(&cra));
2371 
2372 // AP containing it.
2373 let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
2374 ap.extend_from_slice(&[0x00, 0x03]);
2375 ap.extend_from_slice(&cra);
2376 ap.push(0x00);
2377 assert!(detect_h266_keyframe(&ap));
2378 
2379 // FU start fragment of it (FU header on layer 3, tid 2).
2380 let mut fu = hdr(H266NALU_FRAGMENTATION_UNIT_TYPE, 3, 2).to_vec();
2381 fu.push(0x80 | H266NALU_CRA); // S=1
2382 fu.extend_from_slice(&[0x00, 0x00]);
2383 assert!(detect_h266_keyframe(&fu));
2384 
2385 // Non-IRAP on the same layer/tid is not a keyframe.
2386 assert!(!detect_h266_keyframe(&hdr(1, 3, 2)));
2387 Ok(())
2388 }
2389 
2390 /// A full mixed-AU round-trip with differing TIDs per NAL —
2391 /// e.g. a temporal-base IDR plus a higher-sublayer TRAIL — comes
2392 /// out bit-exact, so an SFU can still read layer_id()/tid() on the
2393 /// receive side to implement sublayer dropping.
2394 #[test]
2395 fn test_h266_mixed_temporal_layers_roundtrip() -> Result<()> {
2396 let idr = make_nal_lt(H266NALU_IDR_N_LP, 600, 0, 1); // base layer
2397 let trail_t2 = make_nal_lt(1, 700, 0, 2); // sublayer 2
2398 let trail_t3 = make_nal_lt(1, 50, 0, 3); // sublayer 3 (small)
2399 let payload = annexb(&[&idr, &trail_t2, &trail_t3]);
2400 
2401 let mut pck = H266Packetizer::default();
2402 let packets = pck.packetize(400, &payload)?;
2403 
2404 let (out, keyframe) = depacketize_all(&packets)?;
2405 assert_eq!(out, annexb(&[&idr, &trail_t2, &trail_t3]));
2406 assert!(keyframe);
2407 
2408 // Receive side can classify each NAL by TID again.
2409 let mut tids = Vec::new();
2410 let mut off = 0;
2411 while off + 6 <= out.len() {
2412 assert_eq!(&out[off..off + 4], &[0, 0, 0, 1]);
2413 let h = H266NALUHeader::new(out[off + 4], out[off + 5]);
2414 tids.push(h.tid());
2415 // skip to next start code
2416 let mut next = off + 4;
2417 loop {
2418 next += 1;
2419 if next + 4 > out.len() {
2420 next = out.len();
2421 break;
2422 }
2423 if out[next..next + 4] == [0, 0, 0, 1] {
2424 break;
2425 }
2426 }
2427 off = next;
2428 }
2429 assert_eq!(tids, vec![1, 2, 3]);
2430 Ok(())
2431 }
2432 }
2433 
2434 /// Adversarial and malformed-input hardening tests for the AP and FU
2435 /// paths (header-bit derivation rules, nested RTP-only NAL types,
2436 /// inconsistent length fields).
2437 mod hardening_tests {
2438 use super::*;
2439 
2440 /// the AP header F bit is the OR
2441 /// of the aggregated units' F bits.
2442 #[test]
2443 fn test_h266_ap_header_f_bit_or() -> Result<()> {
2444 let vps = make_nal(H266NALU_VPS_NALU_TYPE, 6);
2445 let mut sps = make_nal(H266NALU_SPS_NALU_TYPE, 6);
2446 sps[0] |= 0b1000_0000; // F set on one unit
2447 let pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
2448 let idr = make_nal(H266NALU_IDR_W_RADL, 10);
2449 let payload = annexb(&[&vps, &sps, &pps, &idr]);
2450 
2451 let mut pck = H266Packetizer::default();
2452 let packets = pck.packetize(1200, &payload)?;
2453 assert_eq!(packets.len(), 2);
2454 
2455 let ap = H266NALUHeader::new(packets[0][0], packets[0][1]);
2456 assert!(ap.is_aggregation_packet());
2457 assert!(ap.f(), "AP header F bit must be OR of unit F bits");
2458 Ok(())
2459 }
2460 
2461 /// the Z bit of aggregated units
2462 /// is ignored — the AP header Z is always 0.
2463 #[test]
2464 fn test_h266_ap_header_z_bit_ignored() -> Result<()> {
2465 let mut vps = make_nal(H266NALU_VPS_NALU_TYPE, 6);
2466 let mut sps = make_nal(H266NALU_SPS_NALU_TYPE, 6);
2467 let mut pps = make_nal(H266NALU_PPS_NALU_TYPE, 6);
2468 for n in [&mut vps, &mut sps, &mut pps] {
2469 n[0] |= 0b0100_0000; // Z set on every unit
2470 }
2471 let idr = make_nal(H266NALU_IDR_W_RADL, 10);
2472 let payload = annexb(&[&vps, &sps, &pps, &idr]);
2473 
2474 let mut pck = H266Packetizer::default();
2475 let packets = pck.packetize(1200, &payload)?;
2476 assert_eq!(packets.len(), 2);
2477 
2478 let ap = H266NALUHeader::new(packets[0][0], packets[0][1]);
2479 assert!(ap.is_aggregation_packet());
2480 assert!(!ap.z(), "AP header Z bit must be 0 regardless of units");
2481 Ok(())
2482 }
2483 
2484 /// an AP containing a nested
2485 /// AP (type 28) is corrupted.
2486 #[test]
2487 fn test_h266_ap_rejects_nested_ap() -> Result<()> {
2488 let trail = make_nal(0, 4);
2489 let nested = make_nal(H266NALU_AGGREGATION_PACKET_TYPE, 4);
2490 
2491 let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
2492 for unit in [&trail, &nested] {
2493 ap.extend_from_slice(&(unit.len() as u16).to_be_bytes());
2494 ap.extend_from_slice(unit);
2495 }
2496 
2497 let mut depack = H266Depacketizer::default();
2498 let mut out = Vec::new();
2499 let mut extra = CodecExtra::None;
2500 let res = depack.depacketize(&ap, &mut out, &mut extra);
2501 assert!(matches!(res, Err(PacketError::ErrH266CorruptedPacket)));
2502 assert!(out.is_empty(), "no partial output on corrupted AP");
2503 Ok(())
2504 }
2505 
2506 /// an AP containing a nested
2507 /// FU (type 29) is corrupted.
2508 #[test]
2509 fn test_h266_ap_rejects_nested_fu() -> Result<()> {
2510 let trail = make_nal(0, 4);
2511 let nested = make_nal(H266NALU_FRAGMENTATION_UNIT_TYPE, 4);
2512 
2513 let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
2514 for unit in [&trail, &nested] {
2515 ap.extend_from_slice(&(unit.len() as u16).to_be_bytes());
2516 ap.extend_from_slice(unit);
2517 }
2518 
2519 let mut depack = H266Depacketizer::default();
2520 let mut out = Vec::new();
2521 let mut extra = CodecExtra::None;
2522 let res = depack.depacketize(&ap, &mut out, &mut extra);
2523 assert!(matches!(res, Err(PacketError::ErrH266CorruptedPacket)));
2524 assert!(out.is_empty());
2525 Ok(())
2526 }
2527 
2528 /// a length field larger than
2529 /// the remaining payload errors — even after valid leading units,
2530 /// and without leaking partial output.
2531 #[test]
2532 fn test_h266_ap_rejects_truncated_tail() -> Result<()> {
2533 let a = make_nal(0, 4);
2534 let b = make_nal(1, 4);
2535 
2536 let mut ap = hdr(H266NALU_AGGREGATION_PACKET_TYPE, 0, 1).to_vec();
2537 for unit in [&a, &b] {
2538 ap.extend_from_slice(&(unit.len() as u16).to_be_bytes());
2539 ap.extend_from_slice(unit);
2540 }
2541 // third unit claims 0x00ff bytes but provides 1
2542 ap.extend_from_slice(&[0x00, 0xFF, 0x42]);
2543 
2544 let mut depack = H266Depacketizer::default();
2545 let mut out = Vec::new();
2546 let mut extra = CodecExtra::None;
2547 let res = depack.depacketize(&ap, &mut out, &mut extra);
2548 assert!(matches!(res, Err(PacketError::ErrShortPacket)));
2549 assert!(
2550 out.is_empty(),
2551 "valid leading units must not leak from a malformed AP"
2552 );
2553 Ok(())
2554 }
2555 
2556 /// a NAL with
2557 /// F, Z, LayerId and TID all set survives fragmentation and
2558 /// reassembly bit-exact (packetizer side; the depacketizer rejects
2559 /// F=1 packets by policy).
2560 #[test]
2561 fn test_h266_fu_preserves_all_header_flags() -> Result<()> {
2562 let mut nal = hdr(1, 1, 1).to_vec();
2563 nal[0] |= 0b1100_0000; // F + Z
2564 nal.extend(std::iter::repeat(0x5A).take(400));
2565 
2566 let mut pck = H266Packetizer::default();
2567 let packets = pck.packetize(100, &nal)?;
2568 assert!(packets.len() > 1);
2569 
2570 for p in &packets {
2571 let h = H266NALUHeader::new(p[0], p[1]);
2572 assert!(h.f() && h.z(), "F/Z must be copied into FU headers");
2573 assert_eq!(h.layer_id(), 1);
2574 assert_eq!(h.tid(), 1);
2575 }
2576 assert_eq!(reconstruct_from_fu_packets(&packets), nal);
2577 Ok(())
2578 }
2579 }
2580}