Skip to main content

gaze_proxy/
codec.rs

1use std::fmt;
2use std::ops::Range;
3
4use gaze::{
5    CommittedSessionSnapshot, PrefixCacheWriteMode, RestoreError, RestoredTextWithProvenance,
6};
7use gaze_types::inspection::{
8    JsonShapeSummaryV1, ProjectionAvailabilityV1, ProjectionOmissionReasonV1, SseTimelineMetaV1,
9};
10
11/// Transport-independent body representation selected by a reviewed adapter contract.
12#[derive(Clone, Copy, Debug, Eq, PartialEq)]
13pub enum WireFormat {
14    Empty,
15    Json,
16    Ndjson,
17    Utf8Text,
18    Sse,
19}
20
21/// Closed phase reported by a codec failure.
22#[derive(Clone, Copy, Debug, Eq, PartialEq)]
23pub enum CodecPhase {
24    RequestParse,
25    RequestProtection,
26    RequestProof,
27    ResponseParse,
28    ResponseRestore,
29    ResponseProof,
30    SseDecode,
31    SseLifecycle,
32    SseReplay,
33}
34
35/// Closed, sanitized codec failure code.
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum CodecErrorCode {
38    InvalidUtf8,
39    InvalidJson,
40    InvalidWireFormat,
41    DuplicateObjectKey,
42    DepthLimitExceeded,
43    NodeLimitExceeded,
44    StringLimitExceeded,
45    NumberLimitExceeded,
46    BodyLimitExceeded,
47    GrowthLimitExceeded,
48    InternalCoverageFailure,
49    UnsupportedRequestSurface,
50    UnsupportedResponseSurface,
51    ControlWouldMutate,
52    OpaqueMediaUninspected,
53    SignedMutationRequired,
54    SignedSurfaceMalformed,
55    ProtectionFailedClosed,
56    RestoreFailedClosed,
57    ProviderOriginNumeric,
58    ProviderOriginPii,
59    TransformedKeyCollision,
60    InvalidSseField,
61    InvalidSseFrame,
62    InvalidSseEventPair,
63    SseLineLimitExceeded,
64    SseFrameLimitExceeded,
65    SseEventLimitExceeded,
66    SseIndexLimitExceeded,
67    SseAccumulatorLimitExceeded,
68    InvalidSseLifecycle,
69    InvalidContentBlockIndex,
70    InvalidContentBlockDelta,
71    IncompleteSseStream,
72    UnsafeFutureEvent,
73    ProviderErrorEvent,
74}
75
76/// Closed carrier schemes that may safely identify an opaque request carrier.
77#[derive(Clone, Copy, Debug, Eq, PartialEq)]
78#[non_exhaustive]
79pub enum OpaqueCarrierScheme {
80    Data,
81    File,
82    Http,
83    Https,
84}
85
86impl OpaqueCarrierScheme {
87    /// Returns the normalized scheme identifier without any carrier payload.
88    #[must_use]
89    pub const fn as_str(self) -> &'static str {
90        match self {
91            Self::Data => "data:",
92            Self::File => "file:",
93            Self::Http => "http:",
94            Self::Https => "https:",
95        }
96    }
97}
98
99/// Structural location of an opaque carrier, never its bytes.
100#[derive(Clone, Copy, Debug, Eq, PartialEq)]
101#[non_exhaustive]
102pub struct OpaqueCarrierLocation {
103    scheme: OpaqueCarrierScheme,
104    byte_offset: usize,
105    byte_length: usize,
106    content_block_index: Option<usize>,
107}
108
109impl OpaqueCarrierLocation {
110    pub(crate) const fn new(
111        scheme: OpaqueCarrierScheme,
112        byte_offset: usize,
113        byte_length: usize,
114        content_block_index: Option<usize>,
115    ) -> Self {
116        Self {
117            scheme,
118            byte_offset,
119            byte_length,
120            content_block_index,
121        }
122    }
123
124    #[must_use]
125    pub const fn scheme(self) -> OpaqueCarrierScheme {
126        self.scheme
127    }
128
129    #[must_use]
130    pub const fn byte_offset(self) -> usize {
131        self.byte_offset
132    }
133
134    #[must_use]
135    pub const fn byte_length(self) -> usize {
136        self.byte_length
137    }
138
139    #[must_use]
140    pub const fn content_block_index(self) -> Option<usize> {
141        self.content_block_index
142    }
143}
144
145/// Sanitized codec failure. It never retains parser, body, key, value, or provider error text.
146#[derive(Clone, Copy, Eq, PartialEq)]
147pub struct CodecError {
148    code: CodecErrorCode,
149    phase: CodecPhase,
150    opaque_carrier: Option<OpaqueCarrierLocation>,
151}
152
153impl CodecError {
154    pub(crate) const fn new(code: CodecErrorCode, phase: CodecPhase) -> Self {
155        Self {
156            code,
157            phase,
158            opaque_carrier: None,
159        }
160    }
161
162    pub(crate) const fn with_opaque_carrier(
163        mut self,
164        opaque_carrier: Option<OpaqueCarrierLocation>,
165    ) -> Self {
166        self.opaque_carrier = opaque_carrier;
167        self
168    }
169
170    #[must_use]
171    pub const fn code(self) -> CodecErrorCode {
172        self.code
173    }
174
175    #[must_use]
176    pub const fn phase(self) -> CodecPhase {
177        self.phase
178    }
179
180    /// Returns only the carrier's structural location, never its bytes.
181    #[must_use]
182    pub const fn opaque_carrier(self) -> Option<OpaqueCarrierLocation> {
183        self.opaque_carrier
184    }
185}
186
187impl fmt::Debug for CodecError {
188    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
189        let mut debug = formatter.debug_struct("CodecError");
190        debug.field("code", &self.code).field("phase", &self.phase);
191        if let Some(opaque_carrier) = self.opaque_carrier {
192            debug.field("opaque_carrier", &opaque_carrier);
193        }
194        debug.finish()
195    }
196}
197
198impl fmt::Display for CodecError {
199    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
200        write!(formatter, "codec_{:?}_{:?}", self.phase, self.code)
201    }
202}
203
204impl std::error::Error for CodecError {}
205
206/// Reviewed finite codec limits. Builders may only lower these defaults in I3.
207#[derive(Clone, Copy, Debug, Eq, PartialEq)]
208pub struct CodecLimits {
209    max_body_bytes: usize,
210    max_json_depth: usize,
211    max_json_nodes: usize,
212    max_string_bytes: usize,
213    max_number_bytes: usize,
214    max_growth_numerator: usize,
215    max_growth_denominator: usize,
216    max_sse_line_bytes: usize,
217    max_sse_frame_bytes: usize,
218    max_sse_events: usize,
219    max_sse_indices: usize,
220    max_sse_accumulator_bytes: usize,
221}
222
223impl Default for CodecLimits {
224    fn default() -> Self {
225        Self {
226            max_body_bytes: 32 * 1024 * 1024,
227            max_json_depth: 128,
228            max_json_nodes: 100_000,
229            max_string_bytes: 4 * 1024 * 1024,
230            max_number_bytes: 128,
231            max_growth_numerator: 2,
232            max_growth_denominator: 1,
233            max_sse_line_bytes: 256 * 1024,
234            max_sse_frame_bytes: 1024 * 1024,
235            max_sse_events: 10_000,
236            max_sse_indices: 256,
237            max_sse_accumulator_bytes: 8 * 1024 * 1024,
238        }
239    }
240}
241
242impl CodecLimits {
243    #[must_use]
244    pub const fn with_max_body_bytes(mut self, value: usize) -> Self {
245        self.max_body_bytes = value;
246        self
247    }
248
249    #[must_use]
250    pub const fn with_max_json_depth(mut self, value: usize) -> Self {
251        self.max_json_depth = value;
252        self
253    }
254
255    #[must_use]
256    pub const fn with_max_json_nodes(mut self, value: usize) -> Self {
257        self.max_json_nodes = value;
258        self
259    }
260
261    #[must_use]
262    pub const fn with_max_string_bytes(mut self, value: usize) -> Self {
263        self.max_string_bytes = value;
264        self
265    }
266
267    #[must_use]
268    pub const fn with_max_number_bytes(mut self, value: usize) -> Self {
269        self.max_number_bytes = value;
270        self
271    }
272
273    #[must_use]
274    pub const fn with_max_sse_line_bytes(mut self, value: usize) -> Self {
275        self.max_sse_line_bytes = value;
276        self
277    }
278
279    #[must_use]
280    pub const fn with_max_sse_frame_bytes(mut self, value: usize) -> Self {
281        self.max_sse_frame_bytes = value;
282        self
283    }
284
285    #[must_use]
286    pub const fn with_max_sse_events(mut self, value: usize) -> Self {
287        self.max_sse_events = value;
288        self
289    }
290
291    #[must_use]
292    pub const fn with_max_sse_indices(mut self, value: usize) -> Self {
293        self.max_sse_indices = value;
294        self
295    }
296
297    #[must_use]
298    pub const fn with_max_sse_accumulator_bytes(mut self, value: usize) -> Self {
299        self.max_sse_accumulator_bytes = value;
300        self
301    }
302
303    #[must_use]
304    pub const fn max_body_bytes(self) -> usize {
305        self.max_body_bytes
306    }
307
308    #[must_use]
309    pub const fn max_json_depth(self) -> usize {
310        self.max_json_depth
311    }
312
313    #[must_use]
314    pub const fn max_json_nodes(self) -> usize {
315        self.max_json_nodes
316    }
317
318    #[must_use]
319    pub const fn max_string_bytes(self) -> usize {
320        self.max_string_bytes
321    }
322
323    #[must_use]
324    pub const fn max_number_bytes(self) -> usize {
325        self.max_number_bytes
326    }
327
328    #[must_use]
329    pub const fn max_sse_line_bytes(self) -> usize {
330        self.max_sse_line_bytes
331    }
332
333    #[must_use]
334    pub const fn max_sse_frame_bytes(self) -> usize {
335        self.max_sse_frame_bytes
336    }
337
338    #[must_use]
339    pub const fn max_sse_events(self) -> usize {
340        self.max_sse_events
341    }
342
343    #[must_use]
344    pub const fn max_sse_indices(self) -> usize {
345        self.max_sse_indices
346    }
347
348    #[must_use]
349    pub const fn max_sse_accumulator_bytes(self) -> usize {
350        self.max_sse_accumulator_bytes
351    }
352
353    pub(crate) fn output_within_growth_limit(self, input: usize, output: usize) -> bool {
354        let proportional = input
355            .checked_mul(self.max_growth_numerator)
356            .map(|value| value / self.max_growth_denominator)
357            .unwrap_or(usize::MAX);
358        output <= self.max_body_bytes && output <= proportional.max(input)
359    }
360}
361
362/// Staged request protection boundary supplied by I4.
363pub trait RequestPseudonymizer {
364    /// Protects one complete decoded logical value against the staged transaction.
365    fn protect(&mut self, input: &str) -> Result<String, CodecErrorCode>;
366
367    /// Protects one decoded value with an explicit prefix-cache mode.
368    ///
369    /// [`PrefixCacheWriteMode::Allow`] preserves the source-compatible delegation to
370    /// [`Self::protect`]. [`PrefixCacheWriteMode::Suppress`] fails closed unless an
371    /// implementation explicitly overrides this method with a stateless or isolated
372    /// no-read/no-write guarantee.
373    fn protect_with_prefix_cache_write_mode(
374        &mut self,
375        input: &str,
376        mode: PrefixCacheWriteMode,
377    ) -> Result<String, CodecErrorCode> {
378        match mode {
379            PrefixCacheWriteMode::Allow => self.protect(input),
380            PrefixCacheWriteMode::Suppress => Err(CodecErrorCode::ProtectionFailedClosed),
381        }
382    }
383
384    /// Proves that every Gaze-shaped token belongs to the staged transaction.
385    fn validate_token_shapes(&mut self, input: &str) -> Result<(), CodecErrorCode>;
386}
387
388/// Residual response proof boundary supplied by I4.
389pub trait ResponseResidualValidator {
390    /// Validates one complete restored logical value. Every finding must fit wholly in an
391    /// authorized manifest-backed output range.
392    fn validate(
393        &mut self,
394        value: &str,
395        authorized_output_ranges: &[Range<usize>],
396    ) -> Result<(), CodecErrorCode>;
397}
398
399/// Private-field request transformation context.
400pub struct RequestTransformContext<'a> {
401    pseudonymizer: &'a mut dyn RequestPseudonymizer,
402    format: WireFormat,
403    limits: CodecLimits,
404    inspection_projection_requested: bool,
405    content_block_index: Option<usize>,
406    opaque_carrier: Option<OpaqueCarrierLocation>,
407}
408
409impl<'a> RequestTransformContext<'a> {
410    #[must_use]
411    pub(crate) fn new(
412        pseudonymizer: &'a mut dyn RequestPseudonymizer,
413        format: WireFormat,
414        limits: CodecLimits,
415    ) -> Self {
416        Self {
417            pseudonymizer,
418            format,
419            limits,
420            inspection_projection_requested: false,
421            content_block_index: None,
422            opaque_carrier: None,
423        }
424    }
425
426    #[must_use]
427    pub const fn format(&self) -> WireFormat {
428        self.format
429    }
430
431    #[must_use]
432    pub const fn limits(&self) -> CodecLimits {
433        self.limits
434    }
435
436    pub(crate) fn request_inspection_projection(&mut self) {
437        self.inspection_projection_requested = true;
438    }
439
440    pub(crate) const fn inspection_projection_requested(&self) -> bool {
441        self.inspection_projection_requested
442    }
443
444    pub(crate) fn replace_content_block_index(
445        &mut self,
446        content_block_index: Option<usize>,
447    ) -> Option<usize> {
448        std::mem::replace(&mut self.content_block_index, content_block_index)
449    }
450
451    pub(crate) fn record_opaque_carrier(
452        &mut self,
453        scheme: OpaqueCarrierScheme,
454        byte_offset: usize,
455        byte_length: usize,
456    ) {
457        self.opaque_carrier.get_or_insert_with(|| {
458            OpaqueCarrierLocation::new(scheme, byte_offset, byte_length, self.content_block_index)
459        });
460    }
461
462    pub(crate) fn clear_opaque_carrier(&mut self) {
463        self.opaque_carrier = None;
464    }
465
466    pub(crate) const fn opaque_carrier(&self) -> Option<OpaqueCarrierLocation> {
467        self.opaque_carrier
468    }
469
470    pub(crate) fn protect(&mut self, input: &str) -> Result<String, CodecErrorCode> {
471        self.pseudonymizer
472            .protect_with_prefix_cache_write_mode(input, PrefixCacheWriteMode::Allow)
473    }
474
475    /// Runs an isolated mutation probe that may neither reuse nor publish prefix-cache state.
476    pub(crate) fn probe_without_prefix_cache_write(
477        &mut self,
478        input: &str,
479    ) -> Result<String, CodecErrorCode> {
480        self.pseudonymizer
481            .protect_with_prefix_cache_write_mode(input, PrefixCacheWriteMode::Suppress)
482    }
483
484    pub(crate) fn validate_token_shapes(&mut self, input: &str) -> Result<(), CodecErrorCode> {
485        self.pseudonymizer.validate_token_shapes(input)
486    }
487}
488
489/// Private-field response transformation context.
490pub struct ResponseTransformContext<'a> {
491    snapshot: &'a CommittedSessionSnapshot,
492    residual: &'a mut dyn ResponseResidualValidator,
493    format: WireFormat,
494    limits: CodecLimits,
495    inspection_projection_requested: bool,
496}
497
498impl<'a> ResponseTransformContext<'a> {
499    #[must_use]
500    pub(crate) fn new(
501        snapshot: &'a CommittedSessionSnapshot,
502        residual: &'a mut dyn ResponseResidualValidator,
503        format: WireFormat,
504        limits: CodecLimits,
505    ) -> Self {
506        Self {
507            snapshot,
508            residual,
509            format,
510            limits,
511            inspection_projection_requested: false,
512        }
513    }
514
515    #[must_use]
516    pub const fn format(&self) -> WireFormat {
517        self.format
518    }
519
520    #[must_use]
521    pub const fn limits(&self) -> CodecLimits {
522        self.limits
523    }
524
525    pub(crate) fn request_inspection_projection(&mut self) {
526        self.inspection_projection_requested = true;
527    }
528
529    pub(crate) const fn inspection_projection_requested(&self) -> bool {
530        self.inspection_projection_requested
531    }
532
533    pub(crate) fn restore(
534        &mut self,
535        input: &str,
536    ) -> Result<RestoredTextWithProvenance, CodecErrorCode> {
537        self.snapshot
538            .restore_strict_text_with_provenance(input)
539            .map_err(map_restore_error)
540    }
541
542    pub(crate) fn validate(
543        &mut self,
544        value: &str,
545        authorized_output_ranges: &[Range<usize>],
546    ) -> Result<(), CodecErrorCode> {
547        self.residual.validate(value, authorized_output_ranges)
548    }
549}
550
551fn map_restore_error(_error: RestoreError) -> CodecErrorCode {
552    CodecErrorCode::RestoreFailedClosed
553}
554
555/// Output-coordinate proof retained beside the exact emitted buffer.
556#[derive(Clone, Default, Eq, PartialEq)]
557pub struct OutputProvenance {
558    authorized_output_ranges: Vec<Range<usize>>,
559    signed_opaque_ranges: Vec<Range<usize>>,
560    occurrence_count: usize,
561    final_buffer_verified: bool,
562}
563
564impl OutputProvenance {
565    #[must_use]
566    pub fn authorized_output_ranges(&self) -> &[Range<usize>] {
567        &self.authorized_output_ranges
568    }
569
570    #[must_use]
571    pub fn signed_opaque_ranges(&self) -> &[Range<usize>] {
572        &self.signed_opaque_ranges
573    }
574
575    #[must_use]
576    pub const fn occurrence_count(&self) -> usize {
577        self.occurrence_count
578    }
579
580    #[must_use]
581    pub const fn final_buffer_verified(&self) -> bool {
582        self.final_buffer_verified
583    }
584
585    pub(crate) fn record_authorized(&mut self, range: Range<usize>) {
586        push_coalesced(&mut self.authorized_output_ranges, range);
587    }
588
589    pub(crate) fn record_signed(&mut self, range: Range<usize>) {
590        push_coalesced(&mut self.signed_opaque_ranges, range);
591    }
592
593    pub(crate) fn set_occurrence_count(&mut self, count: usize) {
594        self.occurrence_count = count;
595    }
596
597    pub(crate) fn mark_final_buffer_verified(&mut self) {
598        self.final_buffer_verified = true;
599    }
600}
601
602fn push_coalesced(ranges: &mut Vec<Range<usize>>, range: Range<usize>) {
603    if range.start >= range.end {
604        return;
605    }
606    if let Some(previous) = ranges.last_mut() {
607        if range.start <= previous.end {
608            previous.end = previous.end.max(range.end);
609            return;
610        }
611    }
612    ranges.push(range);
613}
614
615/// Crate-private structural projection created lazily from codec-owned proved structure.
616pub(crate) struct InspectionStructuralProjectionV1 {
617    pub(crate) json_shape: ProjectionAvailabilityV1<JsonShapeSummaryV1>,
618    pub(crate) sse_timeline: ProjectionAvailabilityV1<SseTimelineMetaV1>,
619}
620
621impl InspectionStructuralProjectionV1 {
622    pub(crate) const fn unsupported() -> Self {
623        Self {
624            json_shape: ProjectionAvailabilityV1::Omitted(
625                ProjectionOmissionReasonV1::UnsupportedFormat,
626            ),
627            sse_timeline: ProjectionAvailabilityV1::Omitted(
628                ProjectionOmissionReasonV1::UnsupportedFormat,
629            ),
630        }
631    }
632
633    pub(crate) const fn unavailable(format: WireFormat) -> Self {
634        match format {
635            WireFormat::Json => Self {
636                json_shape: ProjectionAvailabilityV1::Omitted(
637                    ProjectionOmissionReasonV1::ProjectionFailedClosed,
638                ),
639                sse_timeline: ProjectionAvailabilityV1::Omitted(
640                    ProjectionOmissionReasonV1::UnsupportedFormat,
641                ),
642            },
643            WireFormat::Sse => Self {
644                json_shape: ProjectionAvailabilityV1::Omitted(
645                    ProjectionOmissionReasonV1::UnsupportedFormat,
646                ),
647                sse_timeline: ProjectionAvailabilityV1::Omitted(
648                    ProjectionOmissionReasonV1::ProjectionFailedClosed,
649                ),
650            },
651            WireFormat::Ndjson | WireFormat::Utf8Text | WireFormat::Empty => Self {
652                json_shape: ProjectionAvailabilityV1::Omitted(
653                    ProjectionOmissionReasonV1::UnsupportedFormat,
654                ),
655                sse_timeline: ProjectionAvailabilityV1::Omitted(
656                    ProjectionOmissionReasonV1::UnsupportedFormat,
657                ),
658            },
659        }
660    }
661}
662
663/// Opaque crate-private parsed facts moved from the codec's existing proof pass.
664///
665/// These facts are retained only for an immutable startup-selected stage. Turning them into an
666/// inspection source/timeline remains inside the `gaze-inspection` build closure, after its real
667/// lifecycle pre-projection fence. Proved bytes are never reparsed by the proxy.
668pub(crate) enum InspectionParsedFactsV1 {
669    AnthropicJson(crate::anthropic::JsonInspectionParsedFactsV1),
670    AnthropicSse(crate::anthropic::SseInspectionParsedFactsV1),
671}
672
673impl InspectionParsedFactsV1 {
674    pub(crate) fn project(self) -> InspectionStructuralProjectionV1 {
675        match self {
676            Self::AnthropicJson(facts) => facts.project(),
677            Self::AnthropicSse(facts) => facts.project(),
678        }
679    }
680}
681
682macro_rules! proved_body {
683    ($name:ident) => {
684        pub struct $name {
685            bytes: Vec<u8>,
686            format: WireFormat,
687            provenance: OutputProvenance,
688            inspection_parsed_facts: Option<InspectionParsedFactsV1>,
689            signed_or_encrypted_surface: bool,
690        }
691
692        impl $name {
693            pub(crate) fn new(
694                bytes: Vec<u8>,
695                format: WireFormat,
696                provenance: OutputProvenance,
697            ) -> Self {
698                let signed_or_encrypted_surface = !provenance.signed_opaque_ranges().is_empty();
699                Self {
700                    bytes,
701                    format,
702                    provenance,
703                    inspection_parsed_facts: None,
704                    signed_or_encrypted_surface,
705                }
706            }
707
708            pub(crate) fn new_with_inspection_parsed_facts(
709                bytes: Vec<u8>,
710                format: WireFormat,
711                provenance: OutputProvenance,
712                inspection_parsed_facts: InspectionParsedFactsV1,
713            ) -> Self {
714                let signed_or_encrypted_surface = !provenance.signed_opaque_ranges().is_empty();
715                Self {
716                    bytes,
717                    format,
718                    provenance,
719                    inspection_parsed_facts: (!signed_or_encrypted_surface)
720                        .then_some(inspection_parsed_facts),
721                    signed_or_encrypted_surface,
722                }
723            }
724
725            #[must_use]
726            pub fn bytes(&self) -> &[u8] {
727                &self.bytes
728            }
729
730            #[must_use]
731            pub const fn format(&self) -> WireFormat {
732                self.format
733            }
734
735            #[must_use]
736            pub const fn provenance(&self) -> &OutputProvenance {
737                &self.provenance
738            }
739
740            pub(crate) const fn signed_or_encrypted_surface(&self) -> bool {
741                self.signed_or_encrypted_surface
742            }
743
744            pub(crate) fn into_inspection_parts(
745                self,
746            ) -> (Vec<u8>, Option<InspectionParsedFactsV1>, bool) {
747                (
748                    self.bytes,
749                    self.inspection_parsed_facts,
750                    self.signed_or_encrypted_surface,
751                )
752            }
753
754            pub(crate) fn take_inspection_parsed_facts(
755                &mut self,
756            ) -> Option<InspectionParsedFactsV1> {
757                self.inspection_parsed_facts.take()
758            }
759
760            #[must_use]
761            pub fn into_bytes(self) -> Vec<u8> {
762                self.bytes
763            }
764        }
765
766        impl fmt::Debug for $name {
767            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
768                formatter.write_str(stringify!($name))
769            }
770        }
771    };
772}
773
774proved_body!(ProvedRequestBody);
775proved_body!(ProvedResponseBody);
776
777/// Object-safe, synchronous, transport-free body codec.
778pub trait BodyCodec: Send + Sync + 'static {
779    fn protect_request(
780        &self,
781        input: &[u8],
782        ctx: &mut RequestTransformContext<'_>,
783    ) -> Result<ProvedRequestBody, CodecError>;
784
785    fn restore_response(
786        &self,
787        input: &[u8],
788        ctx: &mut ResponseTransformContext<'_>,
789    ) -> Result<ProvedResponseBody, CodecError>;
790}