Skip to main content

liminal_protocol/wire/
codec.rs

1use alloc::vec::Vec;
2
3use super::{
4    AttachAttemptToken, AttachSecret, BindingEpoch, ClientDiscriminant, ClientRequest,
5    CloseCauseTag, ConnectionIncarnation, CredentialAttachRequest, DecodeClass, DetachAttemptToken,
6    DetachRequest, DetachedCause, DiedCause, EnrollmentRequest, EnrollmentToken, Generation,
7    LeaveAttemptToken, LeaveRequest, MarkerAck, ObserverRecoveryHandshake, ObserverRefusal,
8    ParticipantAck, ParticipantDelivery, ParticipantRecord, ParticipantTransportRejected,
9    ProtocolVersion, PushDiscriminant, RecordAdmission, RecordAdmissionAttemptToken, RecordKind,
10    ServerDiscriminant, ServerPush, ServerValue, TransportRejectionReason,
11};
12
13/// Stable generic frame type assigned to participant traffic.
14pub const PARTICIPANT_FRAME_TYPE: u8 = 0x1A;
15
16/// Bytes in the existing generic frame header.
17pub const GENERIC_HEADER_LEN: usize = 10;
18
19/// Bytes in the version/discriminant prefix of every participant payload.
20pub const PARTICIPANT_PREFIX_LEN: usize = 6;
21
22/// Fixed complete-frame overhead before a selected participant body.
23pub const PARTICIPANT_FRAME_OVERHEAD: usize = GENERIC_HEADER_LEN + PARTICIPANT_PREFIX_LEN;
24
25/// Largest complete frame representable by the generic `u32` payload length.
26pub const FRAME_MAX: u64 = 4_294_967_305;
27
28/// Allocation ceiling used before participant capability is stored.
29pub const PRECAP_PARTICIPANT_FRAME_MAX: u64 = 1_048_576;
30
31/// Smallest valid complete participant-frame allocation limit.
32pub const MIN_PARTICIPANT_FRAME_MAX: u64 = 16;
33
34/// Receiver whose direction registry is used during decoding.
35#[derive(Clone, Copy, Debug, PartialEq, Eq)]
36pub enum ReceiverDirection {
37    /// Server receiver; only client requests are assigned.
38    Server,
39    /// SDK receiver; server values and pushes are assigned.
40    Client,
41}
42
43/// Complete-frame allocation limit proven valid for the participant gate.
44///
45/// Construction enforces the R-D2 range `16..=FRAME_MAX`, so an inbound gate
46/// can compare the declared complete size before allocating its selected body.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48pub struct ValidatedFrameLimit(u64);
49
50impl ValidatedFrameLimit {
51    /// Validates one negotiated complete-frame limit.
52    ///
53    /// # Errors
54    ///
55    /// Returns [`CodecError::InvalidFrameLimit`] when `max_frame_bytes` is
56    /// outside `16..=FRAME_MAX`.
57    pub const fn new(max_frame_bytes: u64) -> Result<Self, CodecError> {
58        if max_frame_bytes < MIN_PARTICIPANT_FRAME_MAX || max_frame_bytes > FRAME_MAX {
59            return Err(CodecError::InvalidFrameLimit { max_frame_bytes });
60        }
61        Ok(Self(max_frame_bytes))
62    }
63
64    /// Returns the validated complete-frame limit.
65    #[must_use]
66    pub const fn get(self) -> u64 {
67        self.0
68    }
69}
70
71/// Connection authentication state presented to the inbound participant gate.
72#[derive(Clone, Copy, Debug, PartialEq, Eq)]
73pub enum AuthenticationState {
74    /// Shared connection authentication has not succeeded.
75    Unauthenticated,
76    /// Shared connection authentication has succeeded.
77    Authenticated,
78}
79
80/// Stored negotiated participant capability.
81///
82/// The private fields ensure this session's only supported capability is
83/// exactly protocol v1.0 with an already-validated complete-frame limit.
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct NegotiatedParticipantCapability {
86    protocol_version: ProtocolVersion,
87    max_frame_bytes: ValidatedFrameLimit,
88}
89
90impl NegotiatedParticipantCapability {
91    /// Constructs the currently supported `participant-v1` capability.
92    #[must_use]
93    pub const fn v1(max_frame_bytes: ValidatedFrameLimit) -> Self {
94        Self {
95            protocol_version: ProtocolVersion::V1,
96            max_frame_bytes,
97        }
98    }
99
100    /// Returns the negotiated participant protocol version.
101    #[must_use]
102    pub const fn protocol_version(self) -> ProtocolVersion {
103        self.protocol_version
104    }
105
106    /// Returns the negotiated complete-frame allocation limit.
107    #[must_use]
108    pub const fn max_frame_bytes(self) -> ValidatedFrameLimit {
109        self.max_frame_bytes
110    }
111}
112
113/// Participant-capability state presented to the inbound gate.
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
115pub enum ParticipantCapabilityState {
116    /// No participant capability is stored; the pre-capability ceiling applies.
117    Missing,
118    /// The `participant-v1` capability is stored.
119    Negotiated(NegotiatedParticipantCapability),
120}
121
122/// Complete connection context needed by the inbound participant gate.
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub struct InboundGateContext {
125    /// Registry direction selected by the local receiver.
126    pub receiver: ReceiverDirection,
127    /// Shared connection authentication state.
128    pub authentication: AuthenticationState,
129    /// Stored participant capability, if negotiation succeeded.
130    pub participant_capability: ParticipantCapabilityState,
131}
132
133/// One complete typed participant frame.
134#[derive(Clone, Debug, PartialEq, Eq)]
135pub enum ParticipantFrame {
136    /// Client-to-server request.
137    ClientRequest(ClientRequest),
138    /// Server-to-client semantic response.
139    ServerValue(ServerValue),
140    /// Server-to-client pushed value.
141    ServerPush(ServerPush),
142}
143
144/// Result category for failures before or during the participant inbound gate.
145///
146/// A non-participant outer frame is returned separately so the caller can
147/// preserve the generic transport's unknown-frame behavior. It is never
148/// converted into a participant transport rejection.
149#[derive(Clone, Debug, PartialEq, Eq)]
150pub enum InboundGateError {
151    /// The generic outer type is not the participant frame type.
152    NotParticipantFrame {
153        /// Unrecognized or differently assigned generic outer type.
154        frame_type: u8,
155    },
156    /// One of the five exact participant transport rejection schemas.
157    ParticipantRejected(ParticipantTransportRejected),
158}
159
160/// Deterministic participant codec failure.
161#[derive(Clone, Copy, Debug, PartialEq, Eq)]
162pub enum CodecError {
163    /// The generic outer type is not assigned to participant traffic.
164    NotParticipantFrame {
165        /// Observed generic frame type.
166        frame_type: u8,
167    },
168    /// A structural body or framing failure with the canonical decode class.
169    Decode {
170        /// Exact failure class.
171        class: DecodeClass,
172    },
173    /// The concrete participant version differs from v1.0.
174    UnsupportedVersion {
175        /// Version read from the structural prefix.
176        presented: ProtocolVersion,
177        /// Version supported by this codec.
178        supported: ProtocolVersion,
179    },
180    /// Caller-provided encoding storage is too short.
181    OutputTooSmall {
182        /// Complete bytes required.
183        required: usize,
184        /// Bytes supplied by the caller.
185        available: usize,
186    },
187    /// A body or complete frame cannot fit its canonical `u32` length field.
188    LengthOverflow,
189    /// A typed value violates a field-domain invariant.
190    InvalidValue,
191    /// A negotiated complete-frame limit lies outside `16..=FRAME_MAX`.
192    InvalidFrameLimit {
193        /// Rejected complete-frame limit.
194        max_frame_bytes: u64,
195    },
196}
197
198/// Computes complete bytes from a generic-header payload length.
199#[must_use]
200pub fn complete_frame_bytes(payload_length: u32) -> u64 {
201    10 + u64::from(payload_length)
202}
203
204/// Applies the total R-D2 inbound gate to one declared complete frame.
205///
206/// The outer type is classified first. For participant frames, the declared
207/// complete size is then checked against the negotiated limit (or the fixed
208/// pre-capability ceiling) before structural body decoding can allocate.
209/// Structural failures map to their exact transport-rejection reason. Only
210/// after a complete structural decode does the gate test authentication and
211/// participant capability, in that order.
212///
213/// An SDK-side, structurally valid v1
214/// [`ServerValue::ParticipantTransportRejected`] bypasses the two local state
215/// checks. This is the sole receive-side exception defined by R-D2.
216///
217/// # Errors
218///
219/// Returns [`InboundGateError::NotParticipantFrame`] without applying any
220/// participant rule when the outer type differs. Otherwise returns the exact
221/// pre-semantic [`ParticipantTransportRejected`] selected by R-D2 inside
222/// [`InboundGateError::ParticipantRejected`].
223pub fn gate_inbound(
224    input: &[u8],
225    context: InboundGateContext,
226) -> Result<ParticipantFrame, InboundGateError> {
227    if let Some(frame_type) = input.first().copied() {
228        if frame_type != PARTICIPANT_FRAME_TYPE {
229            return Err(InboundGateError::NotParticipantFrame { frame_type });
230        }
231    }
232
233    let max_frame_bytes = match context.participant_capability {
234        ParticipantCapabilityState::Missing => PRECAP_PARTICIPANT_FRAME_MAX,
235        ParticipantCapabilityState::Negotiated(capability) => capability.max_frame_bytes().get(),
236    };
237
238    if let Some(complete_frame_bytes) = declared_complete_frame_bytes(input) {
239        if complete_frame_bytes > max_frame_bytes {
240            return Err(gate_rejection(TransportRejectionReason::FrameTooLarge {
241                complete_frame_bytes,
242                max_frame_bytes,
243            }));
244        }
245    }
246
247    let frame = decode(input, context.receiver).map_err(codec_gate_error)?;
248    if matches!(
249        (&frame, context.receiver),
250        (
251            ParticipantFrame::ServerValue(ServerValue::ParticipantTransportRejected(_)),
252            ReceiverDirection::Client
253        )
254    ) {
255        return Ok(frame);
256    }
257
258    if context.authentication == AuthenticationState::Unauthenticated {
259        return Err(gate_rejection(
260            TransportRejectionReason::AuthenticationFailed,
261        ));
262    }
263    if context.participant_capability == ParticipantCapabilityState::Missing {
264        return Err(gate_rejection(
265            TransportRejectionReason::ParticipantCapabilityRequired,
266        ));
267    }
268
269    Ok(frame)
270}
271
272/// Returns the exact complete-frame encoded length.
273///
274/// # Errors
275///
276/// Returns [`CodecError::LengthOverflow`] when a selected variable body cannot
277/// fit the generic frame's `u32` payload length.
278pub fn encoded_len(frame: &ParticipantFrame) -> Result<usize, CodecError> {
279    let mut size = SizeSink::default();
280    encode_body(frame, &mut size)?;
281    let payload = PARTICIPANT_PREFIX_LEN
282        .checked_add(size.len)
283        .ok_or(CodecError::LengthOverflow)?;
284    let _: u32 = payload.try_into().map_err(|_| CodecError::LengthOverflow)?;
285    GENERIC_HEADER_LEN
286        .checked_add(payload)
287        .ok_or(CodecError::LengthOverflow)
288}
289
290/// Encodes one complete participant frame into caller-provided storage.
291///
292/// The generic header, v1.0 structural prefix, and selected body are emitted in
293/// network byte order. Client requests and pushed values encode without
294/// allocation; semantic server values use one temporary exact-body buffer.
295///
296/// # Errors
297///
298/// Returns [`CodecError::OutputTooSmall`] when `output` is shorter than
299/// [`encoded_len`], or propagates a selected body's length/domain error.
300pub fn encode(frame: &ParticipantFrame, output: &mut [u8]) -> Result<usize, CodecError> {
301    let required = encoded_len(frame)?;
302    if output.len() < required {
303        return Err(CodecError::OutputTooSmall {
304            required,
305            available: output.len(),
306        });
307    }
308
309    let payload_len = required
310        .checked_sub(GENERIC_HEADER_LEN)
311        .ok_or(CodecError::LengthOverflow)?;
312    let payload_len: u32 = payload_len
313        .try_into()
314        .map_err(|_| CodecError::LengthOverflow)?;
315    let discriminant = frame_discriminant(frame);
316
317    let mut writer = Writer::new(output);
318    writer.put_u8(PARTICIPANT_FRAME_TYPE)?;
319    writer.put_u8(0)?;
320    writer.put_u32(0)?;
321    writer.put_u32(payload_len)?;
322    writer.put_u16(ProtocolVersion::V1.major)?;
323    writer.put_u16(ProtocolVersion::V1.minor)?;
324    writer.put_u16(discriminant)?;
325    encode_body(frame, &mut writer)?;
326    Ok(required)
327}
328
329/// Decodes one exact complete participant frame for `receiver`.
330///
331/// Counts are proven against the already bounded remaining frame before any
332/// list allocation. Extra bytes inside or outside the declared complete frame
333/// are rejected as non-canonical by this exact-frame API.
334///
335/// # Errors
336///
337/// Returns [`CodecError::NotParticipantFrame`] before participant framing when
338/// the generic outer type differs, the contract's canonical structural class,
339/// or [`CodecError::UnsupportedVersion`] before discriminant/body decoding.
340pub fn decode(input: &[u8], receiver: ReceiverDirection) -> Result<ParticipantFrame, CodecError> {
341    if let Some(frame_type) = input.first().copied() {
342        if frame_type != PARTICIPANT_FRAME_TYPE {
343            return Err(CodecError::NotParticipantFrame { frame_type });
344        }
345    }
346    if input.len() < GENERIC_HEADER_LEN {
347        return Err(decode_error(DecodeClass::Framing));
348    }
349
350    let mut header = Reader::new(input);
351    let _frame_type = header.take_u8()?;
352    let flags = header.take_u8()?;
353    let stream_id = header.take_u32()?;
354    let payload_len = header.take_u32()?;
355    if flags != 0 || stream_id != 0 || payload_len < 6 {
356        return Err(decode_error(DecodeClass::Framing));
357    }
358
359    let complete: usize = complete_frame_bytes(payload_len)
360        .try_into()
361        .map_err(|_| decode_error(DecodeClass::Framing))?;
362    if input.len() < complete {
363        return Err(decode_error(DecodeClass::MissingRequiredField));
364    }
365    if input.len() > complete {
366        return Err(decode_error(DecodeClass::CanonicalEncoding));
367    }
368
369    let payload = input
370        .get(GENERIC_HEADER_LEN..complete)
371        .ok_or_else(|| decode_error(DecodeClass::MissingRequiredField))?;
372    let mut prefix = Reader::new(payload);
373    let version = ProtocolVersion::new(prefix.take_u16()?, prefix.take_u16()?);
374    if version != ProtocolVersion::V1 {
375        return Err(CodecError::UnsupportedVersion {
376            presented: version,
377            supported: ProtocolVersion::V1,
378        });
379    }
380    let discriminant = prefix.take_u16()?;
381    let body = prefix.remaining_bytes();
382
383    match receiver {
384        ReceiverDirection::Server => {
385            let tag = ClientDiscriminant::try_from(discriminant)
386                .map_err(|_| decode_error(DecodeClass::UnknownDiscriminant))?;
387            decode_client_request(tag, body).map(ParticipantFrame::ClientRequest)
388        }
389        ReceiverDirection::Client => {
390            if let Ok(tag) = PushDiscriminant::try_from(discriminant) {
391                return decode_server_push(tag, body).map(ParticipantFrame::ServerPush);
392            }
393            if let Ok(tag) = ServerDiscriminant::try_from(discriminant) {
394                let (value, _) = super::server_codec::decode_server_value_body(tag, version, body)?;
395                return Ok(ParticipantFrame::ServerValue(value));
396            }
397            Err(decode_error(DecodeClass::UnknownDiscriminant))
398        }
399    }
400}
401
402fn declared_complete_frame_bytes(input: &[u8]) -> Option<u64> {
403    let payload_length = input.get(6..GENERIC_HEADER_LEN)?;
404    let payload_length = <[u8; 4]>::try_from(payload_length).ok()?;
405    Some(complete_frame_bytes(u32::from_be_bytes(payload_length)))
406}
407
408const fn transport_rejection(reason: TransportRejectionReason) -> ParticipantTransportRejected {
409    ParticipantTransportRejected { reason }
410}
411
412const fn gate_rejection(reason: TransportRejectionReason) -> InboundGateError {
413    InboundGateError::ParticipantRejected(transport_rejection(reason))
414}
415
416const fn codec_gate_error(error: CodecError) -> InboundGateError {
417    match error {
418        CodecError::NotParticipantFrame { frame_type } => {
419            InboundGateError::NotParticipantFrame { frame_type }
420        }
421        CodecError::Decode { class } => gate_rejection(TransportRejectionReason::DecodeFailed {
422            decode_class: class,
423        }),
424        CodecError::UnsupportedVersion {
425            presented,
426            supported,
427        } => gate_rejection(TransportRejectionReason::UnsupportedVersion {
428            presented_version: presented,
429            supported_version: supported,
430        }),
431        CodecError::OutputTooSmall { .. }
432        | CodecError::LengthOverflow
433        | CodecError::InvalidValue
434        | CodecError::InvalidFrameLimit { .. } => {
435            gate_rejection(TransportRejectionReason::DecodeFailed {
436                decode_class: DecodeClass::InvalidField,
437            })
438        }
439    }
440}
441
442const fn frame_discriminant(frame: &ParticipantFrame) -> u16 {
443    match frame {
444        ParticipantFrame::ClientRequest(value) => value.discriminant().wire_value(),
445        ParticipantFrame::ServerValue(value) => value.discriminant().wire_value(),
446        ParticipantFrame::ServerPush(value) => value.discriminant().wire_value(),
447    }
448}
449
450fn encode_body<S: Sink>(frame: &ParticipantFrame, sink: &mut S) -> Result<(), CodecError> {
451    match frame {
452        ParticipantFrame::ClientRequest(value) => encode_client_request(value, sink),
453        ParticipantFrame::ServerPush(value) => encode_server_push(value, sink),
454        ParticipantFrame::ServerValue(value) => {
455            let (_, body) =
456                super::server_codec::encode_server_value_body(value, ProtocolVersion::V1)?;
457            sink.put(&body)
458        }
459    }
460}
461
462fn encode_client_request<S: Sink>(value: &ClientRequest, sink: &mut S) -> Result<(), CodecError> {
463    match value {
464        ClientRequest::Enrollment(value) => {
465            sink.put_u64(value.conversation_id)?;
466            sink.put_fixed(value.enrollment_token.as_bytes())
467        }
468        ClientRequest::CredentialAttach(value) => {
469            sink.put_u64(value.conversation_id)?;
470            sink.put_u64(value.participant_id)?;
471            put_generation(sink, value.capability_generation)?;
472            sink.put_fixed(value.attach_secret.as_bytes())?;
473            sink.put_fixed(value.attach_attempt_token.as_bytes())?;
474            put_option_u64(sink, value.accept_marker_delivery_seq)
475        }
476        ClientRequest::Detach(value) => {
477            sink.put_u64(value.conversation_id)?;
478            sink.put_u64(value.participant_id)?;
479            put_generation(sink, value.capability_generation)?;
480            sink.put_fixed(value.detach_attempt_token.as_bytes())
481        }
482        ClientRequest::ParticipantAck(value) => {
483            sink.put_u64(value.conversation_id)?;
484            sink.put_u64(value.participant_id)?;
485            put_generation(sink, value.capability_generation)?;
486            sink.put_u64(value.through_seq)
487        }
488        ClientRequest::Leave(value) => {
489            sink.put_u64(value.conversation_id)?;
490            sink.put_u64(value.participant_id)?;
491            put_generation(sink, value.capability_generation)?;
492            sink.put_fixed(value.attach_secret.as_bytes())?;
493            sink.put_fixed(value.leave_attempt_token.as_bytes())
494        }
495        ClientRequest::MarkerAck(value) => {
496            sink.put_u64(value.conversation_id)?;
497            sink.put_u64(value.participant_id)?;
498            put_generation(sink, value.capability_generation)?;
499            sink.put_u64(value.marker_delivery_seq)
500        }
501        ClientRequest::RecordAdmission(value) => {
502            sink.put_u64(value.conversation_id)?;
503            sink.put_u64(value.participant_id)?;
504            put_generation(sink, value.capability_generation)?;
505            sink.put_fixed(value.record_admission_attempt_token.as_bytes())?;
506            sink.put_bytes(&value.payload)
507        }
508        ClientRequest::ObserverRecovery(value) => {
509            let count: u64 = value
510                .observer_refusals
511                .len()
512                .try_into()
513                .map_err(|_| CodecError::LengthOverflow)?;
514            sink.put_u64(count)?;
515            for refusal in &value.observer_refusals {
516                sink.put_u64(refusal.conversation_id)?;
517                sink.put_u64(refusal.refused_epoch)?;
518            }
519            Ok(())
520        }
521    }
522}
523
524fn decode_client_request(
525    tag: ClientDiscriminant,
526    body: &[u8],
527) -> Result<ClientRequest, CodecError> {
528    let mut reader = Reader::new(body);
529    let value = match tag {
530        ClientDiscriminant::EnrollmentRequest => ClientRequest::Enrollment(EnrollmentRequest {
531            conversation_id: reader.take_u64()?,
532            enrollment_token: EnrollmentToken::new(reader.take_fixed()?),
533        }),
534        ClientDiscriminant::CredentialAttachRequest => {
535            ClientRequest::CredentialAttach(CredentialAttachRequest {
536                conversation_id: reader.take_u64()?,
537                participant_id: reader.take_u64()?,
538                capability_generation: reader.take_generation()?,
539                attach_secret: AttachSecret::new(reader.take_fixed()?),
540                attach_attempt_token: AttachAttemptToken::new(reader.take_fixed()?),
541                accept_marker_delivery_seq: reader.take_option_u64()?,
542            })
543        }
544        ClientDiscriminant::DetachRequest => ClientRequest::Detach(DetachRequest {
545            conversation_id: reader.take_u64()?,
546            participant_id: reader.take_u64()?,
547            capability_generation: reader.take_generation()?,
548            detach_attempt_token: DetachAttemptToken::new(reader.take_fixed()?),
549        }),
550        ClientDiscriminant::ParticipantAck => ClientRequest::ParticipantAck(ParticipantAck {
551            conversation_id: reader.take_u64()?,
552            participant_id: reader.take_u64()?,
553            capability_generation: reader.take_generation()?,
554            through_seq: reader.take_u64()?,
555        }),
556        ClientDiscriminant::LeaveRequest => ClientRequest::Leave(LeaveRequest {
557            conversation_id: reader.take_u64()?,
558            participant_id: reader.take_u64()?,
559            capability_generation: reader.take_generation()?,
560            attach_secret: AttachSecret::new(reader.take_fixed()?),
561            leave_attempt_token: LeaveAttemptToken::new(reader.take_fixed()?),
562        }),
563        ClientDiscriminant::MarkerAck => ClientRequest::MarkerAck(MarkerAck {
564            conversation_id: reader.take_u64()?,
565            participant_id: reader.take_u64()?,
566            capability_generation: reader.take_generation()?,
567            marker_delivery_seq: reader.take_u64()?,
568        }),
569        ClientDiscriminant::RecordAdmission => ClientRequest::RecordAdmission(RecordAdmission {
570            conversation_id: reader.take_u64()?,
571            participant_id: reader.take_u64()?,
572            capability_generation: reader.take_generation()?,
573            record_admission_attempt_token: RecordAdmissionAttemptToken::new(reader.take_fixed()?),
574            payload: reader.take_bytes()?,
575        }),
576        ClientDiscriminant::ObserverRecoveryHandshake => {
577            let count = reader.take_u64()?;
578            reader.require_fixed_list(count, 16)?;
579            let mut observer_refusals = Vec::new();
580            for _ in 0..count {
581                observer_refusals.push(ObserverRefusal {
582                    conversation_id: reader.take_u64()?,
583                    refused_epoch: reader.take_u64()?,
584                });
585            }
586            ClientRequest::ObserverRecovery(ObserverRecoveryHandshake { observer_refusals })
587        }
588    };
589    reader.finish()?;
590    Ok(value)
591}
592
593fn encode_server_push<S: Sink>(value: &ServerPush, sink: &mut S) -> Result<(), CodecError> {
594    match value {
595        ServerPush::ObserverProgressed {
596            conversation_id,
597            refused_epoch,
598            observer_progress,
599        } => {
600            sink.put_u64(*conversation_id)?;
601            sink.put_u64(*refused_epoch)?;
602            sink.put_u64(*observer_progress)
603        }
604        ServerPush::ParticipantDelivery(value) => encode_participant_delivery(value, sink),
605    }
606}
607
608fn encode_participant_delivery<S: Sink>(
609    value: &ParticipantDelivery,
610    sink: &mut S,
611) -> Result<(), CodecError> {
612    sink.put_u64(value.conversation_id)?;
613    sink.put_u64(value.delivery_seq)?;
614    sink.put_u16(value.record.record_kind().wire_value())?;
615    match &value.record {
616        ParticipantRecord::OrdinaryRecord {
617            sender_participant_id,
618            payload,
619        } => {
620            sink.put_u64(*sender_participant_id)?;
621            sink.put_bytes(payload)
622        }
623        ParticipantRecord::Attached {
624            affected_participant_id,
625            binding_epoch,
626        } => {
627            sink.put_u64(*affected_participant_id)?;
628            put_binding_epoch(sink, *binding_epoch)
629        }
630        ParticipantRecord::Detached {
631            affected_participant_id,
632            binding_epoch,
633            cause,
634        } => {
635            sink.put_u64(*affected_participant_id)?;
636            put_binding_epoch(sink, *binding_epoch)?;
637            sink.put_u16(cause.close_cause().tag().wire_value())
638        }
639        ParticipantRecord::Died {
640            affected_participant_id,
641            binding_epoch,
642            cause,
643        } => {
644            sink.put_u64(*affected_participant_id)?;
645            put_binding_epoch(sink, *binding_epoch)?;
646            sink.put_u16(cause.close_cause().tag().wire_value())?;
647            if let DiedCause::UncleanServerRestart {
648                prior_server_incarnation,
649            } = cause
650            {
651                sink.put_u64(*prior_server_incarnation)?;
652            }
653            Ok(())
654        }
655        ParticipantRecord::Left {
656            affected_participant_id,
657            ended_binding_epoch,
658        } => {
659            sink.put_u64(*affected_participant_id)?;
660            put_option_binding_epoch(sink, *ended_binding_epoch)
661        }
662        ParticipantRecord::HistoryCompacted {
663            affected_participant_id,
664            abandoned_after,
665            abandoned_through,
666            physical_floor_at_decision,
667        } => {
668            sink.put_u64(*affected_participant_id)?;
669            sink.put_u64(*abandoned_after)?;
670            sink.put_u64(*abandoned_through)?;
671            sink.put_u64(*physical_floor_at_decision)
672        }
673    }
674}
675
676fn decode_server_push(tag: PushDiscriminant, body: &[u8]) -> Result<ServerPush, CodecError> {
677    let mut reader = Reader::new(body);
678    let value = match tag {
679        PushDiscriminant::ObserverProgressed => ServerPush::ObserverProgressed {
680            conversation_id: reader.take_u64()?,
681            refused_epoch: reader.take_u64()?,
682            observer_progress: reader.take_u64()?,
683        },
684        PushDiscriminant::ParticipantDelivery => {
685            let conversation_id = reader.take_u64()?;
686            let delivery_seq = reader.take_u64()?;
687            let kind = RecordKind::try_from(reader.take_u16()?)
688                .map_err(|_| decode_error(DecodeClass::InvalidField))?;
689            let record = decode_participant_record(kind, &mut reader)?;
690            ServerPush::ParticipantDelivery(ParticipantDelivery {
691                conversation_id,
692                delivery_seq,
693                record,
694            })
695        }
696    };
697    reader.finish()?;
698    Ok(value)
699}
700
701fn decode_participant_record(
702    kind: RecordKind,
703    reader: &mut Reader<'_>,
704) -> Result<ParticipantRecord, CodecError> {
705    match kind {
706        RecordKind::OrdinaryRecord => Ok(ParticipantRecord::OrdinaryRecord {
707            sender_participant_id: reader.take_u64()?,
708            payload: reader.take_bytes()?,
709        }),
710        RecordKind::Attached => Ok(ParticipantRecord::Attached {
711            affected_participant_id: reader.take_u64()?,
712            binding_epoch: reader.take_binding_epoch()?,
713        }),
714        RecordKind::Detached => {
715            let affected_participant_id = reader.take_u64()?;
716            let binding_epoch = reader.take_binding_epoch()?;
717            let tag = CloseCauseTag::try_from(reader.take_u16()?)
718                .map_err(|_| decode_error(DecodeClass::InvalidField))?;
719            let cause = match tag {
720                CloseCauseTag::CleanDeregister => DetachedCause::CleanDeregister,
721                CloseCauseTag::Superseded => DetachedCause::Superseded,
722                CloseCauseTag::ServerShutdown => DetachedCause::ServerShutdown,
723                _ => return Err(decode_error(DecodeClass::InvalidField)),
724            };
725            Ok(ParticipantRecord::Detached {
726                affected_participant_id,
727                binding_epoch,
728                cause,
729            })
730        }
731        RecordKind::Died => {
732            let affected_participant_id = reader.take_u64()?;
733            let binding_epoch = reader.take_binding_epoch()?;
734            let tag = CloseCauseTag::try_from(reader.take_u16()?)
735                .map_err(|_| decode_error(DecodeClass::InvalidField))?;
736            let cause = match tag {
737                CloseCauseTag::ConnectionLost => DiedCause::ConnectionLost,
738                CloseCauseTag::ProcessKilled => DiedCause::ProcessKilled,
739                CloseCauseTag::ProtocolError => DiedCause::ProtocolError,
740                CloseCauseTag::UncleanServerRestart => DiedCause::UncleanServerRestart {
741                    prior_server_incarnation: reader.take_u64()?,
742                },
743                _ => return Err(decode_error(DecodeClass::InvalidField)),
744            };
745            Ok(ParticipantRecord::Died {
746                affected_participant_id,
747                binding_epoch,
748                cause,
749            })
750        }
751        RecordKind::Left => Ok(ParticipantRecord::Left {
752            affected_participant_id: reader.take_u64()?,
753            ended_binding_epoch: reader.take_option_binding_epoch()?,
754        }),
755        RecordKind::HistoryCompacted => Ok(ParticipantRecord::HistoryCompacted {
756            affected_participant_id: reader.take_u64()?,
757            abandoned_after: reader.take_u64()?,
758            abandoned_through: reader.take_u64()?,
759            physical_floor_at_decision: reader.take_u64()?,
760        }),
761    }
762}
763
764fn put_generation<S: Sink>(sink: &mut S, generation: Generation) -> Result<(), CodecError> {
765    sink.put_u64(generation.get())
766}
767
768fn put_connection_incarnation<S: Sink>(
769    sink: &mut S,
770    value: ConnectionIncarnation,
771) -> Result<(), CodecError> {
772    sink.put_u64(value.server_incarnation)?;
773    sink.put_u64(value.connection_ordinal)
774}
775
776fn put_binding_epoch<S: Sink>(sink: &mut S, value: BindingEpoch) -> Result<(), CodecError> {
777    put_connection_incarnation(sink, value.connection_incarnation)?;
778    put_generation(sink, value.capability_generation)
779}
780
781fn put_option_u64<S: Sink>(sink: &mut S, value: Option<u64>) -> Result<(), CodecError> {
782    match value {
783        None => sink.put_u8(0),
784        Some(value) => {
785            sink.put_u8(1)?;
786            sink.put_u64(value)
787        }
788    }
789}
790
791fn put_option_binding_epoch<S: Sink>(
792    sink: &mut S,
793    value: Option<BindingEpoch>,
794) -> Result<(), CodecError> {
795    match value {
796        None => sink.put_u8(0),
797        Some(value) => {
798            sink.put_u8(1)?;
799            put_binding_epoch(sink, value)
800        }
801    }
802}
803
804const fn decode_error(class: DecodeClass) -> CodecError {
805    CodecError::Decode { class }
806}
807
808trait Sink {
809    fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError>;
810
811    fn put_u8(&mut self, value: u8) -> Result<(), CodecError> {
812        self.put(&[value])
813    }
814
815    fn put_u16(&mut self, value: u16) -> Result<(), CodecError> {
816        self.put(&value.to_be_bytes())
817    }
818
819    fn put_u32(&mut self, value: u32) -> Result<(), CodecError> {
820        self.put(&value.to_be_bytes())
821    }
822
823    fn put_u64(&mut self, value: u64) -> Result<(), CodecError> {
824        self.put(&value.to_be_bytes())
825    }
826
827    fn put_fixed<const N: usize>(&mut self, value: &[u8; N]) -> Result<(), CodecError> {
828        self.put(value)
829    }
830
831    fn put_bytes(&mut self, value: &[u8]) -> Result<(), CodecError> {
832        let length: u32 = value
833            .len()
834            .try_into()
835            .map_err(|_| CodecError::LengthOverflow)?;
836        self.put_u32(length)?;
837        self.put(value)
838    }
839}
840
841#[derive(Default)]
842struct SizeSink {
843    len: usize,
844}
845
846impl Sink for SizeSink {
847    fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
848        self.len = self
849            .len
850            .checked_add(bytes.len())
851            .ok_or(CodecError::LengthOverflow)?;
852        Ok(())
853    }
854}
855
856struct Writer<'a> {
857    output: &'a mut [u8],
858    position: usize,
859}
860
861impl<'a> Writer<'a> {
862    const fn new(output: &'a mut [u8]) -> Self {
863        Self {
864            output,
865            position: 0,
866        }
867    }
868}
869
870impl Sink for Writer<'_> {
871    fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
872        let end = self
873            .position
874            .checked_add(bytes.len())
875            .ok_or(CodecError::LengthOverflow)?;
876        let available = self.output.len();
877        let Some(destination) = self.output.get_mut(self.position..end) else {
878            return Err(CodecError::OutputTooSmall {
879                required: end,
880                available,
881            });
882        };
883        destination.copy_from_slice(bytes);
884        self.position = end;
885        Ok(())
886    }
887}
888
889struct Reader<'a> {
890    input: &'a [u8],
891    position: usize,
892    invalid_field: bool,
893}
894
895impl<'a> Reader<'a> {
896    const fn new(input: &'a [u8]) -> Self {
897        Self {
898            input,
899            position: 0,
900            invalid_field: false,
901        }
902    }
903
904    const fn remaining(&self) -> usize {
905        self.input.len().saturating_sub(self.position)
906    }
907
908    fn remaining_bytes(&self) -> &'a [u8] {
909        match self.input.get(self.position..) {
910            Some(bytes) => bytes,
911            None => &[],
912        }
913    }
914
915    fn take(&mut self, length: usize) -> Result<&'a [u8], CodecError> {
916        let end = self
917            .position
918            .checked_add(length)
919            .ok_or_else(|| decode_error(DecodeClass::MissingRequiredField))?;
920        let Some(bytes) = self.input.get(self.position..end) else {
921            return Err(decode_error(DecodeClass::MissingRequiredField));
922        };
923        self.position = end;
924        Ok(bytes)
925    }
926
927    fn take_fixed<const N: usize>(&mut self) -> Result<[u8; N], CodecError> {
928        let mut output = [0; N];
929        output.copy_from_slice(self.take(N)?);
930        Ok(output)
931    }
932
933    fn take_u8(&mut self) -> Result<u8, CodecError> {
934        Ok(u8::from_be_bytes(self.take_fixed()?))
935    }
936
937    fn take_u16(&mut self) -> Result<u16, CodecError> {
938        Ok(u16::from_be_bytes(self.take_fixed()?))
939    }
940
941    fn take_u32(&mut self) -> Result<u32, CodecError> {
942        Ok(u32::from_be_bytes(self.take_fixed()?))
943    }
944
945    fn take_u64(&mut self) -> Result<u64, CodecError> {
946        Ok(u64::from_be_bytes(self.take_fixed()?))
947    }
948
949    fn take_generation(&mut self) -> Result<Generation, CodecError> {
950        let raw = self.take_u64()?;
951        if let Some(value) = Generation::new(raw) {
952            return Ok(value);
953        }
954        self.invalid_field = true;
955        Generation::new(1).ok_or(decode_error(DecodeClass::InvalidField))
956    }
957
958    fn take_binding_epoch(&mut self) -> Result<BindingEpoch, CodecError> {
959        Ok(BindingEpoch::new(
960            ConnectionIncarnation::new(self.take_u64()?, self.take_u64()?),
961            self.take_generation()?,
962        ))
963    }
964
965    fn take_option_u64(&mut self) -> Result<Option<u64>, CodecError> {
966        match self.take_u8()? {
967            0 => Ok(None),
968            1 => self.take_u64().map(Some),
969            _ => Err(decode_error(DecodeClass::InvalidField)),
970        }
971    }
972
973    fn take_option_binding_epoch(&mut self) -> Result<Option<BindingEpoch>, CodecError> {
974        match self.take_u8()? {
975            0 => Ok(None),
976            1 => self.take_binding_epoch().map(Some),
977            _ => Err(decode_error(DecodeClass::InvalidField)),
978        }
979    }
980
981    fn take_bytes(&mut self) -> Result<Vec<u8>, CodecError> {
982        let length: usize = self
983            .take_u32()?
984            .try_into()
985            .map_err(|_| decode_error(DecodeClass::MissingRequiredField))?;
986        Ok(self.take(length)?.to_vec())
987    }
988
989    fn require_fixed_list(&self, count: u64, width: usize) -> Result<(), CodecError> {
990        let width =
991            u128::try_from(width).map_err(|_| decode_error(DecodeClass::MissingRequiredField))?;
992        let remaining = u128::try_from(self.remaining())
993            .map_err(|_| decode_error(DecodeClass::MissingRequiredField))?;
994        let required = u128::from(count)
995            .checked_mul(width)
996            .ok_or_else(|| decode_error(DecodeClass::MissingRequiredField))?;
997        if required > remaining {
998            return Err(decode_error(DecodeClass::MissingRequiredField));
999        }
1000        Ok(())
1001    }
1002
1003    const fn finish(self) -> Result<(), CodecError> {
1004        if self.position != self.input.len() {
1005            return Err(decode_error(DecodeClass::CanonicalEncoding));
1006        }
1007        if self.invalid_field {
1008            return Err(decode_error(DecodeClass::InvalidField));
1009        }
1010        Ok(())
1011    }
1012}