moqtap_codec/draft13/data_stream.rs
1//! Draft-13 data stream header encoding and decoding.
2//!
3//! - Subgroup stream type IDs: 0x10-0x15, 0x18-0x1D
4//! - Fetch stream type: 0x05
5//! - Datagram types (separate namespace): 0x00-0x05
6
7use super::types::ObjectStatus;
8use crate::error::CodecError;
9use crate::types::read_bytes;
10use crate::varint::VarInt;
11use bytes::{Buf, BufMut};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14#[repr(u64)]
15pub enum StreamType {
16 Fetch = 0x05,
17 SubgroupZero = 0x10,
18 SubgroupZeroExt = 0x11,
19 SubgroupFirstObj = 0x12,
20 SubgroupFirstObjExt = 0x13,
21 SubgroupExplicit = 0x14,
22 SubgroupExplicitExt = 0x15,
23 SubgroupZeroEog = 0x18,
24 SubgroupZeroEogExt = 0x19,
25 SubgroupFirstObjEog = 0x1A,
26 SubgroupFirstObjEogExt = 0x1B,
27 SubgroupExplicitEog = 0x1C,
28 SubgroupExplicitEogExt = 0x1D,
29}
30
31/// Hold an object to the rule that a non-existent object carries no extensions.
32///
33/// Section 9.2.1.2: "Any Object may have extension headers except those with
34/// Object Status 'Object Does Not Exist'. If an endpoint receives a non-existent
35/// Object containing extension headers it MUST close the session with a Protocol
36/// Violation."
37///
38/// The sentence is about a receiver, and it reaches all three carriers that can
39/// announce a status: an object on a subgroup stream, an object on a fetch
40/// stream, and a status datagram. A plain datagram has no status field, so it
41/// is the only carrier that cannot break the rule.
42///
43/// Reported under [`CodecError::ExtensionsOnNonExistentObject`], which is this
44/// rule and nothing else. It was [`CodecError::InvalidField`] until now, shared
45/// with a dozen unrelated malformations the draft does not answer with a close,
46/// which left a caller unable to act on the sentence above.
47fn check_extensions_against_status(
48 status: ObjectStatus,
49 extensions: &[u8],
50) -> Result<(), CodecError> {
51 if status == ObjectStatus::ObjectDoesNotExist && !extensions.is_empty() {
52 return Err(CodecError::ExtensionsOnNonExistentObject(extensions.len()));
53 }
54 Ok(())
55}
56
57impl StreamType {
58 pub fn from_id(id: u64) -> Option<Self> {
59 match id {
60 0x05 => Some(StreamType::Fetch),
61 0x10 => Some(StreamType::SubgroupZero),
62 0x11 => Some(StreamType::SubgroupZeroExt),
63 0x12 => Some(StreamType::SubgroupFirstObj),
64 0x13 => Some(StreamType::SubgroupFirstObjExt),
65 0x14 => Some(StreamType::SubgroupExplicit),
66 0x15 => Some(StreamType::SubgroupExplicitExt),
67 0x18 => Some(StreamType::SubgroupZeroEog),
68 0x19 => Some(StreamType::SubgroupZeroEogExt),
69 0x1A => Some(StreamType::SubgroupFirstObjEog),
70 0x1B => Some(StreamType::SubgroupFirstObjEogExt),
71 0x1C => Some(StreamType::SubgroupExplicitEog),
72 0x1D => Some(StreamType::SubgroupExplicitEogExt),
73 _ => None,
74 }
75 }
76
77 pub fn is_subgroup(&self) -> bool {
78 matches!(
79 self,
80 StreamType::SubgroupZero
81 | StreamType::SubgroupZeroExt
82 | StreamType::SubgroupFirstObj
83 | StreamType::SubgroupFirstObjExt
84 | StreamType::SubgroupExplicit
85 | StreamType::SubgroupExplicitExt
86 | StreamType::SubgroupZeroEog
87 | StreamType::SubgroupZeroEogExt
88 | StreamType::SubgroupFirstObjEog
89 | StreamType::SubgroupFirstObjEogExt
90 | StreamType::SubgroupExplicitEog
91 | StreamType::SubgroupExplicitEogExt
92 )
93 }
94
95 pub fn has_extensions(&self) -> bool {
96 matches!(
97 self,
98 StreamType::SubgroupZeroExt
99 | StreamType::SubgroupFirstObjExt
100 | StreamType::SubgroupExplicitExt
101 | StreamType::SubgroupZeroEogExt
102 | StreamType::SubgroupFirstObjEogExt
103 | StreamType::SubgroupExplicitEogExt
104 )
105 }
106
107 pub fn contains_end_of_group(&self) -> bool {
108 matches!(
109 self,
110 StreamType::SubgroupZeroEog
111 | StreamType::SubgroupZeroEogExt
112 | StreamType::SubgroupFirstObjEog
113 | StreamType::SubgroupFirstObjEogExt
114 | StreamType::SubgroupExplicitEog
115 | StreamType::SubgroupExplicitEogExt
116 )
117 }
118
119 /// True if this subgroup stream type puts an explicit Subgroup ID on the
120 /// wire.
121 ///
122 /// The Subgroup ID Field Present column of the SUBGROUP_HEADER type table
123 /// in Section 9.4.2. The other two columns of that row say what the
124 /// Subgroup ID *is* where the field is absent — zero, or the first
125 /// Object's ID — so this is only about the field, never about the value.
126 pub fn writes_subgroup_id(&self) -> bool {
127 matches!(
128 self,
129 StreamType::SubgroupExplicit
130 | StreamType::SubgroupExplicitExt
131 | StreamType::SubgroupExplicitEog
132 | StreamType::SubgroupExplicitEogExt
133 )
134 }
135}
136
137/// Datagram wire types (separate namespace from QUIC stream types).
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139#[repr(u64)]
140pub enum DatagramType {
141 Datagram = 0x00,
142 DatagramExt = 0x01,
143 DatagramEog = 0x02,
144 DatagramEogExt = 0x03,
145 DatagramStatus = 0x04,
146 DatagramStatusExt = 0x05,
147}
148
149impl DatagramType {
150 pub fn from_id(id: u64) -> Option<Self> {
151 match id {
152 0x00 => Some(DatagramType::Datagram),
153 0x01 => Some(DatagramType::DatagramExt),
154 0x02 => Some(DatagramType::DatagramEog),
155 0x03 => Some(DatagramType::DatagramEogExt),
156 0x04 => Some(DatagramType::DatagramStatus),
157 0x05 => Some(DatagramType::DatagramStatusExt),
158 _ => None,
159 }
160 }
161
162 pub fn has_extensions(&self) -> bool {
163 matches!(
164 self,
165 DatagramType::DatagramExt
166 | DatagramType::DatagramEogExt
167 | DatagramType::DatagramStatusExt
168 )
169 }
170
171 pub fn is_status(&self) -> bool {
172 matches!(self, DatagramType::DatagramStatus | DatagramType::DatagramStatusExt)
173 }
174
175 pub fn is_end_of_group(&self) -> bool {
176 matches!(self, DatagramType::DatagramEog | DatagramType::DatagramEogExt)
177 }
178}
179
180/// Which failure a leading unidirectional stream type that is not the one a
181/// reader wants is.
182///
183/// Section 9: "An endpoint that receives an unknown stream or datagram type
184/// MUST close the session." One sentence, two tables, and on this draft the two
185/// tables collide: 0x05 is FETCH_HEADER in the stream table and
186/// OBJECT_DATAGRAM_STATUS with extensions in the datagram table. Which table
187/// was consulted is therefore part of the answer, not a detail, and it is why
188/// [`CodecError::UnknownStreamType`] and [`CodecError::UnknownDatagramType`]
189/// are separate variants rather than one.
190///
191/// The stream table assigns 0x05 and the range 0x10 to 0x1D. Everything outside
192/// them is unknown at the head of a stream, and the session ends.
193fn stream_type_error(raw: u64) -> CodecError {
194 if StreamType::from_id(raw).is_some() {
195 CodecError::InvalidField
196 } else {
197 CodecError::UnknownStreamType(raw)
198 }
199}
200
201/// Which failure a leading datagram type that is not one a reader wants is.
202///
203/// The datagram half of the sentence quoted on `stream_type_error`, read
204/// against the other table: 0x00 to 0x05 are what it assigns, and everything
205/// else arriving as a datagram is unknown.
206///
207/// Answered from [`DatagramType`] alone, never from [`StreamType`]. A value in
208/// both tables means one thing as a datagram and another as a stream, and
209/// consulting the wrong one turns an assigned datagram type into an unknown one
210/// or the reverse.
211fn datagram_type_error(raw: u64) -> CodecError {
212 if DatagramType::from_id(raw).is_some() {
213 CodecError::InvalidField
214 } else {
215 CodecError::UnknownDatagramType(raw)
216 }
217}
218
219fn read_extension_bytes(buf: &mut impl Buf, byte_len: u64) -> Result<Vec<u8>, CodecError> {
220 read_bytes(buf, byte_len as usize)
221}
222
223// ============================================================
224// Subgroup stream header
225// ============================================================
226
227#[derive(Debug, Clone, PartialEq, Eq)]
228pub struct SubgroupHeader {
229 pub stream_type: StreamType,
230 pub track_alias: VarInt,
231 pub group_id: VarInt,
232 pub subgroup_id: VarInt,
233 pub publisher_priority: u8,
234}
235
236impl SubgroupHeader {
237 /// Encode a subgroup stream header including its leading stream-type
238 /// field, so the bytes form the start of a data stream a peer can read.
239 ///
240 /// [`Self::encode`] writes the body alone, which is what a caller wants
241 /// once the stream is already open and what a caller must not use for its
242 /// first write. It is also the half that cannot stand on its own here,
243 /// because the stream type is what says whether a Subgroup ID follows it
244 /// and whether the objects on the stream carry extension headers.
245 pub fn encode_stream(&self, buf: &mut impl BufMut) {
246 VarInt::from_usize(self.stream_type as usize).encode(buf);
247 self.encode(buf);
248 }
249
250 /// Encode the header body, without its leading stream-type field.
251 ///
252 /// Driven by the stream type, and silent about a `subgroup_id` it decides
253 /// not to write: on a type whose Subgroup ID Field Present column reads No
254 /// the field is dropped, and the peer reads the subgroup the *type* names -
255 /// zero, or the first Object's ID - rather than the one in hand. Nothing is
256 /// malformed about the result, which is what makes it worth refusing rather
257 /// than tolerating. [`Self::encode_checked`] refuses it.
258 pub fn encode(&self, buf: &mut impl BufMut) {
259 self.track_alias.encode(buf);
260 self.group_id.encode(buf);
261 if self.stream_type.writes_subgroup_id() {
262 self.subgroup_id.encode(buf);
263 }
264 buf.put_u8(self.publisher_priority);
265 }
266
267 /// Encode the header body, refusing a Subgroup ID this stream type has
268 /// nowhere to put.
269 ///
270 /// [`Self::decode_with_type`] leaves the field at zero for every type that
271 /// does not carry it, so a decoded header always passes: the refusal is for
272 /// a header assembled by hand, where a caller set an ID the type will
273 /// discard.
274 ///
275 /// A zero is accepted under any type. It is what the decoder produces, and
276 /// on a Subgroup ID Value column reading `0` it is also the truth, so
277 /// refusing it would refuse the ordinary case to catch nothing.
278 ///
279 /// # Errors
280 ///
281 /// [`CodecError::InvalidField`] if a non-zero Subgroup ID sits under a
282 /// stream type that writes no Subgroup ID field.
283 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
284 if !self.stream_type.writes_subgroup_id() && self.subgroup_id.into_inner() != 0 {
285 return Err(CodecError::InvalidField);
286 }
287 self.encode(buf);
288 Ok(())
289 }
290
291 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
292 Self::decode_with_type(StreamType::SubgroupExplicit, buf)
293 }
294
295 pub fn decode_with_type(
296 stream_type: StreamType,
297 buf: &mut impl Buf,
298 ) -> Result<Self, CodecError> {
299 let track_alias = VarInt::decode(buf)?;
300 let group_id = VarInt::decode(buf)?;
301 let subgroup_id = match stream_type {
302 StreamType::SubgroupZero
303 | StreamType::SubgroupZeroExt
304 | StreamType::SubgroupZeroEog
305 | StreamType::SubgroupZeroEogExt => VarInt::from_usize(0),
306 StreamType::SubgroupExplicit
307 | StreamType::SubgroupExplicitExt
308 | StreamType::SubgroupExplicitEog
309 | StreamType::SubgroupExplicitEogExt => VarInt::decode(buf)?,
310 StreamType::SubgroupFirstObj
311 | StreamType::SubgroupFirstObjExt
312 | StreamType::SubgroupFirstObjEog
313 | StreamType::SubgroupFirstObjEogExt => VarInt::from_usize(0),
314 _ => return Err(CodecError::InvalidField),
315 };
316 if buf.remaining() < 1 {
317 return Err(CodecError::UnexpectedEnd);
318 }
319 let publisher_priority = buf.get_u8();
320 Ok(Self { stream_type, track_alias, group_id, subgroup_id, publisher_priority })
321 }
322
323 /// Decode a subgroup header from the start of a data stream, consuming
324 /// the leading stream type varint and using it to select the variant.
325 ///
326 /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
327 /// not assign the leading type, which this draft answers with a close, and
328 /// with [`CodecError::InvalidField`] when it does assign it but not to a
329 /// subgroup. `stream_type_error` draws that line.
330 pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
331 let raw = VarInt::decode(buf)?.into_inner();
332 let stream_type = StreamType::from_id(raw).ok_or_else(|| stream_type_error(raw))?;
333 if !stream_type.is_subgroup() {
334 return Err(stream_type_error(raw));
335 }
336 Self::decode_with_type(stream_type, buf)
337 }
338}
339
340// ============================================================
341// Object header within subgroup
342// ============================================================
343
344#[derive(Debug, Clone, PartialEq, Eq)]
345pub struct ObjectHeader {
346 pub object_id: VarInt,
347 pub extension_headers_length: VarInt,
348 pub extensions: Vec<u8>,
349 pub payload_length: VarInt,
350 pub object_status: ObjectStatus,
351}
352
353impl ObjectHeader {
354 /// Encode the object header in the framing that carries no extension
355 /// block.
356 ///
357 /// Lossy, and lossy in a way the caller cannot see: an object holding
358 /// extension headers is written without them and without a word. Which
359 /// framing is correct is not a property of the object at all - Section
360 /// 9.4.2 gives the stream's type an Extensions Present column, and every
361 /// object on the stream follows it - so this entry point can only guess,
362 /// and it guesses "absent". Prefer [`Self::encode_with_extensions`], which
363 /// is told, or [`Self::encode_checked`], which refuses what it would
364 /// otherwise drop.
365 pub fn encode(&self, buf: &mut impl BufMut) {
366 self.encode_with_extensions(false, buf);
367 }
368
369 /// Encode the header, refusing a status the framing cannot carry.
370 ///
371 /// Section 9.4.2 puts the Object Status field on the wire only when the
372 /// Object Payload Length is zero, and Section 9.2.1.1 says "Any object
373 /// with a status code other than zero MUST have an empty payload". A
374 /// non-zero status paired with a non-zero payload length therefore has no
375 /// encoding at all: [`Self::encode`] drops the status and the peer reads an
376 /// ordinary object, which is a different object from the one the caller
377 /// described. This refuses instead.
378 ///
379 /// The datagram types on this draft already refuse the same pairing. These
380 /// two did not, and they are the ones a publisher writes on every stream.
381 ///
382 /// Extension headers are refused here rather than dropped, for a reason
383 /// the status rule does not share: this entry point writes the framing
384 /// that has no Extension Headers Length field, so the bytes have nowhere
385 /// to go. Writing them anyway is not an option and losing them silently
386 /// puts a stream on the wire that no reader can follow - a reader on an
387 /// extensions-bearing stream takes the Object Payload Length as the
388 /// extension length and every object after it is misread. A caller that
389 /// knows the stream's framing wants
390 /// [`Self::encode_checked_with_extensions`].
391 ///
392 /// # Errors
393 ///
394 /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
395 /// a non-zero Object Payload Length, or if the object carries extension
396 /// headers this framing cannot write.
397 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
398 self.encode_checked_with_extensions(false, buf)
399 }
400
401 /// Encode the object header into a stream whose type has already settled
402 /// whether objects carry an extension block, refusing what that framing
403 /// cannot express.
404 ///
405 /// `has_extensions` is the stream's answer, not the object's: Section
406 /// 9.4.2 fixes it for the whole stream from the SUBGROUP_HEADER type, so
407 /// an object with no extensions on a stream that carries them still writes
408 /// a length of zero, and that is the one direction not refused here. The
409 /// other direction has no encoding, so it is refused.
410 ///
411 /// # Errors
412 ///
413 /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
414 /// a non-zero Object Payload Length, or if `has_extensions` is `false`
415 /// while the object carries extension headers.
416 pub fn encode_checked_with_extensions(
417 &self,
418 has_extensions: bool,
419 buf: &mut impl BufMut,
420 ) -> Result<(), CodecError> {
421 if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
422 return Err(CodecError::InvalidField);
423 }
424 if !has_extensions && !self.extensions.is_empty() {
425 return Err(CodecError::InvalidField);
426 }
427 self.encode_with_extensions(has_extensions, buf);
428 Ok(())
429 }
430
431 /// Encode the object header, writing the extension block only when the
432 /// stream's type says objects carry one.
433 ///
434 /// Infallible, and so unable to say that a `false` here discards the
435 /// extension headers the object holds. [`Self::encode_checked_with_extensions`]
436 /// is the same write with that refusal in front of it.
437 pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
438 self.object_id.encode(buf);
439 if has_extensions {
440 VarInt::from_usize(self.extensions.len()).encode(buf);
441 buf.put_slice(&self.extensions);
442 }
443 self.payload_length.encode(buf);
444 if self.payload_length.into_inner() == 0 {
445 VarInt::from_usize(self.object_status as usize).encode(buf);
446 }
447 }
448
449 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
450 Self::decode_with_extensions(false, buf)
451 }
452
453 pub fn decode_with_extensions(
454 has_extensions: bool,
455 buf: &mut impl Buf,
456 ) -> Result<Self, CodecError> {
457 let object_id = VarInt::decode(buf)?;
458 let (extension_headers_length, extensions) = if has_extensions {
459 let ehl = VarInt::decode(buf)?;
460 let ext = read_extension_bytes(buf, ehl.into_inner())?;
461 (ehl, ext)
462 } else {
463 (VarInt::from_usize(0), Vec::new())
464 };
465 let payload_length = VarInt::decode(buf)?;
466 let object_status = if payload_length.into_inner() == 0 {
467 let sv = VarInt::decode(buf)?.into_inner();
468 ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?
469 } else {
470 ObjectStatus::Normal
471 };
472 check_extensions_against_status(object_status, &extensions)?;
473 Ok(Self { object_id, extension_headers_length, extensions, payload_length, object_status })
474 }
475}
476
477// ============================================================
478// Datagram (types 0x00, 0x01)
479// ============================================================
480
481#[derive(Debug, Clone, PartialEq, Eq)]
482pub struct DatagramHeader {
483 pub track_alias: VarInt,
484 pub group_id: VarInt,
485 pub object_id: VarInt,
486 pub publisher_priority: u8,
487 pub extension_headers_length: VarInt,
488 pub extensions: Vec<u8>,
489 pub end_of_group: bool,
490}
491
492impl DatagramHeader {
493 /// Encode the datagram header in the framing that carries no extension
494 /// block.
495 ///
496 /// Lossy in the same way the subgroup object header is: an extension block
497 /// this value holds is dropped, because the framing being written has no
498 /// field for it. The type byte decides which framing is right, and this
499 /// entry point does not write the type byte, so it cannot consult it.
500 /// [`Datagram::encode`] does both together and never disagrees with itself;
501 /// this is the piece for a caller that has already written the type.
502 pub fn encode(&self, buf: &mut impl BufMut) {
503 self.encode_with_extensions(false, buf);
504 }
505
506 pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
507 self.track_alias.encode(buf);
508 self.group_id.encode(buf);
509 self.object_id.encode(buf);
510 buf.put_u8(self.publisher_priority);
511 if has_extensions {
512 VarInt::from_usize(self.extensions.len()).encode(buf);
513 buf.put_slice(&self.extensions);
514 }
515 }
516
517 /// Encode the datagram header, refusing what this framing cannot carry.
518 ///
519 /// No status is ever refused, and that is a fact about this draft rather
520 /// than a check left out. This is the OBJECT_DATAGRAM of Section 9.3.1,
521 /// whose layout carries no Object Status field at all; a datagram that
522 /// states a status is the separate OBJECT_DATAGRAM_STATUS message, modelled
523 /// here as [`DatagramStatusHeader`]. So there is no status for
524 /// [`Self::encode`] to drop, and nothing for Section 9.2.1.1's "Any object
525 /// with a status code other than zero MUST have an empty payload" to rule
526 /// on: an object framed this way has status zero by construction.
527 ///
528 /// The extension block is a different matter. [`Self::encode`] writes the
529 /// framing without one, so a block this value holds has nowhere to go, and
530 /// dropping it silently is what puts a datagram on the wire describing
531 /// something other than what the caller built. That is refused here.
532 ///
533 /// The fallible signature is also what lets one entry point span every
534 /// draft. `dispatch::AnyDatagramHeader::encode` calls this on all thirteen,
535 /// and the drafts whose payload-bearing datagram *does* carry a status field
536 /// need somewhere to say no.
537 ///
538 /// # Errors
539 ///
540 /// [`CodecError::InvalidField`] if the value carries extension headers,
541 /// which this framing has no field for.
542 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
543 if !self.extensions.is_empty() {
544 return Err(CodecError::InvalidField);
545 }
546 self.encode(buf);
547 Ok(())
548 }
549
550 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
551 Self::decode_with_extensions(false, buf)
552 }
553
554 pub fn decode_with_extensions(
555 has_extensions: bool,
556 buf: &mut impl Buf,
557 ) -> Result<Self, CodecError> {
558 let track_alias = VarInt::decode(buf)?;
559 let group_id = VarInt::decode(buf)?;
560 let object_id = VarInt::decode(buf)?;
561 if buf.remaining() < 1 {
562 return Err(CodecError::UnexpectedEnd);
563 }
564 let publisher_priority = buf.get_u8();
565 let (extension_headers_length, extensions) = if has_extensions {
566 let ehl = VarInt::decode(buf)?;
567 // A datagram whose type says extensions are present must actually carry
568 // some: receiving one with an Extension Headers Length of 0 closes the
569 // session. The opposite holds on a subgroup stream, where the type byte is
570 // fixed for the whole stream and an object with no extensions has no other
571 // way to say so, which is why this check belongs to the datagram readers
572 // alone.
573 if ehl.into_inner() == 0 {
574 return Err(CodecError::InvalidField);
575 }
576 let ext = read_extension_bytes(buf, ehl.into_inner())?;
577 (ehl, ext)
578 } else {
579 (VarInt::from_usize(0), Vec::new())
580 };
581 Ok(Self {
582 track_alias,
583 group_id,
584 object_id,
585 publisher_priority,
586 extension_headers_length,
587 extensions,
588 end_of_group: false,
589 })
590 }
591}
592
593// ============================================================
594// Datagram Status (types 0x04, 0x05)
595// ============================================================
596
597#[derive(Debug, Clone, PartialEq, Eq)]
598pub struct DatagramStatusHeader {
599 pub track_alias: VarInt,
600 pub group_id: VarInt,
601 pub object_id: VarInt,
602 pub publisher_priority: u8,
603 pub extension_headers_length: VarInt,
604 pub extensions: Vec<u8>,
605 pub object_status: ObjectStatus,
606}
607
608impl DatagramStatusHeader {
609 pub fn encode(&self, buf: &mut impl BufMut) {
610 self.encode_with_extensions(false, buf);
611 }
612
613 /// Encode the status datagram header, refusing an extension block this
614 /// framing cannot carry.
615 ///
616 /// The same one-sided rule the payload-bearing header obeys, and the same
617 /// reason for it: [`Self::encode`] writes the framing without a block, so
618 /// bytes held here would be dropped rather than written.
619 ///
620 /// # Errors
621 ///
622 /// [`CodecError::InvalidField`] if the value carries extension headers.
623 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
624 if !self.extensions.is_empty() {
625 return Err(CodecError::InvalidField);
626 }
627 self.encode(buf);
628 Ok(())
629 }
630
631 pub fn encode_with_extensions(&self, has_extensions: bool, buf: &mut impl BufMut) {
632 self.track_alias.encode(buf);
633 self.group_id.encode(buf);
634 self.object_id.encode(buf);
635 buf.put_u8(self.publisher_priority);
636 if has_extensions {
637 VarInt::from_usize(self.extensions.len()).encode(buf);
638 buf.put_slice(&self.extensions);
639 }
640 VarInt::from_usize(self.object_status as usize).encode(buf);
641 }
642
643 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
644 Self::decode_with_extensions(false, buf)
645 }
646
647 pub fn decode_with_extensions(
648 has_extensions: bool,
649 buf: &mut impl Buf,
650 ) -> Result<Self, CodecError> {
651 let track_alias = VarInt::decode(buf)?;
652 let group_id = VarInt::decode(buf)?;
653 let object_id = VarInt::decode(buf)?;
654 if buf.remaining() < 1 {
655 return Err(CodecError::UnexpectedEnd);
656 }
657 let publisher_priority = buf.get_u8();
658 let (extension_headers_length, extensions) = if has_extensions {
659 let ehl = VarInt::decode(buf)?;
660 // A datagram whose type says extensions are present must actually carry
661 // some: receiving one with an Extension Headers Length of 0 closes the
662 // session. The opposite holds on a subgroup stream, where the type byte is
663 // fixed for the whole stream and an object with no extensions has no other
664 // way to say so, which is why this check belongs to the datagram readers
665 // alone.
666 if ehl.into_inner() == 0 {
667 return Err(CodecError::InvalidField);
668 }
669 let ext = read_extension_bytes(buf, ehl.into_inner())?;
670 (ehl, ext)
671 } else {
672 (VarInt::from_usize(0), Vec::new())
673 };
674 let sv = VarInt::decode(buf)?.into_inner();
675 let object_status = ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?;
676 check_extensions_against_status(object_status, &extensions)?;
677 Ok(Self {
678 track_alias,
679 group_id,
680 object_id,
681 publisher_priority,
682 extension_headers_length,
683 extensions,
684 object_status,
685 })
686 }
687}
688
689// ============================================================
690// Datagram framing
691// ============================================================
692
693/// One datagram, of whichever shape its type field names.
694///
695/// A MoQT datagram opens with a variable-length integer naming its type, and
696/// that integer is what says which of the layouts above follows it, and whether an extension block sits inside it.
697/// Neither [`DatagramHeader`] nor [`DatagramStatusHeader`] reads or writes it, so neither can be handed
698/// the first byte of a datagram a peer sent, and neither produces bytes a peer
699/// can read. This is the entry point that does both.
700///
701/// The payload of a payload-bearing datagram runs to the end of the QUIC
702/// datagram, so it is not part of this value: [`Self::decode`] stops at the end
703/// of the header and leaves the payload in the buffer, and a caller appends the
704/// payload after [`Self::encode`].
705#[derive(Debug, Clone, PartialEq, Eq)]
706pub enum Datagram {
707 /// An object carrying a payload.
708 Payload(DatagramHeader),
709 /// An object stating a status, with no payload.
710 Status(DatagramStatusHeader),
711}
712
713impl Datagram {
714 /// Whether this datagram states an Object Status instead of carrying a
715 /// payload.
716 pub fn is_status(&self) -> bool {
717 matches!(self, Self::Status(_))
718 }
719
720 /// The type field this value writes.
721 ///
722 /// The extensions bit is taken from the extension bytes themselves rather
723 /// than from the declared length beside them, which is what keeps the type
724 /// and the body from contradicting each other: a datagram whose type
725 /// announces extensions and then declares a length of 0 closes the session
726 /// on receipt, and one that announces none has nowhere to put them. The end
727 /// of group bit has no home in the body at all, so it comes from the header
728 /// flag and goes nowhere else.
729 pub fn datagram_type(&self) -> DatagramType {
730 match self {
731 Self::Payload(header) => match (header.end_of_group, header.extensions.is_empty()) {
732 (false, true) => DatagramType::Datagram,
733 (false, false) => DatagramType::DatagramExt,
734 (true, true) => DatagramType::DatagramEog,
735 (true, false) => DatagramType::DatagramEogExt,
736 },
737 Self::Status(header) => {
738 if header.extensions.is_empty() {
739 DatagramType::DatagramStatus
740 } else {
741 DatagramType::DatagramStatusExt
742 }
743 }
744 }
745 }
746
747 /// Decode a datagram from its first byte, type field included.
748 ///
749 /// Errors with [`CodecError::UnknownDatagramType`] when the datagram table
750 /// does not assign the leading type, which this draft answers with a close.
751 /// `datagram_type_error` settles it against that table alone — 0x05 is
752 /// assigned in both tables here and means different things in each.
753 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
754 let raw = VarInt::decode(buf)?.into_inner();
755 let datagram_type = DatagramType::from_id(raw).ok_or_else(|| datagram_type_error(raw))?;
756 let has_extensions = datagram_type.has_extensions();
757 if datagram_type.is_status() {
758 return Ok(Self::Status(DatagramStatusHeader::decode_with_extensions(
759 has_extensions,
760 buf,
761 )?));
762 }
763 let mut header = DatagramHeader::decode_with_extensions(has_extensions, buf)?;
764 header.end_of_group = datagram_type.is_end_of_group();
765 Ok(Self::Payload(header))
766 }
767
768 /// Encode the datagram, type field included.
769 pub fn encode(&self, buf: &mut impl BufMut) {
770 let datagram_type = self.datagram_type();
771 let has_extensions = datagram_type.has_extensions();
772 VarInt::from_usize(datagram_type as usize).encode(buf);
773 match self {
774 Self::Payload(header) => header.encode_with_extensions(has_extensions, buf),
775 Self::Status(header) => header.encode_with_extensions(has_extensions, buf),
776 }
777 }
778
779 /// Encode the datagram, refusing a header the framing it names cannot
780 /// carry.
781 ///
782 /// The body is built before anything reaches `buf`, so a refused datagram
783 /// leaves `buf` untouched rather than a type field with no body under it.
784 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
785 let mut body = Vec::with_capacity(64);
786 let datagram_type = self.datagram_type();
787 let has_extensions = datagram_type.has_extensions();
788 match self {
789 Self::Payload(header) => header.encode_with_extensions(has_extensions, &mut body),
790 Self::Status(header) => {
791 check_extensions_against_status(header.object_status, &header.extensions)?;
792 header.encode_with_extensions(has_extensions, &mut body);
793 }
794 }
795 VarInt::from_usize(datagram_type as usize).encode(buf);
796 buf.put_slice(&body);
797 Ok(())
798 }
799}
800
801// ============================================================
802// Fetch stream (type 0x05)
803// ============================================================
804
805#[derive(Debug, Clone, PartialEq, Eq)]
806pub struct FetchHeader {
807 pub request_id: VarInt,
808}
809
810#[derive(Debug, Clone, PartialEq, Eq)]
811pub struct FetchObjectHeader {
812 pub group_id: VarInt,
813 pub subgroup_id: VarInt,
814 pub object_id: VarInt,
815 pub publisher_priority: u8,
816 pub extension_headers_length: VarInt,
817 pub extensions: Vec<u8>,
818 pub payload_length: VarInt,
819 pub object_status: ObjectStatus,
820}
821
822impl FetchHeader {
823 /// Encode a fetch stream header including its leading stream-type field,
824 /// so the bytes form the start of a data stream a peer can read.
825 ///
826 /// [`Self::encode`] writes the body alone, which is what a caller wants
827 /// once the stream is already open and what a caller must not use for its
828 /// first write. The read side has had [`Self::decode_stream`] all along,
829 /// so without this the codec could not round-trip its own fetch stream
830 /// through its own reader.
831 pub fn encode_stream(&self, buf: &mut impl BufMut) {
832 VarInt::from_usize(StreamType::Fetch as usize).encode(buf);
833 self.encode(buf);
834 }
835
836 pub fn encode(&self, buf: &mut impl BufMut) {
837 self.request_id.encode(buf);
838 }
839
840 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
841 let request_id = VarInt::decode(buf)?;
842 Ok(Self { request_id })
843 }
844
845 /// Decode a fetch header from the start of a data stream, consuming the
846 /// leading stream type varint.
847 ///
848 /// Errors with [`CodecError::UnknownStreamType`] when the stream table does
849 /// not assign the leading type, which this draft answers with a close, and
850 /// with [`CodecError::InvalidField`] when it is assigned but is not
851 /// [`StreamType::Fetch`]. `stream_type_error` draws that line.
852 pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
853 let stream_type = VarInt::decode(buf)?.into_inner();
854 if stream_type != StreamType::Fetch as u64 {
855 return Err(stream_type_error(stream_type));
856 }
857 Self::decode(buf)
858 }
859}
860
861impl FetchObjectHeader {
862 pub fn encode(&self, buf: &mut impl BufMut) {
863 self.group_id.encode(buf);
864 self.subgroup_id.encode(buf);
865 self.object_id.encode(buf);
866 buf.put_u8(self.publisher_priority);
867 VarInt::from_usize(self.extensions.len()).encode(buf);
868 buf.put_slice(&self.extensions);
869 self.payload_length.encode(buf);
870 if self.payload_length.into_inner() == 0 {
871 VarInt::from_usize(self.object_status as usize).encode(buf);
872 }
873 }
874
875 /// Encode the header, refusing a status the framing cannot carry.
876 ///
877 /// Section 9.4.4 puts the Object Status field on the wire only when the
878 /// Object Payload Length is zero, and Section 9.2.1.1 says "Any object
879 /// with a status code other than zero MUST have an empty payload". A
880 /// non-zero status paired with a non-zero payload length therefore has no
881 /// encoding at all: [`Self::encode`] drops the status and the peer reads an
882 /// ordinary object, which is a different object from the one the caller
883 /// described. This refuses instead.
884 ///
885 /// The datagram types on this draft already refuse the same pairing. These
886 /// two did not, and they are the ones a publisher writes on every stream.
887 ///
888 /// # Errors
889 ///
890 /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
891 /// a non-zero Object Payload Length.
892 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
893 if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
894 return Err(CodecError::InvalidField);
895 }
896 self.encode(buf);
897 Ok(())
898 }
899
900 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
901 let group_id = VarInt::decode(buf)?;
902 let subgroup_id = VarInt::decode(buf)?;
903 let object_id = VarInt::decode(buf)?;
904 if buf.remaining() < 1 {
905 return Err(CodecError::UnexpectedEnd);
906 }
907 let publisher_priority = buf.get_u8();
908 let extension_headers_length = VarInt::decode(buf)?;
909 let extensions = read_extension_bytes(buf, extension_headers_length.into_inner())?;
910 let payload_length = VarInt::decode(buf)?;
911 let object_status = if payload_length.into_inner() == 0 {
912 let sv = VarInt::decode(buf)?.into_inner();
913 ObjectStatus::from_u64(sv).ok_or(CodecError::InvalidField)?
914 } else {
915 ObjectStatus::Normal
916 };
917 check_extensions_against_status(object_status, &extensions)?;
918 Ok(Self {
919 group_id,
920 subgroup_id,
921 object_id,
922 publisher_priority,
923 extension_headers_length,
924 extensions,
925 payload_length,
926 object_status,
927 })
928 }
929}