moqtap_codec/draft08/data_stream.rs
1//! Draft-08 data stream header encoding and decoding.
2//!
3//! Differences from draft-07:
4//! - Object headers include `extension_count` (varint) + raw extension bytes
5//! - Separate DatagramStatus type (0x02) for status-only datagrams
6//! - Datagram (0x01) includes extension_count + payload
7
8use super::types::ObjectStatus;
9use crate::error::CodecError;
10use crate::types::read_bytes;
11use crate::varint::VarInt;
12use bytes::{Buf, BufMut};
13
14/// Every type ID this draft's data plane assigns, from two separate tables.
15///
16/// The draft keeps unidirectional stream types and datagram types in different
17/// tables and different number spaces: SUBGROUP_HEADER and FETCH_HEADER name
18/// unidirectional streams, OBJECT_DATAGRAM and OBJECT_DATAGRAM_STATUS name
19/// datagrams. The numbers happen not to collide on this draft, which is what
20/// lets one enum hold all four.
21///
22/// [`StreamType::from_id`] therefore answers across both tables and cannot say
23/// which one a value came from, which makes it the wrong question to ask about
24/// a stream or a datagram in hand. `stream_type_error` and
25/// `datagram_type_error` ask it per table, and the difference is not
26/// cosmetic: a value assigned in the other table is unknown in this one, and
27/// this draft answers an unknown type by ending the session.
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29#[repr(u64)]
30pub enum StreamType {
31 /// Datagram with payload (0x01).
32 Datagram = 0x01,
33 /// Datagram with status only, no payload (0x02).
34 DatagramStatus = 0x02,
35 /// Subgroup stream type (0x04).
36 Subgroup = 0x04,
37 /// Fetch stream type (0x05).
38 Fetch = 0x05,
39}
40
41impl StreamType {
42 /// Convert a raw stream type ID to a `StreamType`, if valid.
43 pub fn from_id(id: u64) -> Option<Self> {
44 match id {
45 0x01 => Some(StreamType::Datagram),
46 0x02 => Some(StreamType::DatagramStatus),
47 0x04 => Some(StreamType::Subgroup),
48 0x05 => Some(StreamType::Fetch),
49 _ => None,
50 }
51 }
52}
53
54/// Which failure a leading unidirectional stream type that is not the one a
55/// reader wants is.
56///
57/// Section 8: "An endpoint that receives an unknown stream or datagram type
58/// MUST close the session." One sentence, two tables. The stream table assigns
59/// SUBGROUP_HEADER and FETCH_HEADER; everything outside those two is unknown at
60/// the head of a stream, and the session ends.
61///
62/// The datagram types are among the values outside them. This draft is the
63/// first to give datagrams a table of their own, with numbering independent of
64/// the stream table, so a datagram type says nothing about a stream and is not
65/// a value the stream table assigns. Draft-07, which numbered both in one
66/// table, is the only draft where that is not so.
67///
68/// A stream announcing the *other* assigned stream type is a different matter
69/// and not that rule. The value is one this draft defines, and the disagreement
70/// is with the reader that was called rather than with the draft, so the
71/// session survives it.
72fn stream_type_error(raw: u64) -> CodecError {
73 if raw == StreamType::Subgroup as u64 || raw == StreamType::Fetch as u64 {
74 CodecError::InvalidField
75 } else {
76 CodecError::UnknownStreamType(raw)
77 }
78}
79
80/// Which failure a leading datagram type that is not one a reader wants is.
81///
82/// The datagram half of the sentence quoted on `stream_type_error`, read
83/// against the other table: OBJECT_DATAGRAM and OBJECT_DATAGRAM_STATUS are what
84/// it assigns, and everything else arriving as a datagram is unknown.
85///
86/// Both assigned values are decodable here, so the [`CodecError::InvalidField`]
87/// arm is reached only by the stream types — 0x04 and 0x05 — delivered as
88/// datagrams. Those are values this draft defines, and refusing them without
89/// closing is the same judgement the stream side makes about a datagram type.
90fn datagram_type_error(raw: u64) -> CodecError {
91 if raw == StreamType::Datagram as u64 || raw == StreamType::DatagramStatus as u64 {
92 CodecError::InvalidField
93 } else {
94 CodecError::UnknownDatagramType(raw)
95 }
96}
97
98// ── Extension helpers ───────────────────────────────────────
99
100/// Skip over extensions in the buffer, reading extension_count varints.
101///
102/// Extension encoding: for each extension, read type (varint).
103/// - Even type: value is a single varint
104/// - Odd type: value is length-prefixed bytes (varint length + bytes)
105fn skip_extensions(buf: &mut impl Buf, count: u64) -> Result<Vec<u8>, CodecError> {
106 let mut raw = Vec::new();
107 for _ in 0..count {
108 let ext_type = VarInt::decode(buf)?;
109 ext_type.encode(&mut raw);
110 if ext_type.into_inner().is_multiple_of(2) {
111 let val = VarInt::decode(buf)?;
112 val.encode(&mut raw);
113 } else {
114 let len = VarInt::decode(buf)?.into_inner() as usize;
115 VarInt::from_usize(len).encode(&mut raw);
116 let bytes = read_bytes(buf, len)?;
117 raw.extend_from_slice(&bytes);
118 }
119 }
120 Ok(raw)
121}
122
123/// Count the extension headers in a raw block, or `None` if the bytes do not
124/// tile into whole headers.
125///
126/// The block is stored opaque, so the number of headers in it is not something
127/// the value carries - it is a property of the bytes, recovered by walking them
128/// with the same rule [`skip_extensions`] reads them by. This is the only draft
129/// that needs it: draft-08 states the field as "Extension Count", a number of
130/// headers, and every later draft states a byte length that
131/// `extensions.len()` supplies directly.
132fn count_extensions(raw: &[u8]) -> Option<u64> {
133 let mut buf = raw;
134 let mut count: u64 = 0;
135 while buf.has_remaining() {
136 let ext_type = VarInt::decode(&mut buf).ok()?;
137 if ext_type.into_inner().is_multiple_of(2) {
138 VarInt::decode(&mut buf).ok()?;
139 } else {
140 let len = VarInt::decode(&mut buf).ok()?.into_inner() as usize;
141 if buf.remaining() < len {
142 return None;
143 }
144 buf.advance(len);
145 }
146 count = count.checked_add(1)?;
147 }
148 Some(count)
149}
150
151/// Refuse an Extension Count that disagrees with the extension bytes beside it.
152///
153/// The count and the bytes are two fields with nothing tying them together, and
154/// [`skip_extensions`] reads exactly `count` headers on the way back in. A
155/// header that states two and carries three leaves the third where the peer
156/// expects the Object Payload Length, so every field after it - and every
157/// object after that on the same stream - is read at the wrong offset. There is
158/// no recovery downstream, which is why this is refused rather than corrected.
159fn check_extension_count(stated: VarInt, raw: &[u8]) -> Result<(), CodecError> {
160 match count_extensions(raw) {
161 Some(actual) if actual == stated.into_inner() => Ok(()),
162 _ => Err(CodecError::InvalidField),
163 }
164}
165
166/// Encode extension bytes back to the buffer.
167fn encode_extensions(extensions: &[u8], buf: &mut impl BufMut) {
168 buf.put_slice(extensions);
169}
170
171// ============================================================
172// Subgroup stream (type 0x04)
173// ============================================================
174
175/// Subgroup stream header (follows the stream type varint).
176#[derive(Debug, Clone, PartialEq, Eq)]
177pub struct SubgroupHeader {
178 /// Track alias identifying the subscription.
179 pub track_alias: VarInt,
180 /// Group identifier.
181 pub group_id: VarInt,
182 /// Subgroup identifier within the group.
183 pub subgroup_id: VarInt,
184 /// Publisher priority for delivery ordering.
185 pub publisher_priority: u8,
186}
187
188/// Object within a subgroup stream (draft-08).
189///
190/// Encoding: object_id(vi), extension_count(vi), [extensions...],
191/// payload_length(vi),
192/// if payload_length == 0: object_status(vi)
193/// else: payload bytes
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub struct ObjectHeader {
196 /// Object identifier within the subgroup.
197 pub object_id: VarInt,
198 /// Number of extensions.
199 pub extension_count: VarInt,
200 /// Raw extension bytes (opaque).
201 pub extensions: Vec<u8>,
202 /// Length of the object payload in bytes.
203 pub payload_length: VarInt,
204 /// Status of this object.
205 pub object_status: ObjectStatus,
206}
207
208impl SubgroupHeader {
209 /// Encode a subgroup stream header including its leading stream-type
210 /// field, so the bytes form the start of a data stream a peer can read.
211 ///
212 /// [`Self::encode`] writes the body alone, which is what a caller wants
213 /// once the stream is already open and what a caller must not use for its
214 /// first write.
215 pub fn encode_stream(&self, buf: &mut impl BufMut) {
216 VarInt::from_usize(StreamType::Subgroup as usize).encode(buf);
217 self.encode(buf);
218 }
219
220 /// Encode the subgroup header into the buffer.
221 pub fn encode(&self, buf: &mut impl BufMut) {
222 self.track_alias.encode(buf);
223 self.group_id.encode(buf);
224 self.subgroup_id.encode(buf);
225 buf.put_u8(self.publisher_priority);
226 }
227
228 /// Decode a subgroup header from the buffer.
229 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
230 let track_alias = VarInt::decode(buf)?;
231 let group_id = VarInt::decode(buf)?;
232 let subgroup_id = VarInt::decode(buf)?;
233 if buf.remaining() < 1 {
234 return Err(CodecError::UnexpectedEnd);
235 }
236 let publisher_priority = buf.get_u8();
237 Ok(Self { track_alias, group_id, subgroup_id, publisher_priority })
238 }
239
240 /// Decode a subgroup header from the start of a data stream, consuming
241 /// the leading stream type varint.
242 ///
243 /// Errors with [`CodecError::UnknownStreamType`] when the stream type is
244 /// one the stream table does not assign, which this draft answers with a
245 /// close, and with [`CodecError::InvalidField`] when it is assigned but is
246 /// not [`StreamType::Subgroup`]. `stream_type_error` draws that line.
247 pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
248 let stream_type = VarInt::decode(buf)?.into_inner();
249 if stream_type != StreamType::Subgroup as u64 {
250 return Err(stream_type_error(stream_type));
251 }
252 Self::decode(buf)
253 }
254}
255
256/// Refuse an end-of-track object that does not end at object zero.
257///
258/// Section 8.1.1.1 describes Object Status 0x5 as "end of Track. GroupID is one
259/// greater than the largest group produced in this track and the ObjectId is
260/// zero", and states the consequence: "An object with this status that has a
261/// Group ID less than or equal to any other Group ID, or an Object ID other
262/// than zero, is a protocol error, and the receiver MUST terminate the
263/// session."
264///
265/// Only the Object ID half is answerable here. The Group ID half compares
266/// against the largest group produced on the track, which no single header
267/// carries and no reader of one header can know.
268///
269/// Applied on both sides. A receiver is required to close the session over
270/// this, so writing one is not a way to send it.
271fn check_end_of_track(object_id: VarInt, status: ObjectStatus) -> Result<(), CodecError> {
272 if status == ObjectStatus::EndOfTrack && object_id.into_inner() != 0 {
273 return Err(CodecError::EndOfTrackObjectId(object_id.into_inner()));
274 }
275 Ok(())
276}
277
278impl ObjectHeader {
279 /// Encode the object header into the buffer.
280 pub fn encode(&self, buf: &mut impl BufMut) {
281 self.object_id.encode(buf);
282 self.extension_count.encode(buf);
283 encode_extensions(&self.extensions, buf);
284 self.payload_length.encode(buf);
285 if self.payload_length.into_inner() == 0 {
286 VarInt::from_usize(self.object_status as usize).encode(buf);
287 }
288 }
289
290 /// Encode the header, refusing a status the framing cannot carry.
291 ///
292 /// Section 8.4.1 puts the Object Status field on the wire only when the
293 /// Object Payload Length is zero, and Section 8.1.1.1 says "Any object
294 /// with a status code other than zero MUST have an empty payload". A
295 /// non-zero status paired with a non-zero payload length therefore has no
296 /// encoding at all: [`Self::encode`] drops the status and the peer reads an
297 /// ordinary object, which is a different object from the one the caller
298 /// described. This refuses instead.
299 ///
300 /// The datagram types on this draft already refuse the same pairing. These
301 /// two did not, and they are the ones a publisher writes on every stream.
302 ///
303 /// # Errors
304 ///
305 /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
306 /// a non-zero Object Payload Length.
307 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
308 check_end_of_track(self.object_id, self.object_status)?;
309 if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
310 return Err(CodecError::InvalidField);
311 }
312 check_extension_count(self.extension_count, &self.extensions)?;
313 self.encode(buf);
314 Ok(())
315 }
316
317 /// Decode an object header from the buffer.
318 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
319 let object_id = VarInt::decode(buf)?;
320 let extension_count = VarInt::decode(buf)?;
321 let extensions = skip_extensions(buf, extension_count.into_inner())?;
322 let payload_length = VarInt::decode(buf)?;
323 let object_status = if payload_length.into_inner() == 0 {
324 let status_val = VarInt::decode(buf)?.into_inner();
325 ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
326 } else {
327 ObjectStatus::Normal
328 };
329 check_end_of_track(object_id, object_status)?;
330 Ok(Self { object_id, extension_count, extensions, payload_length, object_status })
331 }
332}
333
334// ============================================================
335// Datagram (type 0x01)
336// ============================================================
337
338/// Datagram header with payload (draft-08, type 0x01).
339///
340/// Encoding (after type varint):
341/// track_alias(vi), group_id(vi), object_id(vi),
342/// publisher_priority(u8), extension_count(vi), [extensions...],
343/// payload_length(vi),
344/// if payload_length == 0: object_status(vi),
345/// payload bytes
346#[derive(Debug, Clone, PartialEq, Eq)]
347pub struct DatagramHeader {
348 /// Track alias identifying the subscription.
349 pub track_alias: VarInt,
350 /// Group identifier.
351 pub group_id: VarInt,
352 /// Object identifier within the group.
353 pub object_id: VarInt,
354 /// Publisher priority for delivery ordering.
355 pub publisher_priority: u8,
356 /// Number of extensions.
357 pub extension_count: VarInt,
358 /// Raw extension bytes (opaque).
359 pub extensions: Vec<u8>,
360 /// Status of this object.
361 pub object_status: ObjectStatus,
362 /// Length of the object payload in bytes.
363 pub payload_length: VarInt,
364}
365
366impl DatagramHeader {
367 /// Encode the datagram header into the buffer.
368 ///
369 /// The declared payload length is taken as the authority on framing: the
370 /// status field is written exactly when that length is zero, because that
371 /// is the condition under which the OBJECT_DATAGRAM layout in draft-08
372 /// Section 8.2 carries one. That is what makes this infallible — and what
373 /// makes it lossy when the struct disagrees with itself. An
374 /// `object_status` set alongside a non-zero `payload_length` is discarded
375 /// here without a word. Prefer [`Self::encode_checked`], which refuses that
376 /// combination instead of resolving it.
377 pub fn encode(&self, buf: &mut impl BufMut) {
378 self.track_alias.encode(buf);
379 self.group_id.encode(buf);
380 self.object_id.encode(buf);
381 buf.put_u8(self.publisher_priority);
382 self.extension_count.encode(buf);
383 encode_extensions(&self.extensions, buf);
384 self.payload_length.encode(buf);
385 if self.payload_length.into_inner() == 0 {
386 VarInt::from_usize(self.object_status as usize).encode(buf);
387 }
388 }
389
390 /// Encode the datagram header, refusing a status the framing cannot carry.
391 ///
392 /// A datagram states its Object Status only when its Object Payload Length
393 /// is zero. With a non-zero length there is no status field on the wire, so
394 /// an `object_status` of anything but [`ObjectStatus::Normal`] has nowhere
395 /// to go: [`Self::encode`] drops it and the datagram parses back as an
396 /// ordinary payload object. An End of Group marker written that way does
397 /// not arrive late or malformed — it does not arrive at all, and the
398 /// receiver sees a normal object in its place.
399 ///
400 /// That pair is also the frame draft-08 Section 8.1.1.1 forbids outright:
401 /// "Any object with a status code other than zero MUST have an empty
402 /// payload." So the refusal here is not merely about what this encoder can
403 /// express; there is no conforming datagram to express.
404 ///
405 /// [`ObjectStatus::Normal`] beside a payload is not that case and is
406 /// accepted. It is the status every payload-bearing object has under the
407 /// rule above, and the one the encoding elides, so stating it asks for
408 /// exactly the bytes leaving it out asks for and nothing is lost.
409 ///
410 /// Errors with [`CodecError::InvalidField`] on the lossy combination,
411 /// before any byte is written, so a refused header leaves `buf` untouched.
412 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
413 check_end_of_track(self.object_id, self.object_status)?;
414 if self.payload_length.into_inner() != 0 && self.object_status != ObjectStatus::Normal {
415 return Err(CodecError::InvalidField);
416 }
417 check_extension_count(self.extension_count, &self.extensions)?;
418 self.encode(buf);
419 Ok(())
420 }
421
422 /// Decode a datagram header from the buffer.
423 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
424 let track_alias = VarInt::decode(buf)?;
425 let group_id = VarInt::decode(buf)?;
426 let object_id = VarInt::decode(buf)?;
427 if buf.remaining() < 1 {
428 return Err(CodecError::UnexpectedEnd);
429 }
430 let publisher_priority = buf.get_u8();
431 let extension_count = VarInt::decode(buf)?;
432 let extensions = skip_extensions(buf, extension_count.into_inner())?;
433 let payload_length = VarInt::decode(buf)?;
434 let object_status = if payload_length.into_inner() == 0 {
435 let status_val = VarInt::decode(buf)?.into_inner();
436 ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
437 } else {
438 ObjectStatus::Normal
439 };
440 check_end_of_track(object_id, object_status)?;
441 Ok(Self {
442 track_alias,
443 group_id,
444 object_id,
445 publisher_priority,
446 extension_count,
447 extensions,
448 object_status,
449 payload_length,
450 })
451 }
452}
453
454// ============================================================
455// Datagram Status (type 0x02)
456// ============================================================
457
458/// Datagram status header (draft-08, type 0x02).
459///
460/// Status-only datagram with no payload or extensions.
461#[derive(Debug, Clone, PartialEq, Eq)]
462pub struct DatagramStatusHeader {
463 /// Track alias identifying the subscription.
464 pub track_alias: VarInt,
465 /// Group identifier.
466 pub group_id: VarInt,
467 /// Object identifier within the group.
468 pub object_id: VarInt,
469 /// Publisher priority for delivery ordering.
470 pub publisher_priority: u8,
471 /// Object status code.
472 pub object_status: ObjectStatus,
473}
474
475impl DatagramStatusHeader {
476 /// Encode the datagram status header into the buffer.
477 pub fn encode(&self, buf: &mut impl BufMut) {
478 self.track_alias.encode(buf);
479 self.group_id.encode(buf);
480 self.object_id.encode(buf);
481 buf.put_u8(self.publisher_priority);
482 VarInt::from_usize(self.object_status as usize).encode(buf);
483 }
484
485 /// Encode the datagram status header, refusing an end-of-track object that
486 /// does not end at object zero.
487 ///
488 /// # Errors
489 ///
490 /// [`CodecError::EndOfTrackObjectId`] if an end-of-track status is paired
491 /// with a non-zero Object ID.
492 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
493 check_end_of_track(self.object_id, self.object_status)?;
494 self.encode(buf);
495 Ok(())
496 }
497
498 /// Decode a datagram status header from the buffer.
499 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
500 let track_alias = VarInt::decode(buf)?;
501 let group_id = VarInt::decode(buf)?;
502 let object_id = VarInt::decode(buf)?;
503 if buf.remaining() < 1 {
504 return Err(CodecError::UnexpectedEnd);
505 }
506 let publisher_priority = buf.get_u8();
507 let status_val = VarInt::decode(buf)?.into_inner();
508 let object_status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
509 check_end_of_track(object_id, object_status)?;
510 Ok(Self { track_alias, group_id, object_id, publisher_priority, object_status })
511 }
512}
513
514// ============================================================
515// Datagram framing
516// ============================================================
517
518/// One datagram, of whichever shape its type field names.
519///
520/// A MoQT datagram opens with a variable-length integer naming its type, and
521/// that integer is what says which of the layouts above follows it.
522/// Neither [`DatagramHeader`] nor [`DatagramStatusHeader`] reads or writes it, so neither can be handed
523/// the first byte of a datagram a peer sent, and neither produces bytes a peer
524/// can read. This is the entry point that does both.
525///
526/// The payload of a payload-bearing datagram runs to the end of the QUIC
527/// datagram, so it is not part of this value: [`Self::decode`] stops at the end
528/// of the header and leaves the payload in the buffer, and a caller appends the
529/// payload after [`Self::encode`].
530#[derive(Debug, Clone, PartialEq, Eq)]
531pub enum Datagram {
532 /// An object carrying a payload.
533 Payload(DatagramHeader),
534 /// An object stating a status, with no payload.
535 Status(DatagramStatusHeader),
536}
537
538impl Datagram {
539 /// Whether this datagram states an Object Status instead of carrying a
540 /// payload.
541 ///
542 /// Draft-08 can say it two ways: on the dedicated status message, and on
543 /// the payload message with a declared payload length of zero, which
544 /// draft-09 removed.
545 pub fn is_status(&self) -> bool {
546 match self {
547 Self::Payload(header) => header.payload_length.into_inner() == 0,
548 Self::Status(_) => true,
549 }
550 }
551
552 /// The type field this value writes.
553 pub fn datagram_type(&self) -> StreamType {
554 match self {
555 Self::Payload(_) => StreamType::Datagram,
556 Self::Status(_) => StreamType::DatagramStatus,
557 }
558 }
559
560 /// Decode a datagram from its first byte, type field included.
561 ///
562 /// Errors with [`CodecError::UnknownDatagramType`] when the leading type is
563 /// one the datagram table does not assign, which this draft answers with a
564 /// close, and with [`CodecError::InvalidField`] for the stream types, which
565 /// it does assign but not to a datagram. `datagram_type_error` draws that
566 /// line.
567 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
568 let raw = VarInt::decode(buf)?.into_inner();
569 match StreamType::from_id(raw) {
570 Some(StreamType::Datagram) => Ok(Self::Payload(DatagramHeader::decode(buf)?)),
571 Some(StreamType::DatagramStatus) => {
572 Ok(Self::Status(DatagramStatusHeader::decode(buf)?))
573 }
574 _ => Err(datagram_type_error(raw)),
575 }
576 }
577
578 /// Encode the datagram, type field included.
579 pub fn encode(&self, buf: &mut impl BufMut) {
580 VarInt::from_usize(self.datagram_type() as usize).encode(buf);
581 match self {
582 Self::Payload(header) => header.encode(buf),
583 Self::Status(header) => header.encode(buf),
584 }
585 }
586
587 /// Encode the datagram, refusing a header the framing it names cannot
588 /// carry.
589 ///
590 /// The body is built before anything reaches `buf`, so a refused datagram
591 /// leaves `buf` untouched rather than a type field with no body under it.
592 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
593 let mut body = Vec::with_capacity(64);
594 match self {
595 Self::Payload(header) => header.encode_checked(&mut body)?,
596 Self::Status(header) => header.encode_checked(&mut body)?,
597 }
598 VarInt::from_usize(self.datagram_type() as usize).encode(buf);
599 buf.put_slice(&body);
600 Ok(())
601 }
602}
603
604// ============================================================
605// Fetch stream (type 0x05)
606// ============================================================
607
608/// Fetch stream header (follows the stream type varint).
609#[derive(Debug, Clone, PartialEq, Eq)]
610pub struct FetchHeader {
611 /// Subscribe ID this fetch responds to.
612 pub subscribe_id: VarInt,
613}
614
615/// Object within a fetch stream (draft-08).
616///
617/// Encoding: group_id(vi), subgroup_id(vi), object_id(vi),
618/// publisher_priority(u8), extension_count(vi), [extensions...],
619/// payload_length(vi),
620/// [object_status(vi) if payload_length==0],
621/// payload bytes
622#[derive(Debug, Clone, PartialEq, Eq)]
623pub struct FetchObjectHeader {
624 /// Group identifier.
625 pub group_id: VarInt,
626 /// Subgroup identifier within the group.
627 pub subgroup_id: VarInt,
628 /// Object identifier within the subgroup.
629 pub object_id: VarInt,
630 /// Publisher priority for delivery ordering.
631 pub publisher_priority: u8,
632 /// Number of extensions.
633 pub extension_count: VarInt,
634 /// Raw extension bytes (opaque).
635 pub extensions: Vec<u8>,
636 /// Status of this object.
637 pub object_status: ObjectStatus,
638 /// Length of the object payload in bytes.
639 pub payload_length: VarInt,
640}
641
642impl FetchHeader {
643 /// Encode a fetch stream header including its leading stream-type field,
644 /// so the bytes form the start of a data stream a peer can read.
645 ///
646 /// [`Self::encode`] writes the body alone, which is what a caller wants
647 /// once the stream is already open and what a caller must not use for its
648 /// first write. The read side has had [`Self::decode_stream`] all along,
649 /// so without this the codec could not round-trip its own fetch stream
650 /// through its own reader.
651 pub fn encode_stream(&self, buf: &mut impl BufMut) {
652 VarInt::from_usize(StreamType::Fetch as usize).encode(buf);
653 self.encode(buf);
654 }
655
656 /// Encode the fetch header into the buffer.
657 pub fn encode(&self, buf: &mut impl BufMut) {
658 self.subscribe_id.encode(buf);
659 }
660
661 /// Decode a fetch header from the buffer.
662 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
663 let subscribe_id = VarInt::decode(buf)?;
664 Ok(Self { subscribe_id })
665 }
666
667 /// Decode a fetch header from the start of a data stream, consuming the
668 /// leading stream type varint.
669 ///
670 /// Errors with [`CodecError::UnknownStreamType`] when the stream type is
671 /// one the stream table does not assign, which this draft answers with a
672 /// close, and with [`CodecError::InvalidField`] when it is assigned but is
673 /// not [`StreamType::Fetch`]. `stream_type_error` draws that line.
674 pub fn decode_stream(buf: &mut impl Buf) -> Result<Self, CodecError> {
675 let stream_type = VarInt::decode(buf)?.into_inner();
676 if stream_type != StreamType::Fetch as u64 {
677 return Err(stream_type_error(stream_type));
678 }
679 Self::decode(buf)
680 }
681}
682
683impl FetchObjectHeader {
684 /// Encode the fetch object header into the buffer.
685 pub fn encode(&self, buf: &mut impl BufMut) {
686 self.group_id.encode(buf);
687 self.subgroup_id.encode(buf);
688 self.object_id.encode(buf);
689 buf.put_u8(self.publisher_priority);
690 self.extension_count.encode(buf);
691 encode_extensions(&self.extensions, buf);
692 self.payload_length.encode(buf);
693 if self.payload_length.into_inner() == 0 {
694 VarInt::from_usize(self.object_status as usize).encode(buf);
695 }
696 }
697
698 /// Encode the header, refusing a status the framing cannot carry.
699 ///
700 /// Section 8.4.3 puts the Object Status field on the wire only when the
701 /// Object Payload Length is zero, and Section 8.1.1.1 says "Any object
702 /// with a status code other than zero MUST have an empty payload". A
703 /// non-zero status paired with a non-zero payload length therefore has no
704 /// encoding at all: [`Self::encode`] drops the status and the peer reads an
705 /// ordinary object, which is a different object from the one the caller
706 /// described. This refuses instead.
707 ///
708 /// The datagram types on this draft already refuse the same pairing. These
709 /// two did not, and they are the ones a publisher writes on every stream.
710 ///
711 /// # Errors
712 ///
713 /// [`CodecError::InvalidField`] if a non-zero Object Status is paired with
714 /// a non-zero Object Payload Length.
715 pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
716 check_end_of_track(self.object_id, self.object_status)?;
717 if self.payload_length.into_inner() != 0 && self.object_status as usize != 0 {
718 return Err(CodecError::InvalidField);
719 }
720 check_extension_count(self.extension_count, &self.extensions)?;
721 self.encode(buf);
722 Ok(())
723 }
724
725 /// Decode a fetch object header from the buffer.
726 pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
727 let group_id = VarInt::decode(buf)?;
728 let subgroup_id = VarInt::decode(buf)?;
729 let object_id = VarInt::decode(buf)?;
730 if buf.remaining() < 1 {
731 return Err(CodecError::UnexpectedEnd);
732 }
733 let publisher_priority = buf.get_u8();
734 let extension_count = VarInt::decode(buf)?;
735 let extensions = skip_extensions(buf, extension_count.into_inner())?;
736 let payload_length = VarInt::decode(buf)?;
737 let object_status = if payload_length.into_inner() == 0 {
738 let status_val = VarInt::decode(buf)?.into_inner();
739 ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?
740 } else {
741 ObjectStatus::Normal
742 };
743 check_end_of_track(object_id, object_status)?;
744 Ok(Self {
745 group_id,
746 subgroup_id,
747 object_id,
748 publisher_priority,
749 extension_count,
750 extensions,
751 object_status,
752 payload_length,
753 })
754 }
755}