Skip to main content

moqtap_codec/
data_dispatch.rs

1//! Draft-neutral object framing for MoQT data streams.
2//!
3//! [`AnySubgroupObjectReader`](crate::data_dispatch::AnySubgroupObjectReader),
4//! [`AnySubgroupObjectWriter`](crate::data_dispatch::AnySubgroupObjectWriter),
5//! [`AnyFetchObjectReader`](crate::data_dispatch::AnyFetchObjectReader) and
6//! [`AnyFetchObjectWriter`](crate::data_dispatch::AnyFetchObjectWriter)
7//! present one API over thirteen drafts' object encodings. The values they
8//! produce — `AnySubgroupObject`, `AnySubgroupObjectMeta`, `AnyFetchObject`,
9//! `AnyFetchObjectMeta` — are plain structs of primitives, so a caller can
10//! address objects without naming a `draftNN` type.
11//!
12//! Objects on drafts 07-13 are standalone: absolute Object IDs, and (on
13//! drafts 11-13) an extension block whose presence is fixed by the stream
14//! type. Drafts 14-19 delta-encode Object IDs against the previous object on
15//! the stream. Both are constructed from the stream's header and read one
16//! object at a time, so the difference stays inside this module.
17//!
18//! Fetch streams split the same way, at a different draft. Through draft-14 a
19//! fetch object spells out its Group ID, Subgroup ID, Object ID and Publisher
20//! Priority on every object, so each one stands alone. Drafts 15-19 put a
21//! Serialization Flags field first and let it leave any of those four off the
22//! wire, meaning "the prior object's" — and from draft-18 the two ID fields
23//! that remain are differences rather than values. So a fetch object on those
24//! drafts is only meaningful in stream order, and
25//! [`AnyFetchObjectReader`](crate::data_dispatch::AnyFetchObjectReader)
26//! carries the running state that resolves it. The
27//! values it produces are absolute on every draft.
28//!
29//! # Partial buffers
30//!
31//! Reader state after an error is unspecified. A caller that may be handed an
32//! incomplete object clones the reader, decodes against the clone, and
33//! overwrites the real reader only once the decode succeeds.
34
35use bytes::{Buf, BufMut};
36
37use crate::dispatch::{AnyFetchHeader, AnySubgroupHeader};
38use crate::error::CodecError;
39use crate::varint::VarInt;
40use crate::version::DraftVersion;
41
42// ── Draft-neutral object values ─────────────────────────────
43
44/// One object read from a subgroup data stream, normalised across drafts.
45///
46/// Field semantics are identical on every draft 07-19; the per-draft wire
47/// differences (absolute vs delta object IDs, count- vs length-prefixed
48/// extension blocks, typed vs raw status codes) are resolved by
49/// [`AnySubgroupObjectReader`] before this value is produced.
50#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct AnySubgroupObject {
52    /// Absolute Object ID. Already resolved from delta encoding on drafts
53    /// 14-19; copied verbatim on drafts 07-13.
54    pub object_id: u64,
55    /// The extension-header (draft-17+: "property") block's contents,
56    /// excluding any length or count prefix. Empty when the draft has no
57    /// extension block, when the enclosing header's extensions bit is clear,
58    /// or when the block is present but zero-length.
59    ///
60    /// Opaque and never re-parsed by this crate, and a verbatim copy of the
61    /// wire bytes on every draft except draft-08. Draft-08's block is
62    /// count-prefixed with no byte length, so its contents can only be
63    /// delimited by parsing each extension; the blob is therefore
64    /// re-serialized from that parsed form, which re-encodes every varint
65    /// minimally. A draft-08 extension whose value arrived as a legal
66    /// non-minimal varint is semantically but not byte-identically preserved.
67    pub extension_headers: Vec<u8>,
68    /// Number of extensions in `extension_headers`. `Some` only on draft-08,
69    /// whose extension block is count-prefixed rather than
70    /// byte-length-prefixed, so the count cannot be recovered from the blob
71    /// without re-parsing it. `None` on every other draft.
72    pub extension_count: Option<u64>,
73    /// Object Status as the raw wire code, present only when the payload
74    /// length is zero. `None` means a non-empty payload followed and the
75    /// status is implicitly Normal.
76    ///
77    /// Kept as a raw code rather than a typed enum because the assigned set
78    /// changes across drafts and this value crosses drafts — a relay reads a
79    /// status on one and writes it on another, where the same number may mean
80    /// something else or nothing at all. The code is nonetheless always one
81    /// the *source* draft assigns: every draft's decoder refuses an unassigned
82    /// status, so this field never carries a value its draft's Object Status
83    /// section forbids.
84    pub status: Option<u64>,
85    /// Object payload. Empty when `status` is `Some`.
86    pub payload: Vec<u8>,
87}
88
89/// The framing of one subgroup object, without its payload.
90///
91/// Produced by [`AnySubgroupObjectReader::read_object_meta`] for callers that
92/// forward an object's bytes verbatim and never inspect the payload.
93#[derive(Debug, Clone, Copy, PartialEq, Eq)]
94pub struct AnySubgroupObjectMeta {
95    /// Absolute Object ID, resolved as for [`AnySubgroupObject::object_id`].
96    pub object_id: u64,
97    /// Declared payload length in bytes. Zero when `status` is `Some`.
98    pub payload_length: u64,
99    /// Object Status wire code, as for [`AnySubgroupObject::status`].
100    pub status: Option<u64>,
101    /// Byte length of the extension/property block's contents, excluding its
102    /// prefix. Always the length of the blob
103    /// [`AnySubgroupObject::extension_headers`] would carry, which on draft-08
104    /// is a re-serialized copy rather than the wire bytes.
105    pub extension_headers_len: u64,
106    /// Total bytes this object occupies on the wire, prefix fields included.
107    /// Equals the number of bytes the reader consumed.
108    pub wire_len: u64,
109}
110
111/// What an End of Range indicator asserts about the Locations it covers.
112///
113/// Drafts 16-19 let a fetch stream state that a run of Objects was not
114/// serialized instead of sending them: one frame names the Location that ends
115/// the run, and every Location from the previously serialized Object up to and
116/// including that one is covered. The two indicators are the same frame shape
117/// with different claims behind it, and a subscriber may cache the first as a
118/// settled gap while it must not cache the second, so they are carried apart
119/// rather than merged.
120///
121/// Never produced on drafts 07-15, which have no such frame.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum AnyFetchEndOfRange {
124    /// The covered Objects do not exist.
125    NonExistent,
126    /// The covered Objects' status is unknown to the publisher.
127    Unknown,
128}
129
130/// The order a fetch response's groups arrive in.
131///
132/// Drafts 18 and 19 encode an Object's Group ID as a difference from the
133/// previous Object's, and the direction that difference moves is the fetch's
134/// Group Order — which is settled by the control exchange that opened the
135/// fetch and never appears on the data stream. A reader therefore has to be
136/// told, and telling it wrong does not fail to parse: every Object decodes
137/// under a Group ID walking the wrong way.
138///
139/// Ignored on drafts 07-17, whose fetch objects state their Group ID outright.
140#[derive(Debug, Clone, Copy, PartialEq, Eq)]
141pub enum AnyFetchGroupOrder {
142    /// Group IDs increase along the stream; a difference is added.
143    Ascending,
144    /// Group IDs decrease along the stream; a difference is subtracted.
145    Descending,
146}
147
148/// One frame read from a fetch data stream, normalised across drafts.
149///
150/// Usually an object. On drafts 16-19 it may instead be an End of Range
151/// indicator, which carries a Location and no content — [`Self::end_of_range`]
152/// is what tells the two apart, and it is `None` for every object.
153#[derive(Debug, Clone, PartialEq, Eq)]
154pub struct AnyFetchObject {
155    /// Absolute Group ID. Already resolved against the objects before it on
156    /// drafts 15-19, whose fetch objects may omit the field or (on drafts
157    /// 18-19) encode it as a difference; copied verbatim on drafts 07-14.
158    pub group_id: u64,
159    /// Absolute Subgroup ID, resolved as [`Self::group_id`] is. Zero and
160    /// meaningless when [`Self::has_subgroup_id`] is `false`.
161    pub subgroup_id: u64,
162    /// Whether this frame has a Subgroup ID at all.
163    ///
164    /// `true` on every draft 07-15, and for every End of Range indicator's
165    /// predecessor. `false` in two cases drafts 16-19 add: an object whose
166    /// Forwarding Preference is Datagram, which has no Subgroup ID anywhere in
167    /// its framing, and an End of Range indicator, whose Location is a Group
168    /// and Object ID only.
169    ///
170    /// Kept beside `subgroup_id` rather than folded into it because a relay
171    /// that writes this object onto another stream must not invent a Subgroup
172    /// ID of zero for an object that has none: on the receiving draft zero is a
173    /// real subgroup.
174    pub has_subgroup_id: bool,
175    /// Absolute Object ID, resolved as [`Self::group_id`] is.
176    pub object_id: u64,
177    /// Publisher Priority in force for this frame.
178    ///
179    /// Drafts 15-19 let an object omit the field and take the previous
180    /// object's, and an End of Range indicator never carries one. Where
181    /// nothing on the stream has stated a priority, this is 128 — the value
182    /// every draft 15-19 gives a subscription whose Default Publisher Priority
183    /// property is omitted (draft-19 Section 12.4).
184    pub publisher_priority: u8,
185    /// Extension/property block contents, excluding its prefix. Same
186    /// convention as [`AnySubgroupObject::extension_headers`].
187    pub extension_headers: Vec<u8>,
188    /// Number of extensions; `Some` only on draft-08. Same convention as
189    /// [`AnySubgroupObject::extension_count`].
190    pub extension_count: Option<u64>,
191    /// Object Status wire code, present only when the payload is empty.
192    ///
193    /// Always `None` on drafts 16-19: those drafts removed the field from
194    /// fetch objects entirely, stating that Object Status "is only present in
195    /// objects that are delivered via a SUBSCRIPTION, and is absent in Objects
196    /// delivered via a FETCH" (draft-19 Section 11.2.1.1). A zero-length fetch
197    /// object there is an object with no bytes, not a status object.
198    pub status: Option<u64>,
199    /// Which End of Range indicator this frame is, or `None` for an object.
200    ///
201    /// An indicator has a Location and a payload length and nothing else: its
202    /// `payload`, `extension_headers` and `status` are empty, its
203    /// [`Self::has_subgroup_id`] is `false`, and its `publisher_priority` is
204    /// whatever was in force rather than anything it stated.
205    pub end_of_range: Option<AnyFetchEndOfRange>,
206    /// Object payload. Empty when `status` is `Some`.
207    pub payload: Vec<u8>,
208}
209
210/// The framing of one fetch frame, without its payload.
211///
212/// Every field carries the meaning it does on [`AnyFetchObject`].
213#[derive(Debug, Clone, Copy, PartialEq, Eq)]
214pub struct AnyFetchObjectMeta {
215    /// Absolute Group ID.
216    pub group_id: u64,
217    /// Absolute Subgroup ID. Meaningless when `has_subgroup_id` is `false`.
218    pub subgroup_id: u64,
219    /// Whether this frame has a Subgroup ID at all; see
220    /// [`AnyFetchObject::has_subgroup_id`].
221    pub has_subgroup_id: bool,
222    /// Absolute Object ID.
223    pub object_id: u64,
224    /// Publisher Priority in force for this frame; see
225    /// [`AnyFetchObject::publisher_priority`].
226    pub publisher_priority: u8,
227    /// Declared payload length in bytes.
228    pub payload_length: u64,
229    /// Object Status wire code; always `None` on drafts 16-19.
230    pub status: Option<u64>,
231    /// Which End of Range indicator this frame is, or `None` for an object.
232    pub end_of_range: Option<AnyFetchEndOfRange>,
233    /// Byte length of the extension block's contents, excluding its prefix.
234    pub extension_headers_len: u64,
235    /// Total bytes this object occupies on the wire.
236    pub wire_len: u64,
237}
238
239/// The Publisher Priority a fetch frame that states none is read under.
240///
241/// Drafts 16-19 let an object leave the field off the wire and take the
242/// previous object's, and an End of Range indicator carries none at all, so a
243/// stream can reach a frame with no priority ever having been stated. Every one
244/// of those drafts fixes the same fallback for a subscription that never stated
245/// one — draft-19 Section 12.4: "If omitted, the Default Publisher Priority is
246/// 128" — and that is what is reported here.
247///
248/// Draft-15 needs no such fallback: its own reader refuses an object that
249/// inherits a priority with nothing to inherit from, and it has no End of Range
250/// frame, so every draft-15 fetch object has a priority the stream stated.
251///
252/// An End of Range indicator reports the Priority still in force from the last
253/// Object before it, and this constant only when no Object has preceded it.
254/// None of the four drafts decides that. Drafts 17, 18 and 19 say what the
255/// *next* Object inherits — draft-19 Section 11.4.4.2: "Prior Priority: The
256/// Priority from the last actual Object before the End of Range indicator" —
257/// draft-16 does not say even that, and all four agree only that a marker
258/// carries no Priority field. So the answer is chosen here, once, for the four
259/// of them: [`AnyFetchObject::publisher_priority`] is a `u8` with no way to
260/// report "none", and the value in force is one the stream did state, where
261/// this constant would be one it never did.
262#[cfg(any(feature = "draft16", feature = "draft17", feature = "draft18", feature = "draft19"))]
263const DEFAULT_PUBLISHER_PRIORITY: u8 = 128;
264
265/// Conversions shared by the per-draft glue below. Unused when no draft
266/// feature is enabled.
267#[allow(dead_code)]
268mod conv {
269    use super::{AnySubgroupObject, Buf, CodecError};
270    use crate::varint::VarInt;
271
272    /// Advance `buf` past `len` bytes without copying them.
273    pub fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
274        let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
275        if buf.remaining() < len {
276            return Err(CodecError::UnexpectedEnd);
277        }
278        buf.advance(len);
279        Ok(())
280    }
281
282    /// Copy `len` bytes out of `buf`.
283    pub fn take(buf: &mut impl Buf, len: u64) -> Result<Vec<u8>, CodecError> {
284        let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
285        crate::types::read_bytes(buf, len)
286    }
287
288    /// Wrap a value that must fit the varint range.
289    pub fn varint(v: u64) -> Result<VarInt, CodecError> {
290        VarInt::from_u64(v).map_err(|_| CodecError::InvalidField)
291    }
292
293    /// The status code to encode for `object`, or `None` when a payload
294    /// follows instead. An empty payload always carries a status on the
295    /// wire, so a missing one defaults to Normal.
296    ///
297    /// Status `0` with a payload is not a contradiction and is not refused.
298    /// Every draft from 07 to 19 assigns `0x0` to Normal, and every one of them
299    /// encodes a payload-bearing object by leaving the status field off — the
300    /// status is Normal precisely because bytes follow. Saying so explicitly
301    /// asks for the same frame as leaving it out, so both answer `None`, and
302    /// the byte written is identical either way.
303    ///
304    /// Any other status with a payload is refused: those are the statuses whose
305    /// wire form is a status code standing where the payload would be, so there
306    /// is no frame that carries both.
307    pub fn status_to_write(object: &AnySubgroupObject) -> Result<Option<u64>, CodecError> {
308        match (object.status, object.payload.is_empty()) {
309            (Some(0), false) => Ok(None),
310            (Some(_), false) => Err(CodecError::InvalidField),
311            (Some(code), true) => Ok(Some(code)),
312            (None, true) => Ok(Some(0)),
313            (None, false) => Ok(None),
314        }
315    }
316}
317
318// ── Per-draft glue ──────────────────────────────────────────
319
320/// Generates the conversion glue between one draft's standalone
321/// `ObjectHeader` and the draft-neutral object types.
322///
323/// The leading keyword selects the draft's extension-block shape: absent
324/// (draft-07), count-prefixed (draft-08), byte-length-prefixed (drafts
325/// 09/10), or byte-length-prefixed and gated on the stream type (drafts
326/// 11-13).
327macro_rules! legacy_subgroup_glue {
328    (no_extensions $name:ident, $feat:literal, $draft:ident) => {
329        #[cfg(feature = $feat)]
330        mod $name {
331            use super::conv;
332            use super::{AnySubgroupObject, AnySubgroupObjectMeta};
333            use crate::error::CodecError;
334            use crate::$draft::data_stream::ObjectHeader;
335            use crate::$draft::types::ObjectStatus;
336            use bytes::{Buf, BufMut};
337
338            pub fn read_object(buf: &mut impl Buf) -> Result<AnySubgroupObject, CodecError> {
339                let header = ObjectHeader::decode(buf)?;
340                let payload_length = header.payload_length.into_inner();
341                let (status, payload) = if payload_length == 0 {
342                    (Some(header.object_status as u64), Vec::new())
343                } else {
344                    (None, conv::take(buf, payload_length)?)
345                };
346                Ok(AnySubgroupObject {
347                    object_id: header.object_id.into_inner(),
348                    extension_headers: Vec::new(),
349                    extension_count: None,
350                    status,
351                    payload,
352                })
353            }
354
355            pub fn read_object_meta(
356                buf: &mut impl Buf,
357            ) -> Result<AnySubgroupObjectMeta, CodecError> {
358                let start = buf.remaining();
359                let header = ObjectHeader::decode(buf)?;
360                let payload_length = header.payload_length.into_inner();
361                let status = if payload_length == 0 {
362                    Some(header.object_status as u64)
363                } else {
364                    conv::skip(buf, payload_length)?;
365                    None
366                };
367                Ok(AnySubgroupObjectMeta {
368                    object_id: header.object_id.into_inner(),
369                    payload_length,
370                    status,
371                    extension_headers_len: 0,
372                    wire_len: (start - buf.remaining()) as u64,
373                })
374            }
375
376            pub fn write_object(
377                object: &AnySubgroupObject,
378                buf: &mut impl BufMut,
379            ) -> Result<(), CodecError> {
380                if !object.extension_headers.is_empty() {
381                    return Err(CodecError::InvalidField);
382                }
383                let object_status = match conv::status_to_write(object)? {
384                    Some(code) => ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)?,
385                    None => ObjectStatus::Normal,
386                };
387                ObjectHeader {
388                    object_id: conv::varint(object.object_id)?,
389                    payload_length: conv::varint(object.payload.len() as u64)?,
390                    object_status,
391                }
392                .encode(buf);
393                buf.put_slice(&object.payload);
394                Ok(())
395            }
396        }
397    };
398
399    (count_extensions $name:ident, $feat:literal, $draft:ident) => {
400        #[cfg(feature = $feat)]
401        mod $name {
402            use super::conv;
403            use super::{AnySubgroupObject, AnySubgroupObjectMeta};
404            use crate::error::CodecError;
405            use crate::$draft::data_stream::ObjectHeader;
406            use crate::$draft::types::ObjectStatus;
407            use bytes::{Buf, BufMut};
408
409            pub fn read_object(buf: &mut impl Buf) -> Result<AnySubgroupObject, CodecError> {
410                let header = ObjectHeader::decode(buf)?;
411                let payload_length = header.payload_length.into_inner();
412                let (status, payload) = if payload_length == 0 {
413                    (Some(header.object_status as u64), Vec::new())
414                } else {
415                    (None, conv::take(buf, payload_length)?)
416                };
417                Ok(AnySubgroupObject {
418                    object_id: header.object_id.into_inner(),
419                    extension_headers: header.extensions,
420                    extension_count: Some(header.extension_count.into_inner()),
421                    status,
422                    payload,
423                })
424            }
425
426            pub fn read_object_meta(
427                buf: &mut impl Buf,
428            ) -> Result<AnySubgroupObjectMeta, CodecError> {
429                let start = buf.remaining();
430                let header = ObjectHeader::decode(buf)?;
431                let payload_length = header.payload_length.into_inner();
432                let status = if payload_length == 0 {
433                    Some(header.object_status as u64)
434                } else {
435                    conv::skip(buf, payload_length)?;
436                    None
437                };
438                Ok(AnySubgroupObjectMeta {
439                    object_id: header.object_id.into_inner(),
440                    payload_length,
441                    status,
442                    extension_headers_len: header.extensions.len() as u64,
443                    wire_len: (start - buf.remaining()) as u64,
444                })
445            }
446
447            pub fn write_object(
448                object: &AnySubgroupObject,
449                buf: &mut impl BufMut,
450            ) -> Result<(), CodecError> {
451                let extension_count = match object.extension_count {
452                    Some(count) => count,
453                    None if object.extension_headers.is_empty() => 0,
454                    None => return Err(CodecError::InvalidField),
455                };
456                let object_status = match conv::status_to_write(object)? {
457                    Some(code) => ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)?,
458                    None => ObjectStatus::Normal,
459                };
460                ObjectHeader {
461                    object_id: conv::varint(object.object_id)?,
462                    extension_count: conv::varint(extension_count)?,
463                    extensions: object.extension_headers.clone(),
464                    payload_length: conv::varint(object.payload.len() as u64)?,
465                    object_status,
466                }
467                .encode(buf);
468                buf.put_slice(&object.payload);
469                Ok(())
470            }
471        }
472    };
473
474    (length_extensions $name:ident, $feat:literal, $draft:ident) => {
475        #[cfg(feature = $feat)]
476        mod $name {
477            use super::conv;
478            use super::{AnySubgroupObject, AnySubgroupObjectMeta};
479            use crate::error::CodecError;
480            use crate::$draft::data_stream::ObjectHeader;
481            use crate::$draft::types::ObjectStatus;
482            use bytes::{Buf, BufMut};
483
484            pub fn read_object(buf: &mut impl Buf) -> Result<AnySubgroupObject, CodecError> {
485                let header = ObjectHeader::decode(buf)?;
486                let payload_length = header.payload_length.into_inner();
487                let (status, payload) = if payload_length == 0 {
488                    (Some(header.object_status as u64), Vec::new())
489                } else {
490                    (None, conv::take(buf, payload_length)?)
491                };
492                Ok(AnySubgroupObject {
493                    object_id: header.object_id.into_inner(),
494                    extension_headers: header.extensions,
495                    extension_count: None,
496                    status,
497                    payload,
498                })
499            }
500
501            pub fn read_object_meta(
502                buf: &mut impl Buf,
503            ) -> Result<AnySubgroupObjectMeta, CodecError> {
504                let start = buf.remaining();
505                let header = ObjectHeader::decode(buf)?;
506                let payload_length = header.payload_length.into_inner();
507                let status = if payload_length == 0 {
508                    Some(header.object_status as u64)
509                } else {
510                    conv::skip(buf, payload_length)?;
511                    None
512                };
513                Ok(AnySubgroupObjectMeta {
514                    object_id: header.object_id.into_inner(),
515                    payload_length,
516                    status,
517                    extension_headers_len: header.extension_headers_length.into_inner(),
518                    wire_len: (start - buf.remaining()) as u64,
519                })
520            }
521
522            pub fn write_object(
523                object: &AnySubgroupObject,
524                buf: &mut impl BufMut,
525            ) -> Result<(), CodecError> {
526                let object_status = match conv::status_to_write(object)? {
527                    Some(code) => ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)?,
528                    None => ObjectStatus::Normal,
529                };
530                ObjectHeader {
531                    object_id: conv::varint(object.object_id)?,
532                    extension_headers_length: conv::varint(object.extension_headers.len() as u64)?,
533                    extensions: object.extension_headers.clone(),
534                    payload_length: conv::varint(object.payload.len() as u64)?,
535                    object_status,
536                }
537                .encode(buf);
538                buf.put_slice(&object.payload);
539                Ok(())
540            }
541        }
542    };
543
544    (gated_extensions $name:ident, $feat:literal, $draft:ident) => {
545        #[cfg(feature = $feat)]
546        mod $name {
547            use super::conv;
548            use super::{AnySubgroupObject, AnySubgroupObjectMeta};
549            use crate::error::CodecError;
550            use crate::$draft::data_stream::ObjectHeader;
551            use crate::$draft::types::ObjectStatus;
552            use bytes::{Buf, BufMut};
553
554            pub fn read_object(
555                extensions: bool,
556                buf: &mut impl Buf,
557            ) -> Result<AnySubgroupObject, CodecError> {
558                let header = ObjectHeader::decode_with_extensions(extensions, buf)?;
559                let payload_length = header.payload_length.into_inner();
560                let (status, payload) = if payload_length == 0 {
561                    (Some(header.object_status as u64), Vec::new())
562                } else {
563                    (None, conv::take(buf, payload_length)?)
564                };
565                Ok(AnySubgroupObject {
566                    object_id: header.object_id.into_inner(),
567                    extension_headers: header.extensions,
568                    extension_count: None,
569                    status,
570                    payload,
571                })
572            }
573
574            pub fn read_object_meta(
575                extensions: bool,
576                buf: &mut impl Buf,
577            ) -> Result<AnySubgroupObjectMeta, CodecError> {
578                let start = buf.remaining();
579                let header = ObjectHeader::decode_with_extensions(extensions, buf)?;
580                let payload_length = header.payload_length.into_inner();
581                let status = if payload_length == 0 {
582                    Some(header.object_status as u64)
583                } else {
584                    conv::skip(buf, payload_length)?;
585                    None
586                };
587                Ok(AnySubgroupObjectMeta {
588                    object_id: header.object_id.into_inner(),
589                    payload_length,
590                    status,
591                    extension_headers_len: header.extension_headers_length.into_inner(),
592                    wire_len: (start - buf.remaining()) as u64,
593                })
594            }
595
596            pub fn write_object(
597                extensions: bool,
598                object: &AnySubgroupObject,
599                buf: &mut impl BufMut,
600            ) -> Result<(), CodecError> {
601                if !extensions && !object.extension_headers.is_empty() {
602                    return Err(CodecError::InvalidField);
603                }
604                let object_status = match conv::status_to_write(object)? {
605                    Some(code) => ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)?,
606                    None => ObjectStatus::Normal,
607                };
608                ObjectHeader {
609                    object_id: conv::varint(object.object_id)?,
610                    extension_headers_length: conv::varint(object.extension_headers.len() as u64)?,
611                    extensions: object.extension_headers.clone(),
612                    payload_length: conv::varint(object.payload.len() as u64)?,
613                    object_status,
614                }
615                .encode_with_extensions(extensions, buf);
616                buf.put_slice(&object.payload);
617                Ok(())
618            }
619        }
620    };
621}
622
623legacy_subgroup_glue!(no_extensions sg07, "draft07", draft07);
624legacy_subgroup_glue!(count_extensions sg08, "draft08", draft08);
625legacy_subgroup_glue!(length_extensions sg09, "draft09", draft09);
626legacy_subgroup_glue!(length_extensions sg10, "draft10", draft10);
627legacy_subgroup_glue!(gated_extensions sg11, "draft11", draft11);
628legacy_subgroup_glue!(gated_extensions sg12, "draft12", draft12);
629legacy_subgroup_glue!(gated_extensions sg13, "draft13", draft13);
630
631/// Generates the conversion glue between one draft's stateful
632/// `SubgroupObjectReader` and the draft-neutral object types.
633///
634/// Every draft from 14 on carries a typed `ObjectStatus`, so the leading
635/// keyword selects how the payload length reaches the wire instead: draft-14
636/// derives it from the payload, while drafts 15-19 carry an explicit
637/// payload-length field, which this glue always sets from the payload.
638///
639/// Both arms funnel the draft-neutral `AnySubgroupObject`, whose status is a
640/// bare `u64`, through the target draft's `ObjectStatus::from_u64`. That is
641/// the one place a status code the target draft does not assign can still be
642/// offered to an encoder at run time — relaying an object between drafts, for
643/// instance — and it is refused there with `CodecError::InvalidField`.
644macro_rules! modern_subgroup_glue {
645    (derived_length $name:ident, $feat:literal, $draft:ident) => {
646        #[cfg(feature = $feat)]
647        mod $name {
648            use super::conv;
649            use super::{AnySubgroupObject, AnySubgroupObjectMeta};
650            use crate::error::CodecError;
651            use crate::$draft::data_stream::{SubgroupObject, SubgroupObjectReader};
652            use crate::$draft::types::ObjectStatus;
653            use bytes::{Buf, BufMut};
654
655            pub fn read_object(
656                reader: &mut SubgroupObjectReader,
657                buf: &mut impl Buf,
658            ) -> Result<AnySubgroupObject, CodecError> {
659                let object = reader.read_object(buf)?;
660                Ok(AnySubgroupObject {
661                    object_id: object.object_id.into_inner(),
662                    extension_headers: object.extension_headers,
663                    extension_count: None,
664                    status: object.status.map(ObjectStatus::as_u64),
665                    payload: object.payload,
666                })
667            }
668
669            pub fn read_object_meta(
670                reader: &mut SubgroupObjectReader,
671                buf: &mut impl Buf,
672            ) -> Result<AnySubgroupObjectMeta, CodecError> {
673                let meta = reader.read_object_meta(buf)?;
674                Ok(AnySubgroupObjectMeta {
675                    object_id: meta.object_id,
676                    payload_length: meta.payload_length,
677                    status: meta.status,
678                    extension_headers_len: meta.extension_headers_len,
679                    wire_len: meta.wire_len,
680                })
681            }
682
683            pub fn write_object(
684                writer: &mut SubgroupObjectReader,
685                object: &AnySubgroupObject,
686                buf: &mut impl BufMut,
687            ) -> Result<(), CodecError> {
688                let status = match conv::status_to_write(object)? {
689                    Some(code) => {
690                        Some(ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)?)
691                    }
692                    None => None,
693                };
694                writer.write_object(
695                    &SubgroupObject {
696                        object_id: conv::varint(object.object_id)?,
697                        extension_headers: object.extension_headers.clone(),
698                        status,
699                        payload: object.payload.clone(),
700                    },
701                    buf,
702                )
703            }
704        }
705    };
706
707    (explicit_length $name:ident, $feat:literal, $draft:ident) => {
708        #[cfg(feature = $feat)]
709        mod $name {
710            use super::conv;
711            use super::{AnySubgroupObject, AnySubgroupObjectMeta};
712            use crate::error::CodecError;
713            use crate::$draft::data_stream::{SubgroupObject, SubgroupObjectReader};
714            use crate::$draft::types::ObjectStatus;
715            use bytes::{Buf, BufMut};
716
717            pub fn read_object(
718                reader: &mut SubgroupObjectReader,
719                buf: &mut impl Buf,
720            ) -> Result<AnySubgroupObject, CodecError> {
721                let object = reader.read_object(buf)?;
722                Ok(AnySubgroupObject {
723                    object_id: object.object_id.into_inner(),
724                    extension_headers: object.extension_headers,
725                    extension_count: None,
726                    status: object.object_status.map(ObjectStatus::as_u64),
727                    payload: object.payload,
728                })
729            }
730
731            pub fn read_object_meta(
732                reader: &mut SubgroupObjectReader,
733                buf: &mut impl Buf,
734            ) -> Result<AnySubgroupObjectMeta, CodecError> {
735                let meta = reader.read_object_meta(buf)?;
736                Ok(AnySubgroupObjectMeta {
737                    object_id: meta.object_id,
738                    payload_length: meta.payload_length,
739                    status: meta.status,
740                    extension_headers_len: meta.extension_headers_len,
741                    wire_len: meta.wire_len,
742                })
743            }
744
745            pub fn write_object(
746                writer: &mut SubgroupObjectReader,
747                object: &AnySubgroupObject,
748                buf: &mut impl BufMut,
749            ) -> Result<(), CodecError> {
750                let object_status = match conv::status_to_write(object)? {
751                    Some(code) => {
752                        Some(ObjectStatus::from_u64(code).ok_or(CodecError::InvalidField)?)
753                    }
754                    None => None,
755                };
756                writer.write_object(
757                    &SubgroupObject {
758                        object_id: conv::varint(object.object_id)?,
759                        extension_headers: object.extension_headers.clone(),
760                        payload_length: conv::varint(object.payload.len() as u64)?,
761                        object_status,
762                        payload: object.payload.clone(),
763                    },
764                    buf,
765                )
766            }
767        }
768    };
769}
770
771modern_subgroup_glue!(derived_length sg14, "draft14", draft14);
772modern_subgroup_glue!(explicit_length sg15, "draft15", draft15);
773modern_subgroup_glue!(explicit_length sg16, "draft16", draft16);
774modern_subgroup_glue!(explicit_length sg17, "draft17", draft17);
775modern_subgroup_glue!(explicit_length sg18, "draft18", draft18);
776modern_subgroup_glue!(explicit_length sg19, "draft19", draft19);
777
778/// Generates the conversion glue for one draft's fetch objects.
779///
780/// The leading keyword selects the extension-block shape, as for
781/// `legacy_subgroup_glue!`. Unlike subgroup objects, the fetch extension
782/// block is unconditional on drafts 09-13 — it is never gated on the stream
783/// type.
784macro_rules! fetch_glue {
785    (no_extensions $name:ident, $feat:literal, $draft:ident) => {
786        #[cfg(feature = $feat)]
787        mod $name {
788            use super::conv;
789            use super::{AnyFetchObject, AnyFetchObjectMeta};
790            use crate::error::CodecError;
791            use crate::$draft::data_stream::FetchObjectHeader;
792            use bytes::Buf;
793
794            pub fn read_object(buf: &mut impl Buf) -> Result<AnyFetchObject, CodecError> {
795                let header = FetchObjectHeader::decode(buf)?;
796                let payload_length = header.payload_length.into_inner();
797                let (status, payload) = if payload_length == 0 {
798                    (Some(header.object_status as u64), Vec::new())
799                } else {
800                    (None, conv::take(buf, payload_length)?)
801                };
802                Ok(AnyFetchObject {
803                    group_id: header.group_id.into_inner(),
804                    subgroup_id: header.subgroup_id.into_inner(),
805                    has_subgroup_id: true,
806                    object_id: header.object_id.into_inner(),
807                    publisher_priority: header.publisher_priority,
808                    extension_headers: Vec::new(),
809                    extension_count: None,
810                    status,
811                    end_of_range: None,
812                    payload,
813                })
814            }
815
816            pub fn read_object_meta(buf: &mut impl Buf) -> Result<AnyFetchObjectMeta, CodecError> {
817                let start = buf.remaining();
818                let header = FetchObjectHeader::decode(buf)?;
819                let payload_length = header.payload_length.into_inner();
820                let status = if payload_length == 0 {
821                    Some(header.object_status as u64)
822                } else {
823                    conv::skip(buf, payload_length)?;
824                    None
825                };
826                Ok(AnyFetchObjectMeta {
827                    group_id: header.group_id.into_inner(),
828                    subgroup_id: header.subgroup_id.into_inner(),
829                    has_subgroup_id: true,
830                    object_id: header.object_id.into_inner(),
831                    publisher_priority: header.publisher_priority,
832                    payload_length,
833                    status,
834                    end_of_range: None,
835                    extension_headers_len: 0,
836                    wire_len: (start - buf.remaining()) as u64,
837                })
838            }
839        }
840    };
841
842    (count_extensions $name:ident, $feat:literal, $draft:ident) => {
843        #[cfg(feature = $feat)]
844        mod $name {
845            use super::conv;
846            use super::{AnyFetchObject, AnyFetchObjectMeta};
847            use crate::error::CodecError;
848            use crate::$draft::data_stream::FetchObjectHeader;
849            use bytes::Buf;
850
851            pub fn read_object(buf: &mut impl Buf) -> Result<AnyFetchObject, CodecError> {
852                let header = FetchObjectHeader::decode(buf)?;
853                let payload_length = header.payload_length.into_inner();
854                let (status, payload) = if payload_length == 0 {
855                    (Some(header.object_status as u64), Vec::new())
856                } else {
857                    (None, conv::take(buf, payload_length)?)
858                };
859                Ok(AnyFetchObject {
860                    group_id: header.group_id.into_inner(),
861                    subgroup_id: header.subgroup_id.into_inner(),
862                    has_subgroup_id: true,
863                    object_id: header.object_id.into_inner(),
864                    publisher_priority: header.publisher_priority,
865                    extension_headers: header.extensions,
866                    extension_count: Some(header.extension_count.into_inner()),
867                    status,
868                    end_of_range: None,
869                    payload,
870                })
871            }
872
873            pub fn read_object_meta(buf: &mut impl Buf) -> Result<AnyFetchObjectMeta, CodecError> {
874                let start = buf.remaining();
875                let header = FetchObjectHeader::decode(buf)?;
876                let payload_length = header.payload_length.into_inner();
877                let status = if payload_length == 0 {
878                    Some(header.object_status as u64)
879                } else {
880                    conv::skip(buf, payload_length)?;
881                    None
882                };
883                Ok(AnyFetchObjectMeta {
884                    group_id: header.group_id.into_inner(),
885                    subgroup_id: header.subgroup_id.into_inner(),
886                    has_subgroup_id: true,
887                    object_id: header.object_id.into_inner(),
888                    publisher_priority: header.publisher_priority,
889                    payload_length,
890                    status,
891                    end_of_range: None,
892                    extension_headers_len: header.extensions.len() as u64,
893                    wire_len: (start - buf.remaining()) as u64,
894                })
895            }
896        }
897    };
898
899    (length_extensions $name:ident, $feat:literal, $draft:ident) => {
900        #[cfg(feature = $feat)]
901        mod $name {
902            use super::conv;
903            use super::{AnyFetchObject, AnyFetchObjectMeta};
904            use crate::error::CodecError;
905            use crate::$draft::data_stream::FetchObjectHeader;
906            use bytes::Buf;
907
908            pub fn read_object(buf: &mut impl Buf) -> Result<AnyFetchObject, CodecError> {
909                let header = FetchObjectHeader::decode(buf)?;
910                let payload_length = header.payload_length.into_inner();
911                let (status, payload) = if payload_length == 0 {
912                    (Some(header.object_status as u64), Vec::new())
913                } else {
914                    (None, conv::take(buf, payload_length)?)
915                };
916                Ok(AnyFetchObject {
917                    group_id: header.group_id.into_inner(),
918                    subgroup_id: header.subgroup_id.into_inner(),
919                    has_subgroup_id: true,
920                    object_id: header.object_id.into_inner(),
921                    publisher_priority: header.publisher_priority,
922                    extension_headers: header.extensions,
923                    extension_count: None,
924                    status,
925                    end_of_range: None,
926                    payload,
927                })
928            }
929
930            pub fn read_object_meta(buf: &mut impl Buf) -> Result<AnyFetchObjectMeta, CodecError> {
931                let start = buf.remaining();
932                let header = FetchObjectHeader::decode(buf)?;
933                let payload_length = header.payload_length.into_inner();
934                let status = if payload_length == 0 {
935                    Some(header.object_status as u64)
936                } else {
937                    conv::skip(buf, payload_length)?;
938                    None
939                };
940                Ok(AnyFetchObjectMeta {
941                    group_id: header.group_id.into_inner(),
942                    subgroup_id: header.subgroup_id.into_inner(),
943                    has_subgroup_id: true,
944                    object_id: header.object_id.into_inner(),
945                    publisher_priority: header.publisher_priority,
946                    payload_length,
947                    status,
948                    end_of_range: None,
949                    extension_headers_len: header.extension_headers_length.into_inner(),
950                    wire_len: (start - buf.remaining()) as u64,
951                })
952            }
953        }
954    };
955}
956
957fetch_glue!(no_extensions fo07, "draft07", draft07);
958fetch_glue!(count_extensions fo08, "draft08", draft08);
959fetch_glue!(length_extensions fo09, "draft09", draft09);
960fetch_glue!(length_extensions fo10, "draft10", draft10);
961fetch_glue!(length_extensions fo11, "draft11", draft11);
962fetch_glue!(length_extensions fo12, "draft12", draft12);
963fetch_glue!(length_extensions fo13, "draft13", draft13);
964
965#[cfg(feature = "draft14")]
966mod fo14 {
967    use super::{AnyFetchObject, AnyFetchObjectMeta};
968    use crate::draft14::data_stream::FetchObject;
969    use crate::draft14::types::ObjectStatus;
970    use crate::error::CodecError;
971    use bytes::Buf;
972
973    pub fn read_object(buf: &mut impl Buf) -> Result<AnyFetchObject, CodecError> {
974        let object = FetchObject::decode(buf)?;
975        Ok(AnyFetchObject {
976            group_id: object.group_id.into_inner(),
977            subgroup_id: object.subgroup_id.into_inner(),
978            has_subgroup_id: true,
979            object_id: object.object_id.into_inner(),
980            publisher_priority: object.publisher_priority,
981            extension_headers: object.extension_headers,
982            extension_count: None,
983            status: object.status.map(ObjectStatus::as_u64),
984            end_of_range: None,
985            payload: object.payload,
986        })
987    }
988
989    pub fn read_object_meta(buf: &mut impl Buf) -> Result<AnyFetchObjectMeta, CodecError> {
990        let meta = FetchObject::decode_meta(buf)?;
991        Ok(AnyFetchObjectMeta {
992            group_id: meta.group_id,
993            subgroup_id: meta.subgroup_id,
994            has_subgroup_id: true,
995            object_id: meta.object_id,
996            publisher_priority: meta.publisher_priority,
997            payload_length: meta.payload_length,
998            status: meta.status,
999            end_of_range: None,
1000            extension_headers_len: meta.extension_headers_len,
1001            wire_len: meta.wire_len,
1002        })
1003    }
1004}
1005
1006#[cfg(feature = "draft15")]
1007mod fo15 {
1008    use super::{conv, AnyFetchObject, AnyFetchObjectMeta};
1009    use crate::draft15::data_stream::FetchObjectReader;
1010    use crate::draft15::types::ObjectStatus;
1011    use crate::error::CodecError;
1012    use bytes::Buf;
1013
1014    pub fn read_object(
1015        reader: &mut FetchObjectReader,
1016        buf: &mut impl Buf,
1017    ) -> Result<AnyFetchObject, CodecError> {
1018        let header = reader.read_object_header(buf)?;
1019        // The draft-15 reader stops at the payload length so a caller can
1020        // forward the bytes without copying them; the draft-neutral object
1021        // holds the payload, so the copy happens here instead.
1022        let payload = conv::take(buf, header.payload_length.into_inner())?;
1023        Ok(AnyFetchObject {
1024            group_id: header.group_id.into_inner(),
1025            subgroup_id: header.subgroup_id.into_inner(),
1026            has_subgroup_id: true,
1027            object_id: header.object_id.into_inner(),
1028            publisher_priority: header.publisher_priority,
1029            extension_headers: header.extension_headers,
1030            extension_count: None,
1031            // Already `Some` only for a zero-length object, which is the
1032            // draft-neutral convention too.
1033            status: header.object_status.map(ObjectStatus::as_u64),
1034            end_of_range: None,
1035            payload,
1036        })
1037    }
1038
1039    pub fn read_object_frame(
1040        reader: &mut FetchObjectReader,
1041        buf: &mut impl Buf,
1042    ) -> Result<super::AnyFetchFrame, CodecError> {
1043        let start = buf.remaining();
1044        let header = reader.read_object_header(buf)?;
1045        let payload_length = header.payload_length.into_inner();
1046        conv::skip(buf, payload_length)?;
1047        let meta = AnyFetchObjectMeta {
1048            group_id: header.group_id.into_inner(),
1049            subgroup_id: header.subgroup_id.into_inner(),
1050            has_subgroup_id: true,
1051            object_id: header.object_id.into_inner(),
1052            publisher_priority: header.publisher_priority,
1053            payload_length,
1054            status: header.object_status.map(ObjectStatus::as_u64),
1055            end_of_range: None,
1056            extension_headers_len: header.extension_headers.len() as u64,
1057            wire_len: (start - buf.remaining()) as u64,
1058        };
1059        Ok(super::AnyFetchFrame {
1060            meta,
1061            draft: crate::version::DraftVersion::Draft15,
1062            shape: super::FetchFrameShape::Draft15(header),
1063        })
1064    }
1065
1066    pub fn read_object_meta(
1067        reader: &mut FetchObjectReader,
1068        buf: &mut impl Buf,
1069    ) -> Result<AnyFetchObjectMeta, CodecError> {
1070        read_object_frame(reader, buf).map(|frame| frame.meta)
1071    }
1072}
1073
1074#[cfg(feature = "draft16")]
1075mod fo16 {
1076    use super::DEFAULT_PUBLISHER_PRIORITY;
1077    use super::{conv, AnyFetchEndOfRange, AnyFetchObject, AnyFetchObjectMeta};
1078    use crate::draft16::data_stream::{
1079        FetchEndOfRange, FetchObjectHeader, FetchObjectLocation, FetchObjectReader,
1080    };
1081    use crate::error::CodecError;
1082    use bytes::Buf;
1083
1084    /// Draft-16 keeps the frame and its resolved Location apart, and both are
1085    /// carried out of here: the Location fills the draft-neutral value, and the
1086    /// pair of them is what re-encoding this frame later takes.
1087    fn parts(
1088        reader: &mut FetchObjectReader,
1089        buf: &mut impl Buf,
1090    ) -> Result<(FetchObjectHeader, FetchObjectLocation), CodecError> {
1091        let header = FetchObjectHeader::decode(buf)?;
1092        let location = reader.resolve(&header)?;
1093        Ok((header, location))
1094    }
1095
1096    fn resolved(location: &FetchObjectLocation) -> super::Resolved {
1097        let end_of_range = location.end_of_range;
1098        super::Resolved {
1099            group_id: location.group_id,
1100            // Draft-16 Section 10.4.4.2 gives an End of Range indicator a Group
1101            // ID and an Object ID and says "Subgroup ID, Priority and Extensions
1102            // are not present". Its per-draft resolver still reads the two low
1103            // flag bits of a marker as Subgroup ID mode zero and answers zero,
1104            // which is a real Subgroup ID; the draft-neutral value says the
1105            // marker has none, as drafts 17-19 do.
1106            subgroup_id: location.subgroup_id.filter(|_| end_of_range.is_none()),
1107            object_id: location.object_id,
1108            publisher_priority: location.publisher_priority.unwrap_or(DEFAULT_PUBLISHER_PRIORITY),
1109            end_of_range: end_of_range.map(|r| match r {
1110                FetchEndOfRange::NonExistent => AnyFetchEndOfRange::NonExistent,
1111                FetchEndOfRange::Unknown => AnyFetchEndOfRange::Unknown,
1112            }),
1113        }
1114    }
1115
1116    pub fn read_object(
1117        reader: &mut FetchObjectReader,
1118        buf: &mut impl Buf,
1119    ) -> Result<AnyFetchObject, CodecError> {
1120        let (header, location) = parts(reader, buf)?;
1121        let payload = conv::take(buf, header.payload_length.into_inner())?;
1122        Ok(resolved(&location).into_object(header.extensions.unwrap_or_default(), payload))
1123    }
1124
1125    pub fn read_object_frame(
1126        reader: &mut FetchObjectReader,
1127        buf: &mut impl Buf,
1128    ) -> Result<super::AnyFetchFrame, CodecError> {
1129        let start = buf.remaining();
1130        let (header, location) = parts(reader, buf)?;
1131        let payload_length = header.payload_length.into_inner();
1132        conv::skip(buf, payload_length)?;
1133        let meta = resolved(&location).into_meta(
1134            header.extensions.as_ref().map_or(0, |e| e.len() as u64),
1135            payload_length,
1136            (start - buf.remaining()) as u64,
1137        );
1138        Ok(super::AnyFetchFrame {
1139            meta,
1140            draft: crate::version::DraftVersion::Draft16,
1141            shape: super::FetchFrameShape::Draft16(header, location),
1142        })
1143    }
1144
1145    pub fn read_object_meta(
1146        reader: &mut FetchObjectReader,
1147        buf: &mut impl Buf,
1148    ) -> Result<AnyFetchObjectMeta, CodecError> {
1149        read_object_frame(reader, buf).map(|frame| frame.meta)
1150    }
1151}
1152
1153#[cfg(feature = "draft17")]
1154mod fo17 {
1155    use super::{conv, AnyFetchEndOfRange, AnyFetchObject, AnyFetchObjectMeta};
1156    use crate::draft17::data_stream::{EndOfRange, FetchObject, FetchObjectReader};
1157    use crate::error::CodecError;
1158    use bytes::Buf;
1159
1160    fn resolved(object: &FetchObject) -> super::Resolved {
1161        super::Resolved {
1162            group_id: object.group_id,
1163            subgroup_id: object.subgroup_id,
1164            object_id: object.object_id,
1165            publisher_priority: object
1166                .publisher_priority
1167                .unwrap_or(super::DEFAULT_PUBLISHER_PRIORITY),
1168            end_of_range: object.header.end_of_range().map(|r| match r {
1169                EndOfRange::NonExistent => AnyFetchEndOfRange::NonExistent,
1170                EndOfRange::Unknown => AnyFetchEndOfRange::Unknown,
1171            }),
1172        }
1173    }
1174
1175    pub fn read_object(
1176        reader: &mut FetchObjectReader,
1177        buf: &mut impl Buf,
1178    ) -> Result<AnyFetchObject, CodecError> {
1179        let object = reader.read_object_header(buf)?;
1180        let resolved = resolved(&object);
1181        let payload = conv::take(buf, object.header.payload_length.into_inner())?;
1182        Ok(resolved.into_object(object.header.properties, payload))
1183    }
1184
1185    pub fn read_object_frame(
1186        reader: &mut FetchObjectReader,
1187        buf: &mut impl Buf,
1188    ) -> Result<super::AnyFetchFrame, CodecError> {
1189        let start = buf.remaining();
1190        let object = reader.read_object_header(buf)?;
1191        let resolved = resolved(&object);
1192        let payload_length = object.header.payload_length.into_inner();
1193        conv::skip(buf, payload_length)?;
1194        let meta = resolved.into_meta(
1195            object.header.properties.len() as u64,
1196            payload_length,
1197            (start - buf.remaining()) as u64,
1198        );
1199        Ok(super::AnyFetchFrame {
1200            meta,
1201            draft: crate::version::DraftVersion::Draft17,
1202            shape: super::FetchFrameShape::Draft17(object),
1203        })
1204    }
1205
1206    pub fn read_object_meta(
1207        reader: &mut FetchObjectReader,
1208        buf: &mut impl Buf,
1209    ) -> Result<AnyFetchObjectMeta, CodecError> {
1210        read_object_frame(reader, buf).map(|frame| frame.meta)
1211    }
1212}
1213
1214#[cfg(feature = "draft18")]
1215mod fo18 {
1216    use super::{conv, AnyFetchEndOfRange, AnyFetchObject, AnyFetchObjectMeta};
1217    use crate::draft18::data_stream::{EndOfRange, FetchObject, FetchObjectReader};
1218    use crate::error::CodecError;
1219    use bytes::Buf;
1220
1221    fn resolved(object: &FetchObject) -> super::Resolved {
1222        super::Resolved {
1223            group_id: object.group_id,
1224            subgroup_id: object.subgroup_id,
1225            object_id: object.object_id,
1226            publisher_priority: object
1227                .publisher_priority
1228                .unwrap_or(super::DEFAULT_PUBLISHER_PRIORITY),
1229            end_of_range: object.header.end_of_range().map(|r| match r {
1230                EndOfRange::NonExistent => AnyFetchEndOfRange::NonExistent,
1231                EndOfRange::Unknown => AnyFetchEndOfRange::Unknown,
1232            }),
1233        }
1234    }
1235
1236    pub fn read_object(
1237        reader: &mut FetchObjectReader,
1238        buf: &mut impl Buf,
1239    ) -> Result<AnyFetchObject, CodecError> {
1240        let object = reader.read_object_header(buf)?;
1241        let resolved = resolved(&object);
1242        let payload = conv::take(buf, object.header.payload_length.into_inner())?;
1243        Ok(resolved.into_object(object.header.properties, payload))
1244    }
1245
1246    pub fn read_object_frame(
1247        reader: &mut FetchObjectReader,
1248        buf: &mut impl Buf,
1249    ) -> Result<super::AnyFetchFrame, CodecError> {
1250        let start = buf.remaining();
1251        let object = reader.read_object_header(buf)?;
1252        let resolved = resolved(&object);
1253        let payload_length = object.header.payload_length.into_inner();
1254        conv::skip(buf, payload_length)?;
1255        let meta = resolved.into_meta(
1256            object.header.properties.len() as u64,
1257            payload_length,
1258            (start - buf.remaining()) as u64,
1259        );
1260        Ok(super::AnyFetchFrame {
1261            meta,
1262            draft: crate::version::DraftVersion::Draft18,
1263            shape: super::FetchFrameShape::Draft18(object),
1264        })
1265    }
1266
1267    pub fn read_object_meta(
1268        reader: &mut FetchObjectReader,
1269        buf: &mut impl Buf,
1270    ) -> Result<AnyFetchObjectMeta, CodecError> {
1271        read_object_frame(reader, buf).map(|frame| frame.meta)
1272    }
1273}
1274
1275#[cfg(feature = "draft19")]
1276mod fo19 {
1277    use super::{conv, AnyFetchEndOfRange, AnyFetchObject, AnyFetchObjectMeta};
1278    use crate::draft19::data_stream::{FetchEndOfRange, FetchObject, FetchObjectReader};
1279    use crate::error::CodecError;
1280    use bytes::Buf;
1281
1282    fn resolved(object: &FetchObject) -> super::Resolved {
1283        super::Resolved {
1284            group_id: object.group_id,
1285            subgroup_id: object.subgroup_id,
1286            object_id: object.object_id,
1287            publisher_priority: object
1288                .publisher_priority
1289                .unwrap_or(super::DEFAULT_PUBLISHER_PRIORITY),
1290            end_of_range: object.header.end_of_range().map(|r| match r {
1291                FetchEndOfRange::NonExistent => AnyFetchEndOfRange::NonExistent,
1292                FetchEndOfRange::Unknown => AnyFetchEndOfRange::Unknown,
1293            }),
1294        }
1295    }
1296
1297    pub fn read_object(
1298        reader: &mut FetchObjectReader,
1299        buf: &mut impl Buf,
1300    ) -> Result<AnyFetchObject, CodecError> {
1301        let object = reader.read_object_header(buf)?;
1302        let resolved = resolved(&object);
1303        let payload = conv::take(buf, object.header.payload_length.into_inner())?;
1304        Ok(resolved.into_object(object.header.properties.unwrap_or_default(), payload))
1305    }
1306
1307    pub fn read_object_frame(
1308        reader: &mut FetchObjectReader,
1309        buf: &mut impl Buf,
1310    ) -> Result<super::AnyFetchFrame, CodecError> {
1311        let start = buf.remaining();
1312        let object = reader.read_object_header(buf)?;
1313        let resolved = resolved(&object);
1314        let payload_length = object.header.payload_length.into_inner();
1315        conv::skip(buf, payload_length)?;
1316        let meta = resolved.into_meta(
1317            object.header.properties.as_ref().map_or(0, |p| p.len() as u64),
1318            payload_length,
1319            (start - buf.remaining()) as u64,
1320        );
1321        Ok(super::AnyFetchFrame {
1322            meta,
1323            draft: crate::version::DraftVersion::Draft19,
1324            shape: super::FetchFrameShape::Draft19(object),
1325        })
1326    }
1327
1328    pub fn read_object_meta(
1329        reader: &mut FetchObjectReader,
1330        buf: &mut impl Buf,
1331    ) -> Result<AnyFetchObjectMeta, CodecError> {
1332        read_object_frame(reader, buf).map(|frame| frame.meta)
1333    }
1334}
1335
1336/// A drafts-16-to-19 fetch frame's identity once the fields its Serialization
1337/// Flags left off the wire have been filled in.
1338///
1339/// The four drafts read those flags differently enough to need a resolver
1340/// each, but they all end up saying the same five things, and turning that into
1341/// a draft-neutral value is the same work every time. `subgroup_id` is `None`
1342/// for the frames that have none at all rather than zero, which is a real
1343/// Subgroup ID; see [`AnyFetchObject::has_subgroup_id`].
1344#[cfg(any(feature = "draft16", feature = "draft17", feature = "draft18", feature = "draft19"))]
1345struct Resolved {
1346    group_id: u64,
1347    subgroup_id: Option<u64>,
1348    object_id: u64,
1349    publisher_priority: u8,
1350    end_of_range: Option<AnyFetchEndOfRange>,
1351}
1352
1353#[cfg(any(feature = "draft16", feature = "draft17", feature = "draft18", feature = "draft19"))]
1354impl Resolved {
1355    fn into_object(self, extension_headers: Vec<u8>, payload: Vec<u8>) -> AnyFetchObject {
1356        AnyFetchObject {
1357            group_id: self.group_id,
1358            subgroup_id: self.subgroup_id.unwrap_or(0),
1359            has_subgroup_id: self.subgroup_id.is_some(),
1360            object_id: self.object_id,
1361            publisher_priority: self.publisher_priority,
1362            extension_headers,
1363            extension_count: None,
1364            // Drafts 16-19 removed the Object Status field from fetch objects;
1365            // a zero-length payload here carries no code to report.
1366            status: None,
1367            end_of_range: self.end_of_range,
1368            payload,
1369        }
1370    }
1371
1372    fn into_meta(
1373        self,
1374        extension_headers_len: u64,
1375        payload_length: u64,
1376        wire_len: u64,
1377    ) -> AnyFetchObjectMeta {
1378        AnyFetchObjectMeta {
1379            group_id: self.group_id,
1380            subgroup_id: self.subgroup_id.unwrap_or(0),
1381            has_subgroup_id: self.subgroup_id.is_some(),
1382            object_id: self.object_id,
1383            publisher_priority: self.publisher_priority,
1384            payload_length,
1385            status: None,
1386            end_of_range: self.end_of_range,
1387            extension_headers_len,
1388            wire_len,
1389        }
1390    }
1391}
1392
1393// ── Subgroup object reader ──────────────────────────────────
1394
1395/// Per-draft reader state. Drafts 07-10 need none, drafts 11-13 need the
1396/// stream type's extensions-present flag, drafts 14-19 own a stateful
1397/// per-draft reader that tracks the Object ID delta.
1398#[derive(Debug, Clone)]
1399enum SubgroupReaderState {
1400    #[cfg(feature = "draft07")]
1401    Draft07,
1402    #[cfg(feature = "draft08")]
1403    Draft08,
1404    #[cfg(feature = "draft09")]
1405    Draft09,
1406    #[cfg(feature = "draft10")]
1407    Draft10,
1408    #[cfg(feature = "draft11")]
1409    Draft11 { extensions: bool },
1410    #[cfg(feature = "draft12")]
1411    Draft12 { extensions: bool },
1412    #[cfg(feature = "draft13")]
1413    Draft13 { extensions: bool },
1414    #[cfg(feature = "draft14")]
1415    Draft14(crate::draft14::data_stream::SubgroupObjectReader),
1416    #[cfg(feature = "draft15")]
1417    Draft15(crate::draft15::data_stream::SubgroupObjectReader),
1418    #[cfg(feature = "draft16")]
1419    Draft16(crate::draft16::data_stream::SubgroupObjectReader),
1420    #[cfg(feature = "draft17")]
1421    Draft17(crate::draft17::data_stream::SubgroupObjectReader),
1422    #[cfg(feature = "draft18")]
1423    Draft18(crate::draft18::data_stream::SubgroupObjectReader),
1424    #[cfg(feature = "draft19")]
1425    Draft19(crate::draft19::data_stream::SubgroupObjectReader),
1426}
1427
1428/// Stateful reader for the objects on a subgroup data stream, for any
1429/// enabled draft.
1430///
1431/// Drafts 07-13 encode absolute object IDs and need no state, drafts 14-19
1432/// delta-encode them against the previous object. This reader presents both
1433/// as the same API: construct it from the stream's header, then call
1434/// [`read_object`](Self::read_object) once per object.
1435///
1436/// The reader is [`Clone`] specifically so callers can probe a partial buffer
1437/// against a copy and commit only on success; see the module docs.
1438#[derive(Debug, Clone)]
1439pub struct AnySubgroupObjectReader {
1440    state: SubgroupReaderState,
1441}
1442
1443impl AnySubgroupObjectReader {
1444    /// Create a reader seeded from the stream's subgroup header.
1445    ///
1446    /// Returns [`CodecError::UnsupportedDraft`] when the header's draft is
1447    /// not compiled in, and [`CodecError::InvalidField`] when the header's
1448    /// stream type is not a subgroup type.
1449    #[allow(unused_variables, unreachable_code)]
1450    pub fn new(header: &AnySubgroupHeader) -> Result<Self, CodecError> {
1451        let state = match header {
1452            #[cfg(feature = "draft07")]
1453            AnySubgroupHeader::Draft07(_) => SubgroupReaderState::Draft07,
1454            #[cfg(feature = "draft08")]
1455            AnySubgroupHeader::Draft08(_) => SubgroupReaderState::Draft08,
1456            #[cfg(feature = "draft09")]
1457            AnySubgroupHeader::Draft09(_) => SubgroupReaderState::Draft09,
1458            #[cfg(feature = "draft10")]
1459            AnySubgroupHeader::Draft10(_) => SubgroupReaderState::Draft10,
1460            #[cfg(feature = "draft11")]
1461            AnySubgroupHeader::Draft11(h) => {
1462                SubgroupReaderState::Draft11 { extensions: subgroup_extensions_11(h)? }
1463            }
1464            #[cfg(feature = "draft12")]
1465            AnySubgroupHeader::Draft12(h) => {
1466                SubgroupReaderState::Draft12 { extensions: subgroup_extensions_12(h)? }
1467            }
1468            #[cfg(feature = "draft13")]
1469            AnySubgroupHeader::Draft13(h) => {
1470                SubgroupReaderState::Draft13 { extensions: subgroup_extensions_13(h)? }
1471            }
1472            #[cfg(feature = "draft14")]
1473            AnySubgroupHeader::Draft14(h) => SubgroupReaderState::Draft14(
1474                crate::draft14::data_stream::SubgroupObjectReader::new(h),
1475            ),
1476            #[cfg(feature = "draft15")]
1477            AnySubgroupHeader::Draft15(h) => SubgroupReaderState::Draft15(
1478                crate::draft15::data_stream::SubgroupObjectReader::new(h),
1479            ),
1480            #[cfg(feature = "draft16")]
1481            AnySubgroupHeader::Draft16(h) => SubgroupReaderState::Draft16(
1482                crate::draft16::data_stream::SubgroupObjectReader::new(h),
1483            ),
1484            #[cfg(feature = "draft17")]
1485            AnySubgroupHeader::Draft17(h) => SubgroupReaderState::Draft17(
1486                crate::draft17::data_stream::SubgroupObjectReader::new(h),
1487            ),
1488            #[cfg(feature = "draft18")]
1489            AnySubgroupHeader::Draft18(h) => SubgroupReaderState::Draft18(
1490                crate::draft18::data_stream::SubgroupObjectReader::new(h),
1491            ),
1492            #[cfg(feature = "draft19")]
1493            AnySubgroupHeader::Draft19(h) => SubgroupReaderState::Draft19(
1494                crate::draft19::data_stream::SubgroupObjectReader::new(h),
1495            ),
1496            #[allow(unreachable_patterns)]
1497            _ => {
1498                return Err(CodecError::UnsupportedDraft(format!(
1499                    "draft {:?} not enabled via feature flag",
1500                    header.draft()
1501                )));
1502            }
1503        };
1504        Ok(Self { state })
1505    }
1506
1507    /// The draft this reader decodes.
1508    #[allow(unreachable_code)]
1509    pub fn draft(&self) -> DraftVersion {
1510        match &self.state {
1511            #[cfg(feature = "draft07")]
1512            SubgroupReaderState::Draft07 => DraftVersion::Draft07,
1513            #[cfg(feature = "draft08")]
1514            SubgroupReaderState::Draft08 => DraftVersion::Draft08,
1515            #[cfg(feature = "draft09")]
1516            SubgroupReaderState::Draft09 => DraftVersion::Draft09,
1517            #[cfg(feature = "draft10")]
1518            SubgroupReaderState::Draft10 => DraftVersion::Draft10,
1519            #[cfg(feature = "draft11")]
1520            SubgroupReaderState::Draft11 { .. } => DraftVersion::Draft11,
1521            #[cfg(feature = "draft12")]
1522            SubgroupReaderState::Draft12 { .. } => DraftVersion::Draft12,
1523            #[cfg(feature = "draft13")]
1524            SubgroupReaderState::Draft13 { .. } => DraftVersion::Draft13,
1525            #[cfg(feature = "draft14")]
1526            SubgroupReaderState::Draft14(_) => DraftVersion::Draft14,
1527            #[cfg(feature = "draft15")]
1528            SubgroupReaderState::Draft15(_) => DraftVersion::Draft15,
1529            #[cfg(feature = "draft16")]
1530            SubgroupReaderState::Draft16(_) => DraftVersion::Draft16,
1531            #[cfg(feature = "draft17")]
1532            SubgroupReaderState::Draft17(_) => DraftVersion::Draft17,
1533            #[cfg(feature = "draft18")]
1534            SubgroupReaderState::Draft18(_) => DraftVersion::Draft18,
1535            #[cfg(feature = "draft19")]
1536            SubgroupReaderState::Draft19(_) => DraftVersion::Draft19,
1537            #[allow(unreachable_patterns)]
1538            _ => unreachable!("AnySubgroupObjectReader has no enabled variants"),
1539        }
1540    }
1541
1542    /// Decode the next object, including its payload.
1543    ///
1544    /// Returns [`CodecError::UnexpectedEnd`] when `buf` holds only part of an
1545    /// object; the reader's state is unspecified after such an error, so
1546    /// callers that may be fed partial buffers must probe against a clone.
1547    #[allow(unused_variables, unreachable_code)]
1548    pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<AnySubgroupObject, CodecError> {
1549        match &mut self.state {
1550            #[cfg(feature = "draft07")]
1551            SubgroupReaderState::Draft07 => sg07::read_object(buf),
1552            #[cfg(feature = "draft08")]
1553            SubgroupReaderState::Draft08 => sg08::read_object(buf),
1554            #[cfg(feature = "draft09")]
1555            SubgroupReaderState::Draft09 => sg09::read_object(buf),
1556            #[cfg(feature = "draft10")]
1557            SubgroupReaderState::Draft10 => sg10::read_object(buf),
1558            #[cfg(feature = "draft11")]
1559            SubgroupReaderState::Draft11 { extensions } => sg11::read_object(*extensions, buf),
1560            #[cfg(feature = "draft12")]
1561            SubgroupReaderState::Draft12 { extensions } => sg12::read_object(*extensions, buf),
1562            #[cfg(feature = "draft13")]
1563            SubgroupReaderState::Draft13 { extensions } => sg13::read_object(*extensions, buf),
1564            #[cfg(feature = "draft14")]
1565            SubgroupReaderState::Draft14(inner) => sg14::read_object(inner, buf),
1566            #[cfg(feature = "draft15")]
1567            SubgroupReaderState::Draft15(inner) => sg15::read_object(inner, buf),
1568            #[cfg(feature = "draft16")]
1569            SubgroupReaderState::Draft16(inner) => sg16::read_object(inner, buf),
1570            #[cfg(feature = "draft17")]
1571            SubgroupReaderState::Draft17(inner) => sg17::read_object(inner, buf),
1572            #[cfg(feature = "draft18")]
1573            SubgroupReaderState::Draft18(inner) => sg18::read_object(inner, buf),
1574            #[cfg(feature = "draft19")]
1575            SubgroupReaderState::Draft19(inner) => sg19::read_object(inner, buf),
1576            #[allow(unreachable_patterns)]
1577            _ => unreachable!("AnySubgroupObjectReader has no enabled variants"),
1578        }
1579    }
1580
1581    /// Decode the next object's framing without copying its payload.
1582    ///
1583    /// Advances `buf` past the whole object exactly as
1584    /// [`read_object`](Self::read_object) does, but returns only scalars.
1585    /// This is the path a relay uses when it forwards the object's bytes
1586    /// verbatim and never inspects the payload.
1587    #[allow(unused_variables, unreachable_code)]
1588    pub fn read_object_meta(
1589        &mut self,
1590        buf: &mut impl Buf,
1591    ) -> Result<AnySubgroupObjectMeta, CodecError> {
1592        match &mut self.state {
1593            #[cfg(feature = "draft07")]
1594            SubgroupReaderState::Draft07 => sg07::read_object_meta(buf),
1595            #[cfg(feature = "draft08")]
1596            SubgroupReaderState::Draft08 => sg08::read_object_meta(buf),
1597            #[cfg(feature = "draft09")]
1598            SubgroupReaderState::Draft09 => sg09::read_object_meta(buf),
1599            #[cfg(feature = "draft10")]
1600            SubgroupReaderState::Draft10 => sg10::read_object_meta(buf),
1601            #[cfg(feature = "draft11")]
1602            SubgroupReaderState::Draft11 { extensions } => sg11::read_object_meta(*extensions, buf),
1603            #[cfg(feature = "draft12")]
1604            SubgroupReaderState::Draft12 { extensions } => sg12::read_object_meta(*extensions, buf),
1605            #[cfg(feature = "draft13")]
1606            SubgroupReaderState::Draft13 { extensions } => sg13::read_object_meta(*extensions, buf),
1607            #[cfg(feature = "draft14")]
1608            SubgroupReaderState::Draft14(inner) => sg14::read_object_meta(inner, buf),
1609            #[cfg(feature = "draft15")]
1610            SubgroupReaderState::Draft15(inner) => sg15::read_object_meta(inner, buf),
1611            #[cfg(feature = "draft16")]
1612            SubgroupReaderState::Draft16(inner) => sg16::read_object_meta(inner, buf),
1613            #[cfg(feature = "draft17")]
1614            SubgroupReaderState::Draft17(inner) => sg17::read_object_meta(inner, buf),
1615            #[cfg(feature = "draft18")]
1616            SubgroupReaderState::Draft18(inner) => sg18::read_object_meta(inner, buf),
1617            #[cfg(feature = "draft19")]
1618            SubgroupReaderState::Draft19(inner) => sg19::read_object_meta(inner, buf),
1619            #[allow(unreachable_patterns)]
1620            _ => unreachable!("AnySubgroupObjectReader has no enabled variants"),
1621        }
1622    }
1623}
1624
1625// ── Subgroup object writer ──────────────────────────────────
1626
1627/// Per-draft writer state. Mirrors [`SubgroupReaderState`]; drafts 14-19
1628/// reuse each draft's `SubgroupObjectReader`, which owns both directions of
1629/// the delta state. Drafts 07-13 encode absolute IDs, so nothing on the wire
1630/// forces them to increase and they carry a `prev_object_id` of their own —
1631/// see `advance_absolute_id`.
1632#[derive(Debug, Clone)]
1633enum SubgroupWriterState {
1634    #[cfg(feature = "draft07")]
1635    Draft07 { prev_object_id: Option<u64> },
1636    #[cfg(feature = "draft08")]
1637    Draft08 { prev_object_id: Option<u64> },
1638    #[cfg(feature = "draft09")]
1639    Draft09 { prev_object_id: Option<u64> },
1640    #[cfg(feature = "draft10")]
1641    Draft10 { prev_object_id: Option<u64> },
1642    #[cfg(feature = "draft11")]
1643    Draft11 { extensions: bool, prev_object_id: Option<u64> },
1644    #[cfg(feature = "draft12")]
1645    Draft12 { extensions: bool, prev_object_id: Option<u64> },
1646    #[cfg(feature = "draft13")]
1647    Draft13 { extensions: bool, prev_object_id: Option<u64> },
1648    #[cfg(feature = "draft14")]
1649    Draft14 { inner: crate::draft14::data_stream::SubgroupObjectReader, extensions: bool },
1650    #[cfg(feature = "draft15")]
1651    Draft15 { inner: crate::draft15::data_stream::SubgroupObjectReader, extensions: bool },
1652    #[cfg(feature = "draft16")]
1653    Draft16 { inner: crate::draft16::data_stream::SubgroupObjectReader, extensions: bool },
1654    #[cfg(feature = "draft17")]
1655    Draft17 { inner: crate::draft17::data_stream::SubgroupObjectReader, extensions: bool },
1656    #[cfg(feature = "draft18")]
1657    Draft18 { inner: crate::draft18::data_stream::SubgroupObjectReader, extensions: bool },
1658    #[cfg(feature = "draft19")]
1659    Draft19 { inner: crate::draft19::data_stream::SubgroupObjectReader, extensions: bool },
1660}
1661
1662/// Serializer for the objects on a subgroup data stream, for any enabled
1663/// draft.
1664///
1665/// Mirrors [`AnySubgroupObjectReader`]. On drafts 14-19 it tracks the
1666/// previous Object ID so successive writes produce correct deltas; on drafts
1667/// 07-13 object IDs are absolute and the same state only enforces that they
1668/// increase.
1669///
1670/// # Eliding objects
1671///
1672/// To remove an object from a stream, read it and then simply do not write
1673/// it. The writer's delta state advances only when
1674/// [`write_object`](Self::write_object) succeeds, so the next object written
1675/// re-derives its delta against the last *retained* object automatically.
1676/// See [`Self::write_object`] for the exact invariant.
1677#[derive(Debug, Clone)]
1678pub struct AnySubgroupObjectWriter {
1679    state: SubgroupWriterState,
1680}
1681
1682impl AnySubgroupObjectWriter {
1683    /// Create a writer for a stream with the given header.
1684    ///
1685    /// The header fixes the extension-presence and (on drafts 11-13) stream
1686    /// type used for every object written, exactly as it does for
1687    /// [`AnySubgroupObjectReader::new`].
1688    #[allow(unused_variables, unreachable_code)]
1689    pub fn new(header: &AnySubgroupHeader) -> Result<Self, CodecError> {
1690        let state = match header {
1691            #[cfg(feature = "draft07")]
1692            AnySubgroupHeader::Draft07(_) => SubgroupWriterState::Draft07 { prev_object_id: None },
1693            #[cfg(feature = "draft08")]
1694            AnySubgroupHeader::Draft08(_) => SubgroupWriterState::Draft08 { prev_object_id: None },
1695            #[cfg(feature = "draft09")]
1696            AnySubgroupHeader::Draft09(_) => SubgroupWriterState::Draft09 { prev_object_id: None },
1697            #[cfg(feature = "draft10")]
1698            AnySubgroupHeader::Draft10(_) => SubgroupWriterState::Draft10 { prev_object_id: None },
1699            #[cfg(feature = "draft11")]
1700            AnySubgroupHeader::Draft11(h) => SubgroupWriterState::Draft11 {
1701                extensions: subgroup_extensions_11(h)?,
1702                prev_object_id: None,
1703            },
1704            #[cfg(feature = "draft12")]
1705            AnySubgroupHeader::Draft12(h) => SubgroupWriterState::Draft12 {
1706                extensions: subgroup_extensions_12(h)?,
1707                prev_object_id: None,
1708            },
1709            #[cfg(feature = "draft13")]
1710            AnySubgroupHeader::Draft13(h) => SubgroupWriterState::Draft13 {
1711                extensions: subgroup_extensions_13(h)?,
1712                prev_object_id: None,
1713            },
1714            #[cfg(feature = "draft14")]
1715            AnySubgroupHeader::Draft14(h) => SubgroupWriterState::Draft14 {
1716                inner: crate::draft14::data_stream::SubgroupObjectReader::new(h),
1717                extensions: h.stream_type.extensions_present(),
1718            },
1719            #[cfg(feature = "draft15")]
1720            AnySubgroupHeader::Draft15(h) => SubgroupWriterState::Draft15 {
1721                inner: crate::draft15::data_stream::SubgroupObjectReader::new(h),
1722                extensions: h.has_extensions(),
1723            },
1724            #[cfg(feature = "draft16")]
1725            AnySubgroupHeader::Draft16(h) => SubgroupWriterState::Draft16 {
1726                inner: crate::draft16::data_stream::SubgroupObjectReader::new(h),
1727                extensions: h.has_extensions(),
1728            },
1729            #[cfg(feature = "draft17")]
1730            AnySubgroupHeader::Draft17(h) => SubgroupWriterState::Draft17 {
1731                inner: crate::draft17::data_stream::SubgroupObjectReader::new(h),
1732                extensions: h.has_properties(),
1733            },
1734            #[cfg(feature = "draft18")]
1735            AnySubgroupHeader::Draft18(h) => SubgroupWriterState::Draft18 {
1736                inner: crate::draft18::data_stream::SubgroupObjectReader::new(h),
1737                extensions: h.has_properties(),
1738            },
1739            #[cfg(feature = "draft19")]
1740            AnySubgroupHeader::Draft19(h) => SubgroupWriterState::Draft19 {
1741                inner: crate::draft19::data_stream::SubgroupObjectReader::new(h),
1742                extensions: h.has_properties(),
1743            },
1744            #[allow(unreachable_patterns)]
1745            _ => {
1746                return Err(CodecError::UnsupportedDraft(format!(
1747                    "draft {:?} not enabled via feature flag",
1748                    header.draft()
1749                )));
1750            }
1751        };
1752        Ok(Self { state })
1753    }
1754
1755    /// The draft this writer encodes.
1756    #[allow(unreachable_code)]
1757    pub fn draft(&self) -> DraftVersion {
1758        match &self.state {
1759            #[cfg(feature = "draft07")]
1760            SubgroupWriterState::Draft07 { .. } => DraftVersion::Draft07,
1761            #[cfg(feature = "draft08")]
1762            SubgroupWriterState::Draft08 { .. } => DraftVersion::Draft08,
1763            #[cfg(feature = "draft09")]
1764            SubgroupWriterState::Draft09 { .. } => DraftVersion::Draft09,
1765            #[cfg(feature = "draft10")]
1766            SubgroupWriterState::Draft10 { .. } => DraftVersion::Draft10,
1767            #[cfg(feature = "draft11")]
1768            SubgroupWriterState::Draft11 { .. } => DraftVersion::Draft11,
1769            #[cfg(feature = "draft12")]
1770            SubgroupWriterState::Draft12 { .. } => DraftVersion::Draft12,
1771            #[cfg(feature = "draft13")]
1772            SubgroupWriterState::Draft13 { .. } => DraftVersion::Draft13,
1773            #[cfg(feature = "draft14")]
1774            SubgroupWriterState::Draft14 { .. } => DraftVersion::Draft14,
1775            #[cfg(feature = "draft15")]
1776            SubgroupWriterState::Draft15 { .. } => DraftVersion::Draft15,
1777            #[cfg(feature = "draft16")]
1778            SubgroupWriterState::Draft16 { .. } => DraftVersion::Draft16,
1779            #[cfg(feature = "draft17")]
1780            SubgroupWriterState::Draft17 { .. } => DraftVersion::Draft17,
1781            #[cfg(feature = "draft18")]
1782            SubgroupWriterState::Draft18 { .. } => DraftVersion::Draft18,
1783            #[cfg(feature = "draft19")]
1784            SubgroupWriterState::Draft19 { .. } => DraftVersion::Draft19,
1785            #[allow(unreachable_patterns)]
1786            _ => unreachable!("AnySubgroupObjectWriter has no enabled variants"),
1787        }
1788    }
1789
1790    /// Encode one object, advancing the delta state.
1791    ///
1792    /// # Invariant
1793    ///
1794    /// Let a stream's objects decode to absolute IDs `a_0, a_1, .., a_n`.
1795    /// Feeding any strictly-increasing subsequence of those objects through
1796    /// one writer, in order, produces a byte stream that decodes back to
1797    /// exactly that subsequence of absolute IDs, on every draft 07-19.
1798    ///
1799    /// Concretely: dropping `a_2` from `0,1,2,3,4` yields a stream decoding
1800    /// to `0,1,3,4` — not `0,1,2,3`.
1801    ///
1802    /// # Errors
1803    ///
1804    /// [`CodecError::InvalidField`] when `object.object_id` is not strictly
1805    /// greater than the previously written object's ID (two objects on a
1806    /// subgroup stream can never share an ID, so no valid delta exists), when
1807    /// a computed delta or length exceeds the varint range, when the object
1808    /// carries extension bytes that a stream without an extension block
1809    /// cannot represent, or when a non-empty payload is paired with a status.
1810    ///
1811    /// Also [`CodecError::InvalidField`] when `object.status` holds a code the
1812    /// draft being written does not assign. [`AnySubgroupObject::status`] is a
1813    /// raw wire code because it crosses drafts, and the assigned set moves
1814    /// between them, so a status read off one draft's stream is not
1815    /// necessarily writable onto another's: forwarding a draft-15 Object Does
1816    /// Not Exist (0x1) onto a draft-16 or later stream is refused here rather
1817    /// than emitted as a byte the peer must close the session over.
1818    #[allow(unused_variables, unreachable_code)]
1819    pub fn write_object(
1820        &mut self,
1821        object: &AnySubgroupObject,
1822        buf: &mut impl BufMut,
1823    ) -> Result<(), CodecError> {
1824        match &mut self.state {
1825            #[cfg(feature = "draft07")]
1826            SubgroupWriterState::Draft07 { prev_object_id } => {
1827                advance_absolute_id(prev_object_id, object, |o| sg07::write_object(o, buf))
1828            }
1829            #[cfg(feature = "draft08")]
1830            SubgroupWriterState::Draft08 { prev_object_id } => {
1831                advance_absolute_id(prev_object_id, object, |o| sg08::write_object(o, buf))
1832            }
1833            #[cfg(feature = "draft09")]
1834            SubgroupWriterState::Draft09 { prev_object_id } => {
1835                advance_absolute_id(prev_object_id, object, |o| sg09::write_object(o, buf))
1836            }
1837            #[cfg(feature = "draft10")]
1838            SubgroupWriterState::Draft10 { prev_object_id } => {
1839                advance_absolute_id(prev_object_id, object, |o| sg10::write_object(o, buf))
1840            }
1841            #[cfg(feature = "draft11")]
1842            SubgroupWriterState::Draft11 { extensions, prev_object_id } => {
1843                let extensions = *extensions;
1844                advance_absolute_id(prev_object_id, object, |o| {
1845                    sg11::write_object(extensions, o, buf)
1846                })
1847            }
1848            #[cfg(feature = "draft12")]
1849            SubgroupWriterState::Draft12 { extensions, prev_object_id } => {
1850                let extensions = *extensions;
1851                advance_absolute_id(prev_object_id, object, |o| {
1852                    sg12::write_object(extensions, o, buf)
1853                })
1854            }
1855            #[cfg(feature = "draft13")]
1856            SubgroupWriterState::Draft13 { extensions, prev_object_id } => {
1857                let extensions = *extensions;
1858                advance_absolute_id(prev_object_id, object, |o| {
1859                    sg13::write_object(extensions, o, buf)
1860                })
1861            }
1862            #[cfg(feature = "draft14")]
1863            SubgroupWriterState::Draft14 { inner, extensions } => {
1864                reject_unrepresentable_extensions(*extensions, object)?;
1865                sg14::write_object(inner, object, buf)
1866            }
1867            #[cfg(feature = "draft15")]
1868            SubgroupWriterState::Draft15 { inner, extensions } => {
1869                reject_unrepresentable_extensions(*extensions, object)?;
1870                sg15::write_object(inner, object, buf)
1871            }
1872            #[cfg(feature = "draft16")]
1873            SubgroupWriterState::Draft16 { inner, extensions } => {
1874                reject_unrepresentable_extensions(*extensions, object)?;
1875                sg16::write_object(inner, object, buf)
1876            }
1877            #[cfg(feature = "draft17")]
1878            SubgroupWriterState::Draft17 { inner, extensions } => {
1879                reject_unrepresentable_extensions(*extensions, object)?;
1880                sg17::write_object(inner, object, buf)
1881            }
1882            #[cfg(feature = "draft18")]
1883            SubgroupWriterState::Draft18 { inner, extensions } => {
1884                reject_unrepresentable_extensions(*extensions, object)?;
1885                sg18::write_object(inner, object, buf)
1886            }
1887            #[cfg(feature = "draft19")]
1888            SubgroupWriterState::Draft19 { inner, extensions } => {
1889                reject_unrepresentable_extensions(*extensions, object)?;
1890                sg19::write_object(inner, object, buf)
1891            }
1892            #[allow(unreachable_patterns)]
1893            _ => unreachable!("AnySubgroupObjectWriter has no enabled variants"),
1894        }
1895    }
1896}
1897
1898// ── Re-emitting an object whose bytes are already known ─────
1899
1900/// What [`reemit_subgroup_object`] had to do.
1901#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1902pub enum Reemit {
1903    /// The bytes were copied unchanged.
1904    Verbatim,
1905    /// Only the leading Object ID field changed.
1906    Reencoded {
1907        /// Bytes the ID field occupied in `raw`.
1908        id_bytes_before: usize,
1909        /// Bytes it occupies in the output.
1910        id_bytes_after: usize,
1911    },
1912}
1913
1914/// Re-emit a subgroup object whose wire bytes are already known, adjusting
1915/// only this draft's encoding of its identity.
1916///
1917/// This is the whole of what removing an object from a subgroup stream
1918/// costs. Drafts 07-13 encode absolute Object IDs, so every survivor's
1919/// bytes are already correct and this copies `raw` unchanged after checking
1920/// that IDs still increase. Drafts 14-19 encode `id - prev - 1`, so the
1921/// leading varint is recomputed against `prev_forwarded`; when its minimal
1922/// encoding is byte-identical to the one in `raw` the bytes are still
1923/// copied unchanged. Everything after the ID field — extension block,
1924/// length, status, payload — is always copied verbatim.
1925///
1926/// After one object is re-emitted following an elided run, the writer's
1927/// cursor re-converges with the reader's, so every later object's original
1928/// bytes remain correct. An elide therefore costs at most one fix-up, not a
1929/// re-encode of the stream's tail.
1930///
1931/// `prev_forwarded` is the absolute Object ID of the last object actually
1932/// forwarded on this stream, or `None` when none has been.
1933///
1934/// # `raw` need not be a complete object
1935///
1936/// **Any prefix is legal provided the whole leading Object ID field is
1937/// present.** Everything after that field is copied byte-for-byte, however
1938/// many bytes there are, and **no length validation is performed** — this
1939/// function never reads the extension block, never reads the payload
1940/// length field and never compares it to `raw.len()`. It cannot: on drafts
1941/// 07-13 it does not decode past the ID at all, and on 14-19 it decodes
1942/// exactly one varint.
1943///
1944/// This is what lets a caller fix up the **first chunk of an oversized
1945/// object** — an object too large to buffer is forwarded in chunks, and only
1946/// the first one carries the ID field. A caller that cannot guarantee the ID
1947/// field is whole in the chunk it passes gets
1948/// [`CodecError::InvalidField`] rather than a silent truncation.
1949///
1950/// **Do not add a completeness check.** A `raw.len() >= wire_len` assertion
1951/// would look defensive, would pass every test that feeds it whole objects,
1952/// and would refuse the prefix this function exists to accept.
1953///
1954/// # Errors
1955///
1956/// [`CodecError::InvalidField`] when `object_id` is not strictly greater
1957/// than `prev_forwarded`, when the recomputed delta exceeds the varint
1958/// range, or when `raw` does not begin with a decodable varint — which
1959/// includes a `raw` too short to hold the whole leading varint.
1960///
1961/// # Examples
1962///
1963/// ```
1964/// use moqtap_codec::dispatch::{reemit_subgroup_object, Reemit};
1965/// use moqtap_codec::version::DraftVersion;
1966///
1967/// // A draft-19 object that was encoded as the successor of ID 4 —
1968/// // leading delta 0 — re-emitted after ID 3 was the last one forwarded.
1969/// let raw = [0x00, 0x02, 0xca, 0xfe];
1970/// let mut out = Vec::new();
1971/// let what = reemit_subgroup_object(DraftVersion::Draft19, Some(3), 5, &raw, &mut out).unwrap();
1972/// assert_eq!(what, Reemit::Reencoded { id_bytes_before: 1, id_bytes_after: 1 });
1973/// assert_eq!(out, [0x01, 0x02, 0xca, 0xfe]);
1974/// ```
1975pub fn reemit_subgroup_object(
1976    draft: DraftVersion,
1977    prev_forwarded: Option<u64>,
1978    object_id: u64,
1979    raw: &[u8],
1980    out: &mut impl BufMut,
1981) -> Result<Reemit, CodecError> {
1982    if matches!(prev_forwarded, Some(prev) if object_id <= prev) {
1983        return Err(CodecError::InvalidField);
1984    }
1985
1986    // Measure the ID field. Every draft 07-19 puts it first and nothing past
1987    // it is decoded, so `raw` may stop anywhere after it. Which varint measures
1988    // it depends on the draft: 17 replaced the RFC 9000 encoding with MoQT's.
1989    let mut cursor: &[u8] = raw;
1990    draft.decode_varint(&mut cursor).map_err(|_| CodecError::InvalidField)?;
1991    let id_bytes_before = raw.len() - cursor.len();
1992
1993    // Drafts 07-13 write the ID absolutely: nothing about that field depends
1994    // on which objects were forwarded, so the bytes already say the truth.
1995    if !delta_encodes_object_ids(draft) {
1996        out.put_slice(raw);
1997        return Ok(Reemit::Verbatim);
1998    }
1999
2000    let delta = match prev_forwarded {
2001        None => object_id,
2002        Some(prev) => object_id
2003            .checked_sub(prev)
2004            .and_then(|v| v.checked_sub(1))
2005            .ok_or(CodecError::InvalidField)?,
2006    };
2007
2008    // The MoQT encoding reaches the full 64-bit range, so a delta a draft-17+
2009    // peer can legitimately send is not an error there.
2010    let field = if draft.uses_moqt_varint() {
2011        VarInt::from_u64_moqt(delta)
2012    } else {
2013        VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?
2014    };
2015
2016    // Nine bytes: the MoQT encoding is one longer than RFC 9000 at the top.
2017    let mut encoded = [0u8; 9];
2018    let mut slot: &mut [u8] = &mut encoded;
2019    draft.encode_varint(field, &mut slot);
2020    let id_bytes_after = 9 - slot.len();
2021    let encoded = &encoded[..id_bytes_after];
2022
2023    if encoded == &raw[..id_bytes_before] {
2024        out.put_slice(raw);
2025        return Ok(Reemit::Verbatim);
2026    }
2027
2028    out.put_slice(encoded);
2029    out.put_slice(&raw[id_bytes_before..]);
2030    Ok(Reemit::Reencoded { id_bytes_before, id_bytes_after })
2031}
2032
2033/// `true` on the drafts whose subgroup objects encode the Object ID as
2034/// `id - prev - 1` rather than absolutely.
2035///
2036/// Needs no `#[cfg]`: [`DraftVersion`] is not feature-gated, so this answers
2037/// for a draft whose codec is not compiled in.
2038fn delta_encodes_object_ids(draft: DraftVersion) -> bool {
2039    matches!(
2040        draft,
2041        DraftVersion::Draft14
2042            | DraftVersion::Draft15
2043            | DraftVersion::Draft16
2044            | DraftVersion::Draft17
2045            | DraftVersion::Draft18
2046            | DraftVersion::Draft19
2047    )
2048}
2049
2050/// Enforce the strictly-increasing Object ID rule on the drafts that encode
2051/// IDs absolutely.
2052///
2053/// Drafts 14-19 get this for free: their delta is `id - prev - 1`, so a
2054/// repeated or decreasing ID underflows and the per-draft writer rejects it.
2055/// Drafts 07-13 write the ID verbatim and would happily emit a stream no
2056/// publisher can produce, so the check lives here. As on the delta drafts, the
2057/// state advances only once the object is actually written, which is what makes
2058/// elision *read it and do not write it*.
2059#[cfg(any(
2060    feature = "draft07",
2061    feature = "draft08",
2062    feature = "draft09",
2063    feature = "draft10",
2064    feature = "draft11",
2065    feature = "draft12",
2066    feature = "draft13"
2067))]
2068fn advance_absolute_id(
2069    prev_object_id: &mut Option<u64>,
2070    object: &AnySubgroupObject,
2071    write: impl FnOnce(&AnySubgroupObject) -> Result<(), CodecError>,
2072) -> Result<(), CodecError> {
2073    if matches!(*prev_object_id, Some(prev) if object.object_id <= prev) {
2074        return Err(CodecError::InvalidField);
2075    }
2076    write(object)?;
2077    *prev_object_id = Some(object.object_id);
2078    Ok(())
2079}
2080
2081/// A stream whose header says objects carry no extension block cannot encode
2082/// one, so refuse rather than drop the bytes.
2083#[cfg(any(
2084    feature = "draft14",
2085    feature = "draft15",
2086    feature = "draft16",
2087    feature = "draft17",
2088    feature = "draft18",
2089    feature = "draft19"
2090))]
2091fn reject_unrepresentable_extensions(
2092    extensions: bool,
2093    object: &AnySubgroupObject,
2094) -> Result<(), CodecError> {
2095    if !extensions && !object.extension_headers.is_empty() {
2096        return Err(CodecError::InvalidField);
2097    }
2098    Ok(())
2099}
2100
2101// ── Fetch object reader ─────────────────────────────────────
2102
2103/// Per-draft fetch reader state. Fetch objects are self-describing on drafts
2104/// 07-14, so those variants carry none; drafts 15-19 let an object take fields
2105/// from the one before it, so each owns the running state that resolves them.
2106#[derive(Debug, Clone)]
2107enum FetchReaderState {
2108    #[cfg(feature = "draft07")]
2109    Draft07,
2110    #[cfg(feature = "draft08")]
2111    Draft08,
2112    #[cfg(feature = "draft09")]
2113    Draft09,
2114    #[cfg(feature = "draft10")]
2115    Draft10,
2116    #[cfg(feature = "draft11")]
2117    Draft11,
2118    #[cfg(feature = "draft12")]
2119    Draft12,
2120    #[cfg(feature = "draft13")]
2121    Draft13,
2122    #[cfg(feature = "draft14")]
2123    Draft14,
2124    #[cfg(feature = "draft15")]
2125    Draft15(crate::draft15::data_stream::FetchObjectReader),
2126    #[cfg(feature = "draft16")]
2127    Draft16(crate::draft16::data_stream::FetchObjectReader),
2128    #[cfg(feature = "draft17")]
2129    Draft17(crate::draft17::data_stream::FetchObjectReader),
2130    #[cfg(feature = "draft18")]
2131    Draft18(crate::draft18::data_stream::FetchObjectReader),
2132    #[cfg(feature = "draft19")]
2133    Draft19(crate::draft19::data_stream::FetchObjectReader),
2134}
2135
2136/// Stateful reader for the frames on a fetch data stream, for any enabled
2137/// draft.
2138///
2139/// Fetch objects are self-describing on drafts 07-14 and this reader carries no
2140/// state there. From draft-15 a Serialization Flags field decides which of an
2141/// object's Group ID, Subgroup ID, Object ID and Priority reach the wire at
2142/// all, and every field it omits is the object before it on the stream —
2143/// repeated, or stepped by one, or (from draft-18) counted from by a
2144/// difference. This reader holds that running state, so the values it produces
2145/// are absolute on every draft.
2146///
2147/// One reader belongs to one stream. Every draft counts "the prior Object"
2148/// along a single stream, so sharing a reader between streams, or restarting
2149/// one mid-stream, resolves later frames onto the wrong group, subgroup, ID or
2150/// priority — usually without an error anywhere.
2151///
2152/// The reader is [`Clone`] specifically so callers can probe a partial buffer
2153/// against a copy and commit only on success; see the module docs.
2154///
2155/// # Frames that are not objects
2156///
2157/// Drafts 16-19 add End of Range indicators, which state that a run of Objects
2158/// was not serialized. They arrive through the same calls as objects and are
2159/// told apart by [`AnyFetchObject::end_of_range`].
2160#[derive(Debug, Clone)]
2161pub struct AnyFetchObjectReader {
2162    state: FetchReaderState,
2163}
2164
2165impl AnyFetchObjectReader {
2166    /// Create a reader from the stream's fetch header and the Group Order the
2167    /// fetch was opened with.
2168    ///
2169    /// The order matters only on drafts 18 and 19, where an Object's Group ID
2170    /// is a difference from the previous Object's and the order decides its
2171    /// sign. Nothing on the data stream carries it — the FETCH settles it — and
2172    /// it is an argument rather than a default because a descending stream read
2173    /// as ascending does not fail: it decodes, under Group IDs walking the wrong
2174    /// way, and neither this crate nor the caller can tell afterwards. Both
2175    /// readings are legal streams.
2176    ///
2177    /// [`AnyControlMessage::fetch_group_order`](crate::dispatch::AnyControlMessage::fetch_group_order)
2178    /// answers it from the FETCH, including the case where the message names no
2179    /// GROUP_ORDER — draft-19 Section 10.2.8: "If omitted from FETCH, the
2180    /// receiver uses Ascending (0x1)". On drafts 07-17 the argument is ignored.
2181    ///
2182    /// Returns [`CodecError::UnsupportedDraft`] for drafts not compiled in.
2183    #[allow(unused_variables, unreachable_code)]
2184    pub fn new(
2185        header: &AnyFetchHeader,
2186        group_order: AnyFetchGroupOrder,
2187    ) -> Result<Self, CodecError> {
2188        let state = match header {
2189            #[cfg(feature = "draft07")]
2190            AnyFetchHeader::Draft07(_) => FetchReaderState::Draft07,
2191            #[cfg(feature = "draft08")]
2192            AnyFetchHeader::Draft08(_) => FetchReaderState::Draft08,
2193            #[cfg(feature = "draft09")]
2194            AnyFetchHeader::Draft09(_) => FetchReaderState::Draft09,
2195            #[cfg(feature = "draft10")]
2196            AnyFetchHeader::Draft10(_) => FetchReaderState::Draft10,
2197            #[cfg(feature = "draft11")]
2198            AnyFetchHeader::Draft11(_) => FetchReaderState::Draft11,
2199            #[cfg(feature = "draft12")]
2200            AnyFetchHeader::Draft12(_) => FetchReaderState::Draft12,
2201            #[cfg(feature = "draft13")]
2202            AnyFetchHeader::Draft13(_) => FetchReaderState::Draft13,
2203            #[cfg(feature = "draft14")]
2204            AnyFetchHeader::Draft14(_) => FetchReaderState::Draft14,
2205            // The header carries only a request id on drafts 15-19, so nothing
2206            // about it seeds the reader; the first object does.
2207            #[cfg(feature = "draft15")]
2208            AnyFetchHeader::Draft15(_) => {
2209                FetchReaderState::Draft15(crate::draft15::data_stream::FetchObjectReader::new())
2210            }
2211            #[cfg(feature = "draft16")]
2212            AnyFetchHeader::Draft16(_) => {
2213                FetchReaderState::Draft16(crate::draft16::data_stream::FetchObjectReader::new())
2214            }
2215            #[cfg(feature = "draft17")]
2216            AnyFetchHeader::Draft17(_) => {
2217                FetchReaderState::Draft17(crate::draft17::data_stream::FetchObjectReader::new())
2218            }
2219            #[cfg(feature = "draft18")]
2220            AnyFetchHeader::Draft18(_) => FetchReaderState::Draft18(
2221                crate::draft18::data_stream::FetchObjectReader::new(match group_order {
2222                    AnyFetchGroupOrder::Ascending => {
2223                        crate::draft18::data_stream::GroupOrder::Ascending
2224                    }
2225                    AnyFetchGroupOrder::Descending => {
2226                        crate::draft18::data_stream::GroupOrder::Descending
2227                    }
2228                }),
2229            ),
2230            #[cfg(feature = "draft19")]
2231            AnyFetchHeader::Draft19(_) => FetchReaderState::Draft19(
2232                crate::draft19::data_stream::FetchObjectReader::new(match group_order {
2233                    AnyFetchGroupOrder::Ascending => {
2234                        crate::draft19::data_stream::GroupOrder::Ascending
2235                    }
2236                    AnyFetchGroupOrder::Descending => {
2237                        crate::draft19::data_stream::GroupOrder::Descending
2238                    }
2239                }),
2240            ),
2241            #[allow(unreachable_patterns)]
2242            _ => {
2243                return Err(CodecError::UnsupportedDraft(format!(
2244                    "draft {:?} not enabled via feature flag",
2245                    header.draft()
2246                )));
2247            }
2248        };
2249        Ok(Self { state })
2250    }
2251
2252    /// The draft this reader decodes.
2253    #[allow(unreachable_code)]
2254    pub fn draft(&self) -> DraftVersion {
2255        match &self.state {
2256            #[cfg(feature = "draft07")]
2257            FetchReaderState::Draft07 => DraftVersion::Draft07,
2258            #[cfg(feature = "draft08")]
2259            FetchReaderState::Draft08 => DraftVersion::Draft08,
2260            #[cfg(feature = "draft09")]
2261            FetchReaderState::Draft09 => DraftVersion::Draft09,
2262            #[cfg(feature = "draft10")]
2263            FetchReaderState::Draft10 => DraftVersion::Draft10,
2264            #[cfg(feature = "draft11")]
2265            FetchReaderState::Draft11 => DraftVersion::Draft11,
2266            #[cfg(feature = "draft12")]
2267            FetchReaderState::Draft12 => DraftVersion::Draft12,
2268            #[cfg(feature = "draft13")]
2269            FetchReaderState::Draft13 => DraftVersion::Draft13,
2270            #[cfg(feature = "draft14")]
2271            FetchReaderState::Draft14 => DraftVersion::Draft14,
2272            #[cfg(feature = "draft15")]
2273            FetchReaderState::Draft15(_) => DraftVersion::Draft15,
2274            #[cfg(feature = "draft16")]
2275            FetchReaderState::Draft16(_) => DraftVersion::Draft16,
2276            #[cfg(feature = "draft17")]
2277            FetchReaderState::Draft17(_) => DraftVersion::Draft17,
2278            #[cfg(feature = "draft18")]
2279            FetchReaderState::Draft18(_) => DraftVersion::Draft18,
2280            #[cfg(feature = "draft19")]
2281            FetchReaderState::Draft19(_) => DraftVersion::Draft19,
2282            #[allow(unreachable_patterns)]
2283            _ => unreachable!("AnyFetchObjectReader has no enabled variants"),
2284        }
2285    }
2286
2287    /// Decode the next fetch frame, including its payload.
2288    ///
2289    /// Returns [`CodecError::UnexpectedEnd`] when `buf` holds only part of a
2290    /// frame; the reader's state is unspecified after such an error, so callers
2291    /// that may be fed partial buffers must probe against a clone.
2292    ///
2293    /// Returns [`CodecError::InvalidField`] on drafts 15-19 when a frame takes
2294    /// a field from an object before it that does not exist — the first frame
2295    /// of a stream doing so is a protocol violation on every one of those
2296    /// drafts — and when a resolved Group ID, Subgroup ID or Object ID would
2297    /// leave the 64-bit range.
2298    #[allow(unused_variables, unreachable_code)]
2299    pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<AnyFetchObject, CodecError> {
2300        match &mut self.state {
2301            #[cfg(feature = "draft07")]
2302            FetchReaderState::Draft07 => fo07::read_object(buf),
2303            #[cfg(feature = "draft08")]
2304            FetchReaderState::Draft08 => fo08::read_object(buf),
2305            #[cfg(feature = "draft09")]
2306            FetchReaderState::Draft09 => fo09::read_object(buf),
2307            #[cfg(feature = "draft10")]
2308            FetchReaderState::Draft10 => fo10::read_object(buf),
2309            #[cfg(feature = "draft11")]
2310            FetchReaderState::Draft11 => fo11::read_object(buf),
2311            #[cfg(feature = "draft12")]
2312            FetchReaderState::Draft12 => fo12::read_object(buf),
2313            #[cfg(feature = "draft13")]
2314            FetchReaderState::Draft13 => fo13::read_object(buf),
2315            #[cfg(feature = "draft14")]
2316            FetchReaderState::Draft14 => fo14::read_object(buf),
2317            #[cfg(feature = "draft15")]
2318            FetchReaderState::Draft15(inner) => fo15::read_object(inner, buf),
2319            #[cfg(feature = "draft16")]
2320            FetchReaderState::Draft16(inner) => fo16::read_object(inner, buf),
2321            #[cfg(feature = "draft17")]
2322            FetchReaderState::Draft17(inner) => fo17::read_object(inner, buf),
2323            #[cfg(feature = "draft18")]
2324            FetchReaderState::Draft18(inner) => fo18::read_object(inner, buf),
2325            #[cfg(feature = "draft19")]
2326            FetchReaderState::Draft19(inner) => fo19::read_object(inner, buf),
2327            #[allow(unreachable_patterns)]
2328            _ => unreachable!("AnyFetchObjectReader has no enabled variants"),
2329        }
2330    }
2331
2332    /// Decode the next fetch frame, keeping what re-encoding it later takes.
2333    ///
2334    /// Advances `buf` and this reader exactly as
2335    /// [`read_object_meta`](Self::read_object_meta) does, and reports the same
2336    /// framing in [`AnyFetchFrame::meta`]. What it additionally keeps is the
2337    /// shape the frame arrived in, which is the whole of what
2338    /// [`AnyFetchObjectWriter::reemit_object`] needs to write the frame back
2339    /// out against a different predecessor.
2340    ///
2341    /// Costs nothing over `read_object_meta`, which is itself defined over
2342    /// this: the per-draft header it keeps is one the decode produced and
2343    /// dropped.
2344    #[allow(unused_variables, unreachable_code)]
2345    pub fn read_object_frame(&mut self, buf: &mut impl Buf) -> Result<AnyFetchFrame, CodecError> {
2346        match &mut self.state {
2347            #[cfg(feature = "draft07")]
2348            FetchReaderState::Draft07 => fo07::read_object_meta(buf)
2349                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft07, meta)),
2350            #[cfg(feature = "draft08")]
2351            FetchReaderState::Draft08 => fo08::read_object_meta(buf)
2352                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft08, meta)),
2353            #[cfg(feature = "draft09")]
2354            FetchReaderState::Draft09 => fo09::read_object_meta(buf)
2355                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft09, meta)),
2356            #[cfg(feature = "draft10")]
2357            FetchReaderState::Draft10 => fo10::read_object_meta(buf)
2358                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft10, meta)),
2359            #[cfg(feature = "draft11")]
2360            FetchReaderState::Draft11 => fo11::read_object_meta(buf)
2361                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft11, meta)),
2362            #[cfg(feature = "draft12")]
2363            FetchReaderState::Draft12 => fo12::read_object_meta(buf)
2364                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft12, meta)),
2365            #[cfg(feature = "draft13")]
2366            FetchReaderState::Draft13 => fo13::read_object_meta(buf)
2367                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft13, meta)),
2368            #[cfg(feature = "draft14")]
2369            FetchReaderState::Draft14 => fo14::read_object_meta(buf)
2370                .map(|meta| AnyFetchFrame::absolute(DraftVersion::Draft14, meta)),
2371            #[cfg(feature = "draft15")]
2372            FetchReaderState::Draft15(inner) => fo15::read_object_frame(inner, buf),
2373            #[cfg(feature = "draft16")]
2374            FetchReaderState::Draft16(inner) => fo16::read_object_frame(inner, buf),
2375            #[cfg(feature = "draft17")]
2376            FetchReaderState::Draft17(inner) => fo17::read_object_frame(inner, buf),
2377            #[cfg(feature = "draft18")]
2378            FetchReaderState::Draft18(inner) => fo18::read_object_frame(inner, buf),
2379            #[cfg(feature = "draft19")]
2380            FetchReaderState::Draft19(inner) => fo19::read_object_frame(inner, buf),
2381            #[allow(unreachable_patterns)]
2382            _ => unreachable!("AnyFetchObjectReader has no enabled variants"),
2383        }
2384    }
2385
2386    /// Decode the next fetch frame's framing without copying its payload.
2387    ///
2388    /// Advances `buf` past the whole frame exactly as
2389    /// [`read_object`](Self::read_object) does, and advances the same reader
2390    /// state, so the two are interchangeable on one stream.
2391    #[allow(unused_variables, unreachable_code)]
2392    pub fn read_object_meta(
2393        &mut self,
2394        buf: &mut impl Buf,
2395    ) -> Result<AnyFetchObjectMeta, CodecError> {
2396        match &mut self.state {
2397            #[cfg(feature = "draft07")]
2398            FetchReaderState::Draft07 => fo07::read_object_meta(buf),
2399            #[cfg(feature = "draft08")]
2400            FetchReaderState::Draft08 => fo08::read_object_meta(buf),
2401            #[cfg(feature = "draft09")]
2402            FetchReaderState::Draft09 => fo09::read_object_meta(buf),
2403            #[cfg(feature = "draft10")]
2404            FetchReaderState::Draft10 => fo10::read_object_meta(buf),
2405            #[cfg(feature = "draft11")]
2406            FetchReaderState::Draft11 => fo11::read_object_meta(buf),
2407            #[cfg(feature = "draft12")]
2408            FetchReaderState::Draft12 => fo12::read_object_meta(buf),
2409            #[cfg(feature = "draft13")]
2410            FetchReaderState::Draft13 => fo13::read_object_meta(buf),
2411            #[cfg(feature = "draft14")]
2412            FetchReaderState::Draft14 => fo14::read_object_meta(buf),
2413            #[cfg(feature = "draft15")]
2414            FetchReaderState::Draft15(inner) => fo15::read_object_meta(inner, buf),
2415            #[cfg(feature = "draft16")]
2416            FetchReaderState::Draft16(inner) => fo16::read_object_meta(inner, buf),
2417            #[cfg(feature = "draft17")]
2418            FetchReaderState::Draft17(inner) => fo17::read_object_meta(inner, buf),
2419            #[cfg(feature = "draft18")]
2420            FetchReaderState::Draft18(inner) => fo18::read_object_meta(inner, buf),
2421            #[cfg(feature = "draft19")]
2422            FetchReaderState::Draft19(inner) => fo19::read_object_meta(inner, buf),
2423            #[allow(unreachable_patterns)]
2424            _ => unreachable!("AnyFetchObjectReader has no enabled variants"),
2425        }
2426    }
2427}
2428
2429// ── Carrying a fetch frame from a reader to a writer ────────
2430
2431/// Per-draft capture of the shape one fetch frame arrived in.
2432///
2433/// Drafts 07-14 keep nothing: every field of a fetch object is on their wire
2434/// outright, so the bytes say the same thing whatever precedes them. Drafts
2435/// 15-19 keep the frame's own header, and draft-16 the resolved Location
2436/// beside it, because those two are exactly what each draft's
2437/// `FetchObjectWriter` is handed.
2438#[derive(Debug, Clone)]
2439enum FetchFrameShape {
2440    /// A frame whose fields are all absolute.
2441    #[cfg(any(
2442        feature = "draft07",
2443        feature = "draft08",
2444        feature = "draft09",
2445        feature = "draft10",
2446        feature = "draft11",
2447        feature = "draft12",
2448        feature = "draft13",
2449        feature = "draft14"
2450    ))]
2451    Absolute,
2452    #[cfg(feature = "draft15")]
2453    Draft15(crate::draft15::data_stream::FetchObjectHeader),
2454    #[cfg(feature = "draft16")]
2455    Draft16(
2456        crate::draft16::data_stream::FetchObjectHeader,
2457        crate::draft16::data_stream::FetchObjectLocation,
2458    ),
2459    #[cfg(feature = "draft17")]
2460    Draft17(crate::draft17::data_stream::FetchObject),
2461    #[cfg(feature = "draft18")]
2462    Draft18(crate::draft18::data_stream::FetchObject),
2463    #[cfg(feature = "draft19")]
2464    Draft19(crate::draft19::data_stream::FetchObject),
2465}
2466
2467/// One fetch frame, in the form re-encoding it takes.
2468///
2469/// Produced by [`AnyFetchObjectReader::read_object_frame`] and consumed by
2470/// [`AnyFetchObjectWriter::reemit_object`]. It is one value rather than two
2471/// because a frame's resolved identity and the shape it arrived in are only
2472/// meaningful together: the first says what the frame *is*, the second is the
2473/// encoding a writer keeps wherever it still says the same thing, and that is
2474/// what reproduces an untouched stream byte for byte.
2475#[derive(Debug, Clone)]
2476pub struct AnyFetchFrame {
2477    /// The framing, exactly as [`AnyFetchObjectReader::read_object_meta`]
2478    /// reports it.
2479    pub meta: AnyFetchObjectMeta,
2480    draft: DraftVersion,
2481    shape: FetchFrameShape,
2482}
2483
2484impl AnyFetchFrame {
2485    /// The draft whose stream this frame was read off.
2486    ///
2487    /// A writer refuses a frame from any other draft rather than re-encoding
2488    /// it: the two would agree on the resolved Location and disagree on
2489    /// everything the flags mean.
2490    #[must_use]
2491    pub fn draft(&self) -> DraftVersion {
2492        self.draft
2493    }
2494
2495    /// A frame from one of the drafts that keeps nothing.
2496    #[cfg(any(
2497        feature = "draft07",
2498        feature = "draft08",
2499        feature = "draft09",
2500        feature = "draft10",
2501        feature = "draft11",
2502        feature = "draft12",
2503        feature = "draft13",
2504        feature = "draft14"
2505    ))]
2506    fn absolute(draft: DraftVersion, meta: AnyFetchObjectMeta) -> Self {
2507        Self { meta, draft, shape: FetchFrameShape::Absolute }
2508    }
2509}
2510
2511// ── Fetch object writer ─────────────────────────────────────
2512
2513/// What [`AnyFetchObjectWriter::reemit_object`] had to do.
2514///
2515/// The counterpart of [`Reemit`], and deliberately not the same type. A
2516/// subgroup object's fix-up rewrites one leading varint and always writes the
2517/// whole object out; a fetch frame's is a re-encode of the whole header, and
2518/// the case worth having a shape for is the one where no re-encode is owed and
2519/// nothing is written at all.
2520#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2521pub enum FetchReemit {
2522    /// The frame's own bytes still encode it against the frame now in front of
2523    /// it, so **nothing was written** and the caller forwards `raw` untouched.
2524    ///
2525    /// This is every frame on a stream nothing was removed from, which is why
2526    /// it writes nothing: a relay that copied each frame through an output
2527    /// buffer to discover it would copy every payload it forwards.
2528    Unchanged,
2529    /// New framing was needed. The whole frame — the new framing followed by
2530    /// every byte of `raw` behind the old — was written to `out`, and the
2531    /// caller forwards that instead of `raw`.
2532    Reframed {
2533        /// Bytes the framing occupied in `raw`.
2534        framing_bytes_before: usize,
2535        /// Bytes it occupies in the output.
2536        framing_bytes_after: usize,
2537    },
2538}
2539
2540/// Per-draft writer state, mirroring [`FetchReaderState`].
2541#[derive(Debug, Clone)]
2542enum FetchWriterState {
2543    /// Drafts 07-14, which write every field of a fetch object outright and
2544    /// have nothing to write one *against*. The draft is carried so that a
2545    /// frame from another one is still refused.
2546    #[cfg(any(
2547        feature = "draft07",
2548        feature = "draft08",
2549        feature = "draft09",
2550        feature = "draft10",
2551        feature = "draft11",
2552        feature = "draft12",
2553        feature = "draft13",
2554        feature = "draft14"
2555    ))]
2556    Absolute(DraftVersion),
2557    #[cfg(feature = "draft15")]
2558    Draft15(crate::draft15::data_stream::FetchObjectWriter),
2559    #[cfg(feature = "draft16")]
2560    Draft16(crate::draft16::data_stream::FetchObjectWriter),
2561    #[cfg(feature = "draft17")]
2562    Draft17(crate::draft17::data_stream::FetchObjectWriter),
2563    #[cfg(feature = "draft18")]
2564    Draft18(crate::draft18::data_stream::FetchObjectWriter),
2565    #[cfg(feature = "draft19")]
2566    Draft19(crate::draft19::data_stream::FetchObjectWriter),
2567}
2568
2569/// Re-emitter for the frames of a fetch data stream, for any enabled draft.
2570///
2571/// The inverse of [`AnyFetchObjectReader`], and it exists for one caller: a
2572/// relay reading one fetch stream and writing another from the same frames,
2573/// having removed some of them. Removing a frame changes what the frames
2574/// behind it are encoded *against*, and on drafts 15-19 nearly every field of
2575/// a fetch object is defined against the frame before it — draft-17
2576/// Section 10.4.4.1, Table 7: "Object ID is the prior Object's ID plus one" —
2577/// so a survivor following a removed run cannot keep its original bytes.
2578///
2579/// # How it is driven
2580///
2581/// One writer belongs to one stream, and [`reemit_object`](Self::reemit_object)
2582/// is called **for every frame the caller forwards**, in wire order, whether or
2583/// not anything has been removed yet. That call is what moves the writer, so a
2584/// forwarded frame it never saw leaves it a frame behind and re-encodes the
2585/// next survivor against the wrong predecessor. A frame the caller *elides* is
2586/// the one it is not called for — that is the whole of eliding.
2587///
2588/// # What it costs
2589///
2590/// Nothing on drafts 07-14, and on drafts 15-19 one header re-derivation per
2591/// frame, which allocates only when the answer differs from the bytes that
2592/// arrived. A stream with nothing removed from it therefore forwards every
2593/// frame's own bytes and copies no payload.
2594///
2595/// # Drafts 18 and 19 need the Group Order
2596///
2597/// Their Group ID is a difference whose sign the fetch's Group Order decides,
2598/// exactly as for [`AnyFetchObjectReader`], and it is settled on the control
2599/// plane rather than on the data stream. [`new`](Self::new) takes it for that
2600/// reason: the wrong order re-encodes without error onto groups walking the
2601/// wrong way.
2602#[derive(Debug, Clone)]
2603pub struct AnyFetchObjectWriter {
2604    state: FetchWriterState,
2605}
2606
2607impl AnyFetchObjectWriter {
2608    /// Create a writer for a stream with the given header, whose groups are
2609    /// written in `group_order`.
2610    ///
2611    /// See the type's own documentation for what the order is for and why it
2612    /// cannot be read off the stream. On drafts 07-17 the argument is ignored.
2613    ///
2614    /// Returns [`CodecError::UnsupportedDraft`] for drafts not compiled in.
2615    #[allow(unused_variables, unreachable_code)]
2616    pub fn new(
2617        header: &AnyFetchHeader,
2618        group_order: AnyFetchGroupOrder,
2619    ) -> Result<Self, CodecError> {
2620        let state = match header {
2621            #[cfg(feature = "draft07")]
2622            AnyFetchHeader::Draft07(_) => FetchWriterState::Absolute(DraftVersion::Draft07),
2623            #[cfg(feature = "draft08")]
2624            AnyFetchHeader::Draft08(_) => FetchWriterState::Absolute(DraftVersion::Draft08),
2625            #[cfg(feature = "draft09")]
2626            AnyFetchHeader::Draft09(_) => FetchWriterState::Absolute(DraftVersion::Draft09),
2627            #[cfg(feature = "draft10")]
2628            AnyFetchHeader::Draft10(_) => FetchWriterState::Absolute(DraftVersion::Draft10),
2629            #[cfg(feature = "draft11")]
2630            AnyFetchHeader::Draft11(_) => FetchWriterState::Absolute(DraftVersion::Draft11),
2631            #[cfg(feature = "draft12")]
2632            AnyFetchHeader::Draft12(_) => FetchWriterState::Absolute(DraftVersion::Draft12),
2633            #[cfg(feature = "draft13")]
2634            AnyFetchHeader::Draft13(_) => FetchWriterState::Absolute(DraftVersion::Draft13),
2635            #[cfg(feature = "draft14")]
2636            AnyFetchHeader::Draft14(_) => FetchWriterState::Absolute(DraftVersion::Draft14),
2637            #[cfg(feature = "draft15")]
2638            AnyFetchHeader::Draft15(_) => {
2639                FetchWriterState::Draft15(crate::draft15::data_stream::FetchObjectWriter::new())
2640            }
2641            #[cfg(feature = "draft16")]
2642            AnyFetchHeader::Draft16(_) => {
2643                FetchWriterState::Draft16(crate::draft16::data_stream::FetchObjectWriter::new())
2644            }
2645            #[cfg(feature = "draft17")]
2646            AnyFetchHeader::Draft17(_) => {
2647                FetchWriterState::Draft17(crate::draft17::data_stream::FetchObjectWriter::new())
2648            }
2649            #[cfg(feature = "draft18")]
2650            AnyFetchHeader::Draft18(_) => FetchWriterState::Draft18(
2651                crate::draft18::data_stream::FetchObjectWriter::new(match group_order {
2652                    AnyFetchGroupOrder::Ascending => {
2653                        crate::draft18::data_stream::GroupOrder::Ascending
2654                    }
2655                    AnyFetchGroupOrder::Descending => {
2656                        crate::draft18::data_stream::GroupOrder::Descending
2657                    }
2658                }),
2659            ),
2660            #[cfg(feature = "draft19")]
2661            AnyFetchHeader::Draft19(_) => FetchWriterState::Draft19(
2662                crate::draft19::data_stream::FetchObjectWriter::new(match group_order {
2663                    AnyFetchGroupOrder::Ascending => {
2664                        crate::draft19::data_stream::GroupOrder::Ascending
2665                    }
2666                    AnyFetchGroupOrder::Descending => {
2667                        crate::draft19::data_stream::GroupOrder::Descending
2668                    }
2669                }),
2670            ),
2671            #[allow(unreachable_patterns)]
2672            _ => {
2673                return Err(CodecError::UnsupportedDraft(format!(
2674                    "draft {:?} not enabled via feature flag",
2675                    header.draft()
2676                )));
2677            }
2678        };
2679        Ok(Self { state })
2680    }
2681
2682    /// The draft this writer encodes.
2683    #[must_use]
2684    #[allow(unreachable_code)]
2685    pub fn draft(&self) -> DraftVersion {
2686        match &self.state {
2687            #[cfg(any(
2688                feature = "draft07",
2689                feature = "draft08",
2690                feature = "draft09",
2691                feature = "draft10",
2692                feature = "draft11",
2693                feature = "draft12",
2694                feature = "draft13",
2695                feature = "draft14"
2696            ))]
2697            FetchWriterState::Absolute(draft) => *draft,
2698            #[cfg(feature = "draft15")]
2699            FetchWriterState::Draft15(_) => DraftVersion::Draft15,
2700            #[cfg(feature = "draft16")]
2701            FetchWriterState::Draft16(_) => DraftVersion::Draft16,
2702            #[cfg(feature = "draft17")]
2703            FetchWriterState::Draft17(_) => DraftVersion::Draft17,
2704            #[cfg(feature = "draft18")]
2705            FetchWriterState::Draft18(_) => DraftVersion::Draft18,
2706            #[cfg(feature = "draft19")]
2707            FetchWriterState::Draft19(_) => DraftVersion::Draft19,
2708            #[allow(unreachable_patterns)]
2709            _ => unreachable!("AnyFetchObjectWriter has no enabled variants"),
2710        }
2711    }
2712
2713    /// Re-emit one forwarded fetch frame, re-encoding its framing against the
2714    /// frames actually forwarded before it, and advance.
2715    ///
2716    /// `frame` came from [`AnyFetchObjectReader::read_object_frame`] on the
2717    /// stream being read; `raw` is that frame's wire bytes. The return value
2718    /// says which bytes to forward, and the two answers are not symmetric:
2719    /// [`FetchReemit::Unchanged`] writes nothing and means `raw` is still
2720    /// correct, while [`FetchReemit::Reframed`] has written the whole frame to
2721    /// `out` and `raw` must not also be forwarded.
2722    ///
2723    /// # `raw` need not be a complete frame
2724    ///
2725    /// **Any prefix is legal provided the whole framing is present** — the
2726    /// framing being `meta.wire_len - meta.payload_length` bytes, which is a
2727    /// number the frame already carries. Everything behind it is copied
2728    /// byte-for-byte, however many bytes there are, and no length validation is
2729    /// performed. That is what lets a caller fix up the first chunk of a frame
2730    /// too large to buffer, where only the first chunk carries the framing at
2731    /// all.
2732    ///
2733    /// # Errors
2734    ///
2735    /// [`CodecError::UnsupportedDraft`] when `frame` was read off another
2736    /// draft's stream.
2737    ///
2738    /// [`CodecError::InvalidField`] when `raw` is shorter than the framing the
2739    /// frame declares, and when the frame has no encoding against the
2740    /// predecessor now in front of it — a Group ID that moves against the
2741    /// Group Order, an Object ID that does not advance, and the arithmetic
2742    /// overflows. The writer is left where it was in that case, so a caller
2743    /// that gives up on one frame and carries on is not also one frame out.
2744    #[allow(unused_variables)]
2745    pub fn reemit_object(
2746        &mut self,
2747        frame: &AnyFetchFrame,
2748        raw: &[u8],
2749        out: &mut impl BufMut,
2750    ) -> Result<FetchReemit, CodecError> {
2751        if frame.draft != self.draft() {
2752            return Err(CodecError::UnsupportedDraft(format!(
2753                "a draft {:?} fetch frame cannot be written onto a draft {:?} stream",
2754                frame.draft,
2755                self.draft()
2756            )));
2757        }
2758
2759        let framing_len = frame.meta.wire_len.saturating_sub(frame.meta.payload_length);
2760        let framing_len = usize::try_from(framing_len).map_err(|_| CodecError::InvalidField)?;
2761        if framing_len > raw.len() {
2762            return Err(CodecError::InvalidField);
2763        }
2764        let (framing, rest) = raw.split_at(framing_len);
2765
2766        match (&mut self.state, &frame.shape) {
2767            // Nothing on these drafts' wire is written against anything, so
2768            // the frame's own bytes are correct wherever it lands.
2769            #[cfg(any(
2770                feature = "draft07",
2771                feature = "draft08",
2772                feature = "draft09",
2773                feature = "draft10",
2774                feature = "draft11",
2775                feature = "draft12",
2776                feature = "draft13",
2777                feature = "draft14"
2778            ))]
2779            (FetchWriterState::Absolute(_), FetchFrameShape::Absolute) => {
2780                Ok(FetchReemit::Unchanged)
2781            }
2782            #[cfg(feature = "draft15")]
2783            (FetchWriterState::Draft15(writer), FetchFrameShape::Draft15(original)) => {
2784                let reframed = writer.header_for(original)?;
2785                if reframed == *original {
2786                    writer.advance(original);
2787                    return Ok(FetchReemit::Unchanged);
2788                }
2789                let mut encoded = Vec::with_capacity(framing.len() + 16);
2790                reframed.encode(&mut encoded)?;
2791                writer.advance(&reframed);
2792                Ok(put_reframed(&encoded, framing.len(), rest, out))
2793            }
2794            #[cfg(feature = "draft16")]
2795            (FetchWriterState::Draft16(writer), FetchFrameShape::Draft16(original, location)) => {
2796                let reframed = writer.header_for(original, location)?;
2797                if reframed == *original {
2798                    writer.advance(original, location);
2799                    return Ok(FetchReemit::Unchanged);
2800                }
2801                let mut encoded = Vec::with_capacity(framing.len() + 16);
2802                reframed.encode(&mut encoded)?;
2803                writer.advance(&reframed, location);
2804                Ok(put_reframed(&encoded, framing.len(), rest, out))
2805            }
2806            #[cfg(feature = "draft17")]
2807            (FetchWriterState::Draft17(writer), FetchFrameShape::Draft17(original)) => {
2808                let reframed = writer.header_for(original)?;
2809                if reframed == original.header {
2810                    writer.advance(original);
2811                    return Ok(FetchReemit::Unchanged);
2812                }
2813                let mut encoded = Vec::with_capacity(framing.len() + 16);
2814                reframed.encode(&mut encoded)?;
2815                writer.advance(original);
2816                Ok(put_reframed(&encoded, framing.len(), rest, out))
2817            }
2818            #[cfg(feature = "draft18")]
2819            (FetchWriterState::Draft18(writer), FetchFrameShape::Draft18(original)) => {
2820                let reframed = writer.header_for(original)?;
2821                if reframed == original.header {
2822                    writer.advance(original);
2823                    return Ok(FetchReemit::Unchanged);
2824                }
2825                let mut encoded = Vec::with_capacity(framing.len() + 16);
2826                reframed.encode(&mut encoded)?;
2827                writer.advance(original);
2828                Ok(put_reframed(&encoded, framing.len(), rest, out))
2829            }
2830            #[cfg(feature = "draft19")]
2831            (FetchWriterState::Draft19(writer), FetchFrameShape::Draft19(original)) => {
2832                let reframed = writer.header_for(original)?;
2833                if reframed == original.header {
2834                    writer.advance(original);
2835                    return Ok(FetchReemit::Unchanged);
2836                }
2837                let mut encoded = Vec::with_capacity(framing.len() + 16);
2838                reframed.encode(&mut encoded)?;
2839                writer.advance(original);
2840                Ok(put_reframed(&encoded, framing.len(), rest, out))
2841            }
2842            // Unreachable: the drafts were compared before this match, and one
2843            // draft has one state and one shape.
2844            #[allow(unreachable_patterns)]
2845            _ => Err(CodecError::UnsupportedDraft(format!(
2846                "no fetch writer for draft {:?}",
2847                frame.draft
2848            ))),
2849        }
2850    }
2851}
2852
2853/// Write a re-encoded frame out: the new framing, then every byte that stood
2854/// behind the old one.
2855#[cfg(any(
2856    feature = "draft15",
2857    feature = "draft16",
2858    feature = "draft17",
2859    feature = "draft18",
2860    feature = "draft19"
2861))]
2862fn put_reframed(
2863    encoded: &[u8],
2864    framing_bytes_before: usize,
2865    rest: &[u8],
2866    out: &mut impl BufMut,
2867) -> FetchReemit {
2868    out.put_slice(encoded);
2869    out.put_slice(rest);
2870    FetchReemit::Reframed { framing_bytes_before, framing_bytes_after: encoded.len() }
2871}
2872
2873// ── Header helpers for the stream-type-gated drafts ─────────
2874
2875/// Generates the subgroup stream-type check drafts 11-13 share: the header's
2876/// stream type must be a subgroup type, and it decides whether objects carry
2877/// an extension block.
2878macro_rules! subgroup_extensions_fn {
2879    ($name:ident, $feat:literal, $draft:ident) => {
2880        #[cfg(feature = $feat)]
2881        fn $name(header: &crate::$draft::data_stream::SubgroupHeader) -> Result<bool, CodecError> {
2882            if !header.stream_type.is_subgroup() {
2883                return Err(CodecError::InvalidField);
2884            }
2885            Ok(header.stream_type.has_extensions())
2886        }
2887    };
2888}
2889
2890subgroup_extensions_fn!(subgroup_extensions_11, "draft11", draft11);
2891subgroup_extensions_fn!(subgroup_extensions_12, "draft12", draft12);
2892subgroup_extensions_fn!(subgroup_extensions_13, "draft13", draft13);