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, SettlementQuery, 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::SettlementQuery(value) => {
509            sink.put_u64(value.conversation_id)?;
510            sink.put_u64(value.participant_id)?;
511            put_generation(sink, value.capability_generation)?;
512            sink.put_fixed(value.record_admission_attempt_token.as_bytes())?;
513            sink.put_fixed(&value.payload_fingerprint)
514        }
515        ClientRequest::ObserverRecovery(value) => {
516            let count: u64 = value
517                .observer_refusals
518                .len()
519                .try_into()
520                .map_err(|_| CodecError::LengthOverflow)?;
521            sink.put_u64(count)?;
522            for refusal in &value.observer_refusals {
523                sink.put_u64(refusal.conversation_id)?;
524                sink.put_u64(refusal.refused_epoch)?;
525            }
526            Ok(())
527        }
528    }
529}
530
531fn decode_client_request(
532    tag: ClientDiscriminant,
533    body: &[u8],
534) -> Result<ClientRequest, CodecError> {
535    let mut reader = Reader::new(body);
536    let value = match tag {
537        ClientDiscriminant::EnrollmentRequest => ClientRequest::Enrollment(EnrollmentRequest {
538            conversation_id: reader.take_u64()?,
539            enrollment_token: EnrollmentToken::new(reader.take_fixed()?),
540        }),
541        ClientDiscriminant::CredentialAttachRequest => {
542            ClientRequest::CredentialAttach(CredentialAttachRequest {
543                conversation_id: reader.take_u64()?,
544                participant_id: reader.take_u64()?,
545                capability_generation: reader.take_generation()?,
546                attach_secret: AttachSecret::new(reader.take_fixed()?),
547                attach_attempt_token: AttachAttemptToken::new(reader.take_fixed()?),
548                accept_marker_delivery_seq: reader.take_option_u64()?,
549            })
550        }
551        ClientDiscriminant::DetachRequest => ClientRequest::Detach(DetachRequest {
552            conversation_id: reader.take_u64()?,
553            participant_id: reader.take_u64()?,
554            capability_generation: reader.take_generation()?,
555            detach_attempt_token: DetachAttemptToken::new(reader.take_fixed()?),
556        }),
557        ClientDiscriminant::ParticipantAck => ClientRequest::ParticipantAck(ParticipantAck {
558            conversation_id: reader.take_u64()?,
559            participant_id: reader.take_u64()?,
560            capability_generation: reader.take_generation()?,
561            through_seq: reader.take_u64()?,
562        }),
563        ClientDiscriminant::LeaveRequest => ClientRequest::Leave(LeaveRequest {
564            conversation_id: reader.take_u64()?,
565            participant_id: reader.take_u64()?,
566            capability_generation: reader.take_generation()?,
567            attach_secret: AttachSecret::new(reader.take_fixed()?),
568            leave_attempt_token: LeaveAttemptToken::new(reader.take_fixed()?),
569        }),
570        ClientDiscriminant::MarkerAck => ClientRequest::MarkerAck(MarkerAck {
571            conversation_id: reader.take_u64()?,
572            participant_id: reader.take_u64()?,
573            capability_generation: reader.take_generation()?,
574            marker_delivery_seq: reader.take_u64()?,
575        }),
576        ClientDiscriminant::RecordAdmission => ClientRequest::RecordAdmission(RecordAdmission {
577            conversation_id: reader.take_u64()?,
578            participant_id: reader.take_u64()?,
579            capability_generation: reader.take_generation()?,
580            record_admission_attempt_token: RecordAdmissionAttemptToken::new(reader.take_fixed()?),
581            payload: reader.take_bytes()?,
582        }),
583        ClientDiscriminant::SettlementQuery => ClientRequest::SettlementQuery(SettlementQuery {
584            conversation_id: reader.take_u64()?,
585            participant_id: reader.take_u64()?,
586            capability_generation: reader.take_generation()?,
587            record_admission_attempt_token: RecordAdmissionAttemptToken::new(reader.take_fixed()?),
588            payload_fingerprint: reader.take_fixed()?,
589        }),
590        ClientDiscriminant::ObserverRecoveryHandshake => {
591            let count = reader.take_u64()?;
592            reader.require_fixed_list(count, 16)?;
593            let mut observer_refusals = Vec::new();
594            for _ in 0..count {
595                observer_refusals.push(ObserverRefusal {
596                    conversation_id: reader.take_u64()?,
597                    refused_epoch: reader.take_u64()?,
598                });
599            }
600            ClientRequest::ObserverRecovery(ObserverRecoveryHandshake { observer_refusals })
601        }
602    };
603    reader.finish()?;
604    Ok(value)
605}
606
607fn encode_server_push<S: Sink>(value: &ServerPush, sink: &mut S) -> Result<(), CodecError> {
608    match value {
609        ServerPush::ObserverProgressed {
610            conversation_id,
611            refused_epoch,
612            observer_progress,
613        } => {
614            sink.put_u64(*conversation_id)?;
615            sink.put_u64(*refused_epoch)?;
616            sink.put_u64(*observer_progress)
617        }
618        ServerPush::ParticipantDelivery(value) => encode_participant_delivery(value, sink),
619        ServerPush::MarkerSettled {
620            conversation_id,
621            refused_epoch,
622        } => {
623            sink.put_u64(*conversation_id)?;
624            sink.put_u64(*refused_epoch)
625        }
626    }
627}
628
629fn encode_participant_delivery<S: Sink>(
630    value: &ParticipantDelivery,
631    sink: &mut S,
632) -> Result<(), CodecError> {
633    sink.put_u64(value.conversation_id)?;
634    sink.put_u64(value.delivery_seq)?;
635    sink.put_u16(value.record.record_kind().wire_value())?;
636    match &value.record {
637        ParticipantRecord::OrdinaryRecord {
638            sender_participant_id,
639            payload,
640        } => {
641            sink.put_u64(*sender_participant_id)?;
642            sink.put_bytes(payload)
643        }
644        ParticipantRecord::Attached {
645            affected_participant_id,
646            binding_epoch,
647        } => {
648            sink.put_u64(*affected_participant_id)?;
649            put_binding_epoch(sink, *binding_epoch)
650        }
651        ParticipantRecord::Detached {
652            affected_participant_id,
653            binding_epoch,
654            cause,
655        } => {
656            sink.put_u64(*affected_participant_id)?;
657            put_binding_epoch(sink, *binding_epoch)?;
658            sink.put_u16(cause.close_cause().tag().wire_value())
659        }
660        ParticipantRecord::Died {
661            affected_participant_id,
662            binding_epoch,
663            cause,
664        } => {
665            sink.put_u64(*affected_participant_id)?;
666            put_binding_epoch(sink, *binding_epoch)?;
667            sink.put_u16(cause.close_cause().tag().wire_value())?;
668            if let DiedCause::UncleanServerRestart {
669                prior_server_incarnation,
670            } = cause
671            {
672                sink.put_u64(*prior_server_incarnation)?;
673            }
674            Ok(())
675        }
676        ParticipantRecord::Left {
677            affected_participant_id,
678            ended_binding_epoch,
679        } => {
680            sink.put_u64(*affected_participant_id)?;
681            put_option_binding_epoch(sink, *ended_binding_epoch)
682        }
683        ParticipantRecord::HistoryCompacted {
684            affected_participant_id,
685            abandoned_after,
686            abandoned_through,
687            physical_floor_at_decision,
688        } => {
689            sink.put_u64(*affected_participant_id)?;
690            sink.put_u64(*abandoned_after)?;
691            sink.put_u64(*abandoned_through)?;
692            sink.put_u64(*physical_floor_at_decision)
693        }
694    }
695}
696
697fn decode_server_push(tag: PushDiscriminant, body: &[u8]) -> Result<ServerPush, CodecError> {
698    let mut reader = Reader::new(body);
699    let value = match tag {
700        PushDiscriminant::ObserverProgressed => ServerPush::ObserverProgressed {
701            conversation_id: reader.take_u64()?,
702            refused_epoch: reader.take_u64()?,
703            observer_progress: reader.take_u64()?,
704        },
705        PushDiscriminant::ParticipantDelivery => {
706            let conversation_id = reader.take_u64()?;
707            let delivery_seq = reader.take_u64()?;
708            let kind = RecordKind::try_from(reader.take_u16()?)
709                .map_err(|_| decode_error(DecodeClass::InvalidField))?;
710            let record = decode_participant_record(kind, &mut reader)?;
711            ServerPush::ParticipantDelivery(ParticipantDelivery {
712                conversation_id,
713                delivery_seq,
714                record,
715            })
716        }
717        PushDiscriminant::MarkerSettled => ServerPush::MarkerSettled {
718            conversation_id: reader.take_u64()?,
719            refused_epoch: reader.take_u64()?,
720        },
721    };
722    reader.finish()?;
723    Ok(value)
724}
725
726fn decode_participant_record(
727    kind: RecordKind,
728    reader: &mut Reader<'_>,
729) -> Result<ParticipantRecord, CodecError> {
730    match kind {
731        RecordKind::OrdinaryRecord => Ok(ParticipantRecord::OrdinaryRecord {
732            sender_participant_id: reader.take_u64()?,
733            payload: reader.take_bytes()?,
734        }),
735        RecordKind::Attached => Ok(ParticipantRecord::Attached {
736            affected_participant_id: reader.take_u64()?,
737            binding_epoch: reader.take_binding_epoch()?,
738        }),
739        RecordKind::Detached => {
740            let affected_participant_id = reader.take_u64()?;
741            let binding_epoch = reader.take_binding_epoch()?;
742            let tag = CloseCauseTag::try_from(reader.take_u16()?)
743                .map_err(|_| decode_error(DecodeClass::InvalidField))?;
744            let cause = match tag {
745                CloseCauseTag::CleanDeregister => DetachedCause::CleanDeregister,
746                CloseCauseTag::Superseded => DetachedCause::Superseded,
747                CloseCauseTag::ServerShutdown => DetachedCause::ServerShutdown,
748                _ => return Err(decode_error(DecodeClass::InvalidField)),
749            };
750            Ok(ParticipantRecord::Detached {
751                affected_participant_id,
752                binding_epoch,
753                cause,
754            })
755        }
756        RecordKind::Died => {
757            let affected_participant_id = reader.take_u64()?;
758            let binding_epoch = reader.take_binding_epoch()?;
759            let tag = CloseCauseTag::try_from(reader.take_u16()?)
760                .map_err(|_| decode_error(DecodeClass::InvalidField))?;
761            let cause = match tag {
762                CloseCauseTag::ConnectionLost => DiedCause::ConnectionLost,
763                CloseCauseTag::ProcessKilled => DiedCause::ProcessKilled,
764                CloseCauseTag::ProtocolError => DiedCause::ProtocolError,
765                CloseCauseTag::UncleanServerRestart => DiedCause::UncleanServerRestart {
766                    prior_server_incarnation: reader.take_u64()?,
767                },
768                _ => return Err(decode_error(DecodeClass::InvalidField)),
769            };
770            Ok(ParticipantRecord::Died {
771                affected_participant_id,
772                binding_epoch,
773                cause,
774            })
775        }
776        RecordKind::Left => Ok(ParticipantRecord::Left {
777            affected_participant_id: reader.take_u64()?,
778            ended_binding_epoch: reader.take_option_binding_epoch()?,
779        }),
780        RecordKind::HistoryCompacted => Ok(ParticipantRecord::HistoryCompacted {
781            affected_participant_id: reader.take_u64()?,
782            abandoned_after: reader.take_u64()?,
783            abandoned_through: reader.take_u64()?,
784            physical_floor_at_decision: reader.take_u64()?,
785        }),
786    }
787}
788
789fn put_generation<S: Sink>(sink: &mut S, generation: Generation) -> Result<(), CodecError> {
790    sink.put_u64(generation.get())
791}
792
793fn put_connection_incarnation<S: Sink>(
794    sink: &mut S,
795    value: ConnectionIncarnation,
796) -> Result<(), CodecError> {
797    sink.put_u64(value.server_incarnation)?;
798    sink.put_u64(value.connection_ordinal)
799}
800
801fn put_binding_epoch<S: Sink>(sink: &mut S, value: BindingEpoch) -> Result<(), CodecError> {
802    put_connection_incarnation(sink, value.connection_incarnation)?;
803    put_generation(sink, value.capability_generation)
804}
805
806fn put_option_u64<S: Sink>(sink: &mut S, value: Option<u64>) -> Result<(), CodecError> {
807    match value {
808        None => sink.put_u8(0),
809        Some(value) => {
810            sink.put_u8(1)?;
811            sink.put_u64(value)
812        }
813    }
814}
815
816fn put_option_binding_epoch<S: Sink>(
817    sink: &mut S,
818    value: Option<BindingEpoch>,
819) -> Result<(), CodecError> {
820    match value {
821        None => sink.put_u8(0),
822        Some(value) => {
823            sink.put_u8(1)?;
824            put_binding_epoch(sink, value)
825        }
826    }
827}
828
829const fn decode_error(class: DecodeClass) -> CodecError {
830    CodecError::Decode { class }
831}
832
833trait Sink {
834    fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError>;
835
836    fn put_u8(&mut self, value: u8) -> Result<(), CodecError> {
837        self.put(&[value])
838    }
839
840    fn put_u16(&mut self, value: u16) -> Result<(), CodecError> {
841        self.put(&value.to_be_bytes())
842    }
843
844    fn put_u32(&mut self, value: u32) -> Result<(), CodecError> {
845        self.put(&value.to_be_bytes())
846    }
847
848    fn put_u64(&mut self, value: u64) -> Result<(), CodecError> {
849        self.put(&value.to_be_bytes())
850    }
851
852    fn put_fixed<const N: usize>(&mut self, value: &[u8; N]) -> Result<(), CodecError> {
853        self.put(value)
854    }
855
856    fn put_bytes(&mut self, value: &[u8]) -> Result<(), CodecError> {
857        let length: u32 = value
858            .len()
859            .try_into()
860            .map_err(|_| CodecError::LengthOverflow)?;
861        self.put_u32(length)?;
862        self.put(value)
863    }
864}
865
866#[derive(Default)]
867struct SizeSink {
868    len: usize,
869}
870
871impl Sink for SizeSink {
872    fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
873        self.len = self
874            .len
875            .checked_add(bytes.len())
876            .ok_or(CodecError::LengthOverflow)?;
877        Ok(())
878    }
879}
880
881struct Writer<'a> {
882    output: &'a mut [u8],
883    position: usize,
884}
885
886impl<'a> Writer<'a> {
887    const fn new(output: &'a mut [u8]) -> Self {
888        Self {
889            output,
890            position: 0,
891        }
892    }
893}
894
895impl Sink for Writer<'_> {
896    fn put(&mut self, bytes: &[u8]) -> Result<(), CodecError> {
897        let end = self
898            .position
899            .checked_add(bytes.len())
900            .ok_or(CodecError::LengthOverflow)?;
901        let available = self.output.len();
902        let Some(destination) = self.output.get_mut(self.position..end) else {
903            return Err(CodecError::OutputTooSmall {
904                required: end,
905                available,
906            });
907        };
908        destination.copy_from_slice(bytes);
909        self.position = end;
910        Ok(())
911    }
912}
913
914struct Reader<'a> {
915    input: &'a [u8],
916    position: usize,
917    invalid_field: bool,
918}
919
920impl<'a> Reader<'a> {
921    const fn new(input: &'a [u8]) -> Self {
922        Self {
923            input,
924            position: 0,
925            invalid_field: false,
926        }
927    }
928
929    const fn remaining(&self) -> usize {
930        self.input.len().saturating_sub(self.position)
931    }
932
933    fn remaining_bytes(&self) -> &'a [u8] {
934        match self.input.get(self.position..) {
935            Some(bytes) => bytes,
936            None => &[],
937        }
938    }
939
940    fn take(&mut self, length: usize) -> Result<&'a [u8], CodecError> {
941        let end = self
942            .position
943            .checked_add(length)
944            .ok_or_else(|| decode_error(DecodeClass::MissingRequiredField))?;
945        let Some(bytes) = self.input.get(self.position..end) else {
946            return Err(decode_error(DecodeClass::MissingRequiredField));
947        };
948        self.position = end;
949        Ok(bytes)
950    }
951
952    fn take_fixed<const N: usize>(&mut self) -> Result<[u8; N], CodecError> {
953        let mut output = [0; N];
954        output.copy_from_slice(self.take(N)?);
955        Ok(output)
956    }
957
958    fn take_u8(&mut self) -> Result<u8, CodecError> {
959        Ok(u8::from_be_bytes(self.take_fixed()?))
960    }
961
962    fn take_u16(&mut self) -> Result<u16, CodecError> {
963        Ok(u16::from_be_bytes(self.take_fixed()?))
964    }
965
966    fn take_u32(&mut self) -> Result<u32, CodecError> {
967        Ok(u32::from_be_bytes(self.take_fixed()?))
968    }
969
970    fn take_u64(&mut self) -> Result<u64, CodecError> {
971        Ok(u64::from_be_bytes(self.take_fixed()?))
972    }
973
974    fn take_generation(&mut self) -> Result<Generation, CodecError> {
975        let raw = self.take_u64()?;
976        if let Some(value) = Generation::new(raw) {
977            return Ok(value);
978        }
979        self.invalid_field = true;
980        Generation::new(1).ok_or(decode_error(DecodeClass::InvalidField))
981    }
982
983    fn take_binding_epoch(&mut self) -> Result<BindingEpoch, CodecError> {
984        Ok(BindingEpoch::new(
985            ConnectionIncarnation::new(self.take_u64()?, self.take_u64()?),
986            self.take_generation()?,
987        ))
988    }
989
990    fn take_option_u64(&mut self) -> Result<Option<u64>, CodecError> {
991        match self.take_u8()? {
992            0 => Ok(None),
993            1 => self.take_u64().map(Some),
994            _ => Err(decode_error(DecodeClass::InvalidField)),
995        }
996    }
997
998    fn take_option_binding_epoch(&mut self) -> Result<Option<BindingEpoch>, CodecError> {
999        match self.take_u8()? {
1000            0 => Ok(None),
1001            1 => self.take_binding_epoch().map(Some),
1002            _ => Err(decode_error(DecodeClass::InvalidField)),
1003        }
1004    }
1005
1006    fn take_bytes(&mut self) -> Result<Vec<u8>, CodecError> {
1007        let length: usize = self
1008            .take_u32()?
1009            .try_into()
1010            .map_err(|_| decode_error(DecodeClass::MissingRequiredField))?;
1011        Ok(self.take(length)?.to_vec())
1012    }
1013
1014    fn require_fixed_list(&self, count: u64, width: usize) -> Result<(), CodecError> {
1015        let width =
1016            u128::try_from(width).map_err(|_| decode_error(DecodeClass::MissingRequiredField))?;
1017        let remaining = u128::try_from(self.remaining())
1018            .map_err(|_| decode_error(DecodeClass::MissingRequiredField))?;
1019        let required = u128::from(count)
1020            .checked_mul(width)
1021            .ok_or_else(|| decode_error(DecodeClass::MissingRequiredField))?;
1022        if required > remaining {
1023            return Err(decode_error(DecodeClass::MissingRequiredField));
1024        }
1025        Ok(())
1026    }
1027
1028    const fn finish(self) -> Result<(), CodecError> {
1029        if self.position != self.input.len() {
1030            return Err(decode_error(DecodeClass::CanonicalEncoding));
1031        }
1032        if self.invalid_field {
1033            return Err(decode_error(DecodeClass::InvalidField));
1034        }
1035        Ok(())
1036    }
1037}