1#[derive(Debug, Clone, Copy, PartialEq, Eq)]
3pub enum RtpError {
4 TooShort,
6 InvalidVersion(u8),
8 InvalidExtension,
10 InvalidPadding,
12 EmptyPayload,
14 UnsupportedPayload,
16 FragmentOverflow,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22pub enum Codec {
23 H264,
25 H265,
27}
28
29pub const RTP_PAYLOAD_TYPE_H264: u8 = 96;
31pub const RTP_PAYLOAD_TYPE_H265: u8 = 97;
33pub const RTP_PAYLOAD_TYPE_OPUS: u8 = 98;
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
38pub struct RtpHeader {
39 pub marker: bool,
41 pub payload_type: u8,
43 pub sequence_number: u16,
45 pub timestamp: u32,
47 pub ssrc: u32,
49 pub csrc_count: u8,
51 pub has_extension: bool,
53 pub header_len: usize,
55 pub payload_len: usize,
57}
58
59impl RtpHeader {
60 pub fn parse(packet: &[u8]) -> Result<Self, RtpError> {
62 if packet.len() < 12 {
63 return Err(RtpError::TooShort);
64 }
65 let version = packet[0] >> 6;
66 if version != 2 {
67 return Err(RtpError::InvalidVersion(version));
68 }
69
70 let padding = packet[0] & 0x20 != 0;
71 let extension = packet[0] & 0x10 != 0;
72 let csrc_count = packet[0] & 0x0f;
73 let mut header_len = 12 + csrc_count as usize * 4;
74 if packet.len() < header_len {
75 return Err(RtpError::TooShort);
76 }
77
78 if extension {
79 if packet.len() < header_len + 4 {
80 return Err(RtpError::InvalidExtension);
81 }
82 let ext_words =
83 u16::from_be_bytes([packet[header_len + 2], packet[header_len + 3]]) as usize;
84 header_len += 4 + ext_words * 4;
85 if packet.len() < header_len {
86 return Err(RtpError::InvalidExtension);
87 }
88 }
89
90 let padding_len = if padding {
91 let len = *packet.last().ok_or(RtpError::InvalidPadding)? as usize;
92 if len == 0 || len > packet.len() - header_len {
93 return Err(RtpError::InvalidPadding);
94 }
95 len
96 } else {
97 0
98 };
99
100 let payload_len = packet.len() - header_len - padding_len;
101 if payload_len == 0 {
102 return Err(RtpError::EmptyPayload);
103 }
104
105 Ok(Self {
106 marker: packet[1] & 0x80 != 0,
107 payload_type: packet[1] & 0x7f,
108 sequence_number: u16::from_be_bytes([packet[2], packet[3]]),
109 timestamp: u32::from_be_bytes([packet[4], packet[5], packet[6], packet[7]]),
110 ssrc: u32::from_be_bytes([packet[8], packet[9], packet[10], packet[11]]),
111 csrc_count,
112 has_extension: extension,
113 header_len,
114 payload_len,
115 })
116 }
117
118 pub fn payload<'a>(&self, packet: &'a [u8]) -> &'a [u8] {
120 &packet[self.header_len..self.header_len + self.payload_len]
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq)]
126pub struct DepacketizedFrame {
127 pub data: Vec<u8>,
129 pub timestamp: u32,
131 pub is_keyframe: bool,
133 pub codec: Codec,
135}
136
137#[derive(Debug, Default, Clone)]
138struct FragmentState {
139 data: Vec<u8>,
140 timestamp: u32,
141 next_sequence: Option<u16>,
142 corrupted: bool,
143}
144
145#[derive(Debug, Clone)]
150pub struct RtpDepacketizer {
151 h264: FragmentState,
152 h265: FragmentState,
153 h264_sps: Option<Vec<u8>>,
154 h264_pps: Option<Vec<u8>>,
155 h265_vps: Option<Vec<u8>>,
156 h265_sps: Option<Vec<u8>>,
157 h265_pps: Option<Vec<u8>>,
158 max_fragment: usize,
159}
160
161impl Default for RtpDepacketizer {
162 fn default() -> Self {
163 Self::new()
164 }
165}
166
167impl RtpDepacketizer {
168 pub fn new() -> Self {
170 Self {
171 h264: FragmentState::default(),
172 h265: FragmentState::default(),
173 h264_sps: None,
174 h264_pps: None,
175 h265_vps: None,
176 h265_sps: None,
177 h265_pps: None,
178 max_fragment: 1024 * 1024,
179 }
180 }
181
182 pub fn push(&mut self, packet: &[u8]) -> Result<Option<DepacketizedFrame>, RtpError> {
184 let header = RtpHeader::parse(packet)?;
185 let payload = header.payload(packet);
186 if header.payload_type == RTP_PAYLOAD_TYPE_OPUS {
187 return Err(RtpError::UnsupportedPayload);
188 }
189 let codec = codec_from_payload_type(header.payload_type)
190 .or_else(|| detect_codec(payload))
191 .ok_or(RtpError::UnsupportedPayload)?;
192 match codec {
193 Codec::H264 => self.push_h264(payload, header),
194 Codec::H265 => self.push_h265(payload, header),
195 }
196 }
197
198 fn push_h264(
199 &mut self,
200 payload: &[u8],
201 header: RtpHeader,
202 ) -> Result<Option<DepacketizedFrame>, RtpError> {
203 let nal_type = payload[0] & 0x1f;
204 match nal_type {
205 7 => {
206 self.h264_sps = Some(payload.to_vec());
207 Ok(None)
208 }
209 8 => {
210 self.h264_pps = Some(payload.to_vec());
211 Ok(None)
212 }
213 24 => self.h264_stap_a(payload, header.timestamp),
214 28 => self.h264_fu_a(payload, header),
215 _ if self.has_decoder_config(Codec::H264) => Ok(Some(self.frame_with_prefix(
216 payload,
217 header.timestamp,
218 nal_type == 5,
219 Codec::H264,
220 ))),
221 _ => Ok(None),
222 }
223 }
224
225 fn push_h265(
226 &mut self,
227 payload: &[u8],
228 header: RtpHeader,
229 ) -> Result<Option<DepacketizedFrame>, RtpError> {
230 if payload.len() < 2 {
231 return Err(RtpError::UnsupportedPayload);
232 }
233 let nal_type = (payload[0] >> 1) & 0x3f;
234 match nal_type {
235 32 => {
236 self.h265_vps = Some(payload.to_vec());
237 Ok(None)
238 }
239 33 => {
240 self.h265_sps = Some(payload.to_vec());
241 Ok(None)
242 }
243 34 => {
244 self.h265_pps = Some(payload.to_vec());
245 Ok(None)
246 }
247 48 => self.h265_ap(payload, header.timestamp),
248 49 => self.h265_fu(payload, header),
249 _ if self.has_decoder_config(Codec::H265) => Ok(Some(self.frame_with_prefix(
250 payload,
251 header.timestamp,
252 (16..=23).contains(&nal_type),
253 Codec::H265,
254 ))),
255 _ => Ok(None),
256 }
257 }
258
259 fn h264_fu_a(
260 &mut self,
261 payload: &[u8],
262 header: RtpHeader,
263 ) -> Result<Option<DepacketizedFrame>, RtpError> {
264 if payload.len() < 2 {
265 return Err(RtpError::UnsupportedPayload);
266 }
267 let fu_indicator = payload[0];
268 let fu_header = payload[1];
269 let start = fu_header & 0x80 != 0;
270 let end = fu_header & 0x40 != 0;
271 let nal_type = fu_header & 0x1f;
272 if start {
273 self.h264.data.clear();
274 self.h264.timestamp = header.timestamp;
275 self.h264.next_sequence = Some(header.sequence_number.wrapping_add(1));
276 self.h264.corrupted = false;
277 self.h264.data.push((fu_indicator & 0xe0) | nal_type);
278 } else if !self.accept_fragment_sequence(Codec::H264, header.sequence_number) {
279 return Ok(None);
280 }
281 if !self.h264.corrupted {
282 self.append_fragment(Codec::H264, &payload[2..])?;
283 }
284 if end {
285 if self.h264.corrupted || !self.has_decoder_config(Codec::H264) {
286 self.reset_fragment(Codec::H264);
287 return Ok(None);
288 }
289 let data = self.h264.data.clone();
290 let frame =
291 self.frame_with_prefix(&data, self.h264.timestamp, nal_type == 5, Codec::H264);
292 self.reset_fragment(Codec::H264);
293 Ok(Some(frame))
294 } else {
295 Ok(None)
296 }
297 }
298
299 fn h265_fu(
300 &mut self,
301 payload: &[u8],
302 header: RtpHeader,
303 ) -> Result<Option<DepacketizedFrame>, RtpError> {
304 if payload.len() < 3 {
305 return Err(RtpError::UnsupportedPayload);
306 }
307 let fu_header = payload[2];
308 let start = fu_header & 0x80 != 0;
309 let end = fu_header & 0x40 != 0;
310 let nal_type = fu_header & 0x3f;
311 if start {
312 self.h265.data.clear();
313 self.h265.timestamp = header.timestamp;
314 self.h265.next_sequence = Some(header.sequence_number.wrapping_add(1));
315 self.h265.corrupted = false;
316 self.h265.data.push((nal_type << 1) | (payload[0] & 0x01));
317 self.h265.data.push(payload[1]);
318 } else if !self.accept_fragment_sequence(Codec::H265, header.sequence_number) {
319 return Ok(None);
320 }
321 if !self.h265.corrupted {
322 self.append_fragment(Codec::H265, &payload[3..])?;
323 }
324 if end {
325 if self.h265.corrupted || !self.has_decoder_config(Codec::H265) {
326 self.reset_fragment(Codec::H265);
327 return Ok(None);
328 }
329 let data = self.h265.data.clone();
330 let frame = self.frame_with_prefix(
331 &data,
332 self.h265.timestamp,
333 (16..=23).contains(&nal_type),
334 Codec::H265,
335 );
336 self.reset_fragment(Codec::H265);
337 Ok(Some(frame))
338 } else {
339 Ok(None)
340 }
341 }
342
343 fn accept_fragment_sequence(&mut self, codec: Codec, sequence_number: u16) -> bool {
344 let state = match codec {
345 Codec::H264 => &mut self.h264,
346 Codec::H265 => &mut self.h265,
347 };
348 let Some(expected) = state.next_sequence else {
349 return false;
350 };
351 state.next_sequence = Some(sequence_number.wrapping_add(1));
352 if sequence_number != expected {
353 state.data.clear();
354 state.corrupted = true;
355 return false;
356 }
357 true
358 }
359
360 fn reset_fragment(&mut self, codec: Codec) {
361 let state = match codec {
362 Codec::H264 => &mut self.h264,
363 Codec::H265 => &mut self.h265,
364 };
365 state.data.clear();
366 state.next_sequence = None;
367 state.corrupted = false;
368 }
369
370 fn h264_stap_a(
371 &mut self,
372 payload: &[u8],
373 timestamp: u32,
374 ) -> Result<Option<DepacketizedFrame>, RtpError> {
375 let mut out = Vec::new();
376 let mut offset = 1;
377 let mut keyframe = false;
378 let mut has_slice = false;
379 while offset + 2 <= payload.len() {
380 let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
381 offset += 2;
382 if offset + len > payload.len() {
383 break;
384 }
385 let nalu = &payload[offset..offset + len];
386 let nal_type = nalu.first().map(|b| b & 0x1f).unwrap_or(0);
387 match nal_type {
388 7 => self.h264_sps = Some(nalu.to_vec()),
389 8 => self.h264_pps = Some(nalu.to_vec()),
390 _ => {}
391 }
392 has_slice |= (1..=5).contains(&nal_type);
393 keyframe |= nal_type == 5;
394 append_annex_b(&mut out, nalu);
395 offset += len;
396 }
397 if !has_slice || !self.has_decoder_config(Codec::H264) {
398 return Ok(None);
399 }
400 Ok(Some(DepacketizedFrame {
401 data: out,
402 timestamp,
403 is_keyframe: keyframe,
404 codec: Codec::H264,
405 }))
406 }
407
408 fn h265_ap(
409 &mut self,
410 payload: &[u8],
411 timestamp: u32,
412 ) -> Result<Option<DepacketizedFrame>, RtpError> {
413 let mut out = Vec::new();
414 let mut offset = 2;
415 let mut keyframe = false;
416 let mut has_slice = false;
417 while offset + 2 <= payload.len() {
418 let len = u16::from_be_bytes([payload[offset], payload[offset + 1]]) as usize;
419 offset += 2;
420 if offset + len > payload.len() {
421 break;
422 }
423 let nalu = &payload[offset..offset + len];
424 let nal_type = nalu.first().map(|b| (b >> 1) & 0x3f).unwrap_or(0);
425 match nal_type {
426 32 => self.h265_vps = Some(nalu.to_vec()),
427 33 => self.h265_sps = Some(nalu.to_vec()),
428 34 => self.h265_pps = Some(nalu.to_vec()),
429 _ => {}
430 }
431 has_slice |= !nalu.is_empty() && nal_type <= 31;
432 keyframe |= (16..=23).contains(&nal_type);
433 append_annex_b(&mut out, nalu);
434 offset += len;
435 }
436 if !has_slice || !self.has_decoder_config(Codec::H265) {
437 return Ok(None);
438 }
439 Ok(Some(DepacketizedFrame {
440 data: out,
441 timestamp,
442 is_keyframe: keyframe,
443 codec: Codec::H265,
444 }))
445 }
446
447 fn append_fragment(&mut self, codec: Codec, bytes: &[u8]) -> Result<(), RtpError> {
448 let state = match codec {
449 Codec::H264 => &mut self.h264,
450 Codec::H265 => &mut self.h265,
451 };
452 if state.data.len() + bytes.len() > self.max_fragment {
453 return Err(RtpError::FragmentOverflow);
454 }
455 state.data.extend_from_slice(bytes);
456 Ok(())
457 }
458
459 fn frame_with_prefix(
460 &self,
461 nalu: &[u8],
462 timestamp: u32,
463 is_keyframe: bool,
464 codec: Codec,
465 ) -> DepacketizedFrame {
466 let mut data = Vec::new();
467 if is_keyframe {
468 match codec {
469 Codec::H264 => {
470 if let Some(sps) = &self.h264_sps {
471 append_annex_b(&mut data, sps);
472 }
473 if let Some(pps) = &self.h264_pps {
474 append_annex_b(&mut data, pps);
475 }
476 }
477 Codec::H265 => {
478 if let Some(vps) = &self.h265_vps {
479 append_annex_b(&mut data, vps);
480 }
481 if let Some(sps) = &self.h265_sps {
482 append_annex_b(&mut data, sps);
483 }
484 if let Some(pps) = &self.h265_pps {
485 append_annex_b(&mut data, pps);
486 }
487 }
488 }
489 }
490 append_annex_b(&mut data, nalu);
491 DepacketizedFrame {
492 data,
493 timestamp,
494 is_keyframe,
495 codec,
496 }
497 }
498
499 fn has_decoder_config(&self, codec: Codec) -> bool {
500 match codec {
501 Codec::H264 => self.h264_sps.is_some() && self.h264_pps.is_some(),
502 Codec::H265 => {
503 self.h265_vps.is_some() && self.h265_sps.is_some() && self.h265_pps.is_some()
504 }
505 }
506 }
507}
508
509fn codec_from_payload_type(payload_type: u8) -> Option<Codec> {
510 match payload_type {
511 RTP_PAYLOAD_TYPE_H264 => Some(Codec::H264),
512 RTP_PAYLOAD_TYPE_H265 => Some(Codec::H265),
513 _ => None,
514 }
515}
516
517fn detect_codec(payload: &[u8]) -> Option<Codec> {
518 if payload.is_empty() {
519 return None;
520 }
521 if payload.len() >= 2 {
522 let h265_nal_type = (payload[0] >> 1) & 0x3f;
523 if h265_nal_type == 48 || h265_nal_type == 49 || (32..=40).contains(&h265_nal_type) {
524 return Some(Codec::H265);
525 }
526 }
527 let h264_nal_type = payload[0] & 0x1f;
528 if h264_nal_type == 24 || h264_nal_type == 28 || (1..=12).contains(&h264_nal_type) {
529 return Some(Codec::H264);
530 }
531 None
532}
533
534fn append_annex_b(out: &mut Vec<u8>, nalu: &[u8]) {
535 out.extend_from_slice(&[0, 0, 0, 1]);
536 out.extend_from_slice(nalu);
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542
543 fn rtp(payload: &[u8], marker: bool, seq: u16, timestamp: u32) -> Vec<u8> {
544 rtp_with_payload_type(payload, RTP_PAYLOAD_TYPE_H264, marker, seq, timestamp)
545 }
546
547 fn rtp_with_payload_type(
548 payload: &[u8],
549 payload_type: u8,
550 marker: bool,
551 seq: u16,
552 timestamp: u32,
553 ) -> Vec<u8> {
554 let mut packet = vec![
555 0x80,
556 (if marker { 0x80 } else { 0x00 }) | (payload_type & 0x7f),
557 ];
558 packet.extend_from_slice(&seq.to_be_bytes());
559 packet.extend_from_slice(×tamp.to_be_bytes());
560 packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
561 packet.extend_from_slice(payload);
562 packet
563 }
564
565 fn stap_a(units: &[&[u8]]) -> Vec<u8> {
566 let mut payload = vec![24];
567 for unit in units {
568 payload.extend_from_slice(&(unit.len() as u16).to_be_bytes());
569 payload.extend_from_slice(unit);
570 }
571 payload
572 }
573
574 fn prime_h264(depay: &mut RtpDepacketizer) {
575 assert!(depay
576 .push(&rtp(&[0x67, 0x64, 0x00, 0x1f], true, 1, 10))
577 .unwrap()
578 .is_none());
579 assert!(depay
580 .push(&rtp(&[0x68, 0xee], true, 2, 10))
581 .unwrap()
582 .is_none());
583 }
584
585 fn prime_h265(depay: &mut RtpDepacketizer) {
586 for (seq, payload) in [
587 (1, &[0x40, 0x01, 0xaa][..]),
588 (2, &[0x42, 0x01, 0xbb][..]),
589 (3, &[0x44, 0x01, 0xcc][..]),
590 ] {
591 assert!(depay
592 .push(&rtp_with_payload_type(
593 payload,
594 RTP_PAYLOAD_TYPE_H265,
595 true,
596 seq,
597 10,
598 ))
599 .unwrap()
600 .is_none());
601 }
602 }
603
604 #[test]
605 fn parses_rtp_header() {
606 let packet = rtp(&[0x65, 1, 2], true, 7, 1234);
607 let header = RtpHeader::parse(&packet).unwrap();
608 assert!(header.marker);
609 assert_eq!(header.payload_type, 96);
610 assert_eq!(header.sequence_number, 7);
611 assert_eq!(header.timestamp, 1234);
612 assert_eq!(header.payload(&packet), &[0x65, 1, 2]);
613 }
614
615 #[test]
616 fn depacketizes_h264_single_nalu_as_annex_b() {
617 let mut depay = RtpDepacketizer::new();
618 prime_h264(&mut depay);
619 let frame = depay
620 .push(&rtp(&[0x65, 0xaa], true, 1, 42))
621 .unwrap()
622 .unwrap();
623 assert_eq!(frame.codec, Codec::H264);
624 assert!(frame.is_keyframe);
625 assert_eq!(
626 frame.data,
627 [
628 &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
629 &[0, 0, 0, 1, 0x68, 0xee][..],
630 &[0, 0, 0, 1, 0x65, 0xaa][..],
631 ]
632 .concat()
633 );
634 }
635
636 #[test]
637 fn drops_h264_video_until_sps_and_pps_are_seen() {
638 let mut depay = RtpDepacketizer::new();
639 assert!(depay
640 .push(&rtp(&[0x65, 0xaa], true, 1, 42))
641 .unwrap()
642 .is_none());
643 }
644
645 #[test]
646 fn h264_payload_type_prevents_h265_false_positive() {
647 let mut depay = RtpDepacketizer::new();
648 prime_h264(&mut depay);
649 let frame = depay
650 .push(&rtp(&[0x41, 0xaa], true, 1, 42))
651 .unwrap()
652 .unwrap();
653 assert_eq!(frame.codec, Codec::H264);
654 assert!(!frame.is_keyframe);
655 assert_eq!(frame.data, &[0, 0, 0, 1, 0x41, 0xaa]);
656 }
657
658 #[test]
659 fn opus_payload_type_is_not_sniffed_as_video() {
660 let mut depay = RtpDepacketizer::new();
661 prime_h264(&mut depay);
662 let err = depay
663 .push(&rtp_with_payload_type(
664 &[0x65, 0xaa],
665 RTP_PAYLOAD_TYPE_OPUS,
666 true,
667 1,
668 42,
669 ))
670 .unwrap_err();
671 assert_eq!(err, RtpError::UnsupportedPayload);
672 }
673
674 #[test]
675 fn depacketizes_h265_single_nalu_by_payload_type() {
676 let mut depay = RtpDepacketizer::new();
677 prime_h265(&mut depay);
678 let frame = depay
679 .push(&rtp_with_payload_type(
680 &[0x02, 0x01, 0xaa],
681 RTP_PAYLOAD_TYPE_H265,
682 true,
683 1,
684 42,
685 ))
686 .unwrap()
687 .unwrap();
688 assert_eq!(frame.codec, Codec::H265);
689 assert!(!frame.is_keyframe);
690 assert_eq!(frame.data, &[0, 0, 0, 1, 0x02, 0x01, 0xaa]);
691 }
692
693 #[test]
694 fn h264_stap_a_caches_parameter_sets_for_later_keyframe() {
695 let mut depay = RtpDepacketizer::new();
696 let sps = &[0x67, 0x64, 0x00, 0x1f][..];
697 let pps = &[0x68, 0xee][..];
698 let aggregate = depay.push(&rtp(&stap_a(&[sps, pps]), true, 1, 10)).unwrap();
699 assert!(aggregate.is_none());
700
701 let frame = depay
702 .push(&rtp(&[0x65, 0xaa], true, 2, 20))
703 .unwrap()
704 .unwrap();
705 assert!(frame.is_keyframe);
706 assert_eq!(
707 frame.data,
708 [
709 &[0, 0, 0, 1][..],
710 sps,
711 &[0, 0, 0, 1][..],
712 pps,
713 &[0, 0, 0, 1, 0x65, 0xaa][..],
714 ]
715 .concat()
716 );
717 }
718
719 #[test]
720 fn depacketizes_h264_fu_a() {
721 let mut depay = RtpDepacketizer::new();
722 prime_h264(&mut depay);
723 assert!(depay
724 .push(&rtp(&[0x7c, 0x85, 1, 2], false, 1, 99))
725 .unwrap()
726 .is_none());
727 let frame = depay
728 .push(&rtp(&[0x7c, 0x45, 3, 4], true, 2, 99))
729 .unwrap()
730 .unwrap();
731 assert_eq!(
732 frame.data,
733 [
734 &[0, 0, 0, 1, 0x67, 0x64, 0x00, 0x1f][..],
735 &[0, 0, 0, 1, 0x68, 0xee][..],
736 &[0, 0, 0, 1, 0x65, 1, 2, 3, 4][..],
737 ]
738 .concat()
739 );
740 }
741
742 #[test]
743 fn drops_h264_fu_a_after_sequence_gap() {
744 let mut depay = RtpDepacketizer::new();
745 prime_h264(&mut depay);
746 assert!(depay
747 .push(&rtp(&[0x7c, 0x85, 1, 2], false, 10, 99))
748 .unwrap()
749 .is_none());
750 assert!(depay
751 .push(&rtp(&[0x7c, 0x45, 3, 4], true, 12, 99))
752 .unwrap()
753 .is_none());
754
755 assert!(depay
756 .push(&rtp(&[0x7c, 0x85, 5, 6], false, 13, 100))
757 .unwrap()
758 .is_none());
759 let frame = depay
760 .push(&rtp(&[0x7c, 0x45, 7, 8], true, 14, 100))
761 .unwrap()
762 .unwrap();
763 assert!(frame.data.ends_with(&[0, 0, 0, 1, 0x65, 5, 6, 7, 8]));
764 }
765
766 #[test]
767 fn drops_fragment_end_without_start() {
768 let mut depay = RtpDepacketizer::new();
769 prime_h264(&mut depay);
770 assert!(depay
771 .push(&rtp(&[0x7c, 0x45, 1, 2], true, 10, 99))
772 .unwrap()
773 .is_none());
774 }
775}