Skip to main content

hpke_http/
engine.rs

1//! Typed one-shot client and staged server transactions.
2
3use std::{
4    fmt,
5    time::{SystemTime, UNIX_EPOCH},
6};
7
8use hkdf::Hkdf;
9use hpke::{
10    Deserializable, Kem as KemTrait, OpModeR, OpModeS, PskBundle, Serializable,
11    aead::{AeadCtxR, AeadCtxS, AeadTag, ChaCha20Poly1305 as HpkeChaCha20Poly1305},
12    inout::InOutBuf,
13    kdf::HkdfSha256,
14    kem::X25519HkdfSha256,
15    setup_receiver, setup_sender_with_rng,
16};
17use rand_chacha::ChaCha20Rng;
18use rand_core::SeedableRng;
19use sha2::{Digest, Sha256};
20use zeroize::Zeroizing;
21
22use crate::{
23    CLOCK_SKEW_SECS, EntropySource, Error, HARD_MAX_ID_LEN, Limits, Method, REQUEST_LIFETIME_SECS,
24    SystemEntropy,
25    codec::{
26        ENC_LEN, REQUEST_FIXED_LEN, RESPONSE_NONCE_LEN, TAG_LEN, encode_request_header,
27        parse_stream_start, stream_start_length,
28    },
29    compression,
30    message::{self, HeaderField, Request, RequestHead, Response},
31    response::{ResponseMode, ResponseOpener, ResponseRecord, ResponseSealer},
32};
33
34type Kem = X25519HkdfSha256;
35type Kdf = HkdfSha256;
36type HpkeAead = HpkeChaCha20Poly1305;
37type RecipientContext = AeadCtxR<HpkeAead, Kdf, Kem>;
38type SenderContext = AeadCtxS<HpkeAead, Kdf, Kem>;
39
40const STREAM_REQUEST_INFO_LABEL: &[u8] = b"message/hpke-http request\0v3\0";
41const STREAM_REQUEST_AAD_LABEL: &[u8] = b"hpke-http/3 request record\0";
42const STREAM_START: u8 = 1;
43const STREAM_DATA: u8 = 2;
44const STREAM_END: u8 = 3;
45/// Largest clear DATA part in a protected upload.
46pub const STREAM_DATA_LEN: usize = 64 * 1024;
47/// Maximum DATA records, including small writes.
48const STREAM_MAX_DATA_RECORDS: u64 = 1_048_576;
49const RESPONSE_EXPORT_LABEL: &[u8] = b"message/hpke-http response\0v3";
50const REPLAY_LABEL: &[u8] = b"hpke-http/replay\0v3\0";
51const RESPONSE_KEY_INFO: &[u8] = b"hpke-http/3 response key";
52const RESPONSE_NONCE_INFO: &[u8] = b"hpke-http/3 response nonce";
53const RESPONSE_SECRET_LEN: usize = 32;
54const RESPONSE_KEY_LEN: usize = 32;
55const AEAD_NONCE_LEN: usize = 12;
56const MIN_PSK_LEN: usize = 32;
57pub(crate) type ResponseKey = Zeroizing<[u8; RESPONSE_KEY_LEN]>;
58pub(crate) type ResponseNonce = Zeroizing<[u8; AEAD_NONCE_LEN]>;
59
60/// A generated X25519 recipient key pair.
61pub struct KeyPair {
62    private_key: Zeroizing<Vec<u8>>,
63    public_key: Vec<u8>,
64}
65
66impl fmt::Debug for KeyPair {
67    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
68        formatter
69            .debug_struct("KeyPair")
70            .field("private_key", &"[REDACTED]")
71            .field("public_key_len", &self.public_key.len())
72            .finish()
73    }
74}
75
76impl KeyPair {
77    /// Return the encoded private and public keys.
78    #[must_use]
79    pub fn into_parts(self) -> (Zeroizing<Vec<u8>>, Vec<u8>) {
80        (self.private_key, self.public_key)
81    }
82
83    /// Borrow the encoded public key.
84    #[must_use]
85    pub fn public_key(&self) -> &[u8] {
86        &self.public_key
87    }
88}
89
90/// Generate a recipient key pair with the platform entropy source.
91///
92/// # Errors
93///
94/// Returns [`Error::EntropyUnavailable`] if the platform source fails.
95pub fn generate_key_pair() -> Result<KeyPair, Error> {
96    generate_key_pair_with_entropy(&mut SystemEntropy)
97}
98
99/// Generate a recipient key pair with an injected fallible entropy source.
100///
101/// # Errors
102///
103/// Returns the error reported by `entropy` before producing a key pair.
104pub fn generate_key_pair_with_entropy(entropy: &mut impl EntropySource) -> Result<KeyPair, Error> {
105    let mut input_key_material = Zeroizing::new([0_u8; 32]);
106    entropy.fill(input_key_material.as_mut())?;
107    let (private_key, public_key) = Kem::derive_keypair(input_key_material.as_ref());
108    Ok(KeyPair {
109        private_key: Zeroizing::new(private_key.to_bytes().to_vec()),
110        public_key: public_key.to_bytes().to_vec(),
111    })
112}
113
114/// A request envelope and the one-use capability needed to open its response.
115#[must_use]
116pub struct ProtectedRequest {
117    envelope: Vec<u8>,
118    response_token: ResponseToken,
119}
120
121impl fmt::Debug for ProtectedRequest {
122    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
123        formatter
124            .debug_struct("ProtectedRequest")
125            .field("envelope_len", &self.envelope.len())
126            .field("response_token", &self.response_token)
127            .finish()
128    }
129}
130
131impl ProtectedRequest {
132    /// Borrow the complete protected request envelope.
133    #[must_use]
134    pub fn envelope(&self) -> &[u8] {
135        &self.envelope
136    }
137
138    /// Split the envelope from its one-use response token.
139    pub fn into_parts(self) -> (Vec<u8>, ResponseToken) {
140        (self.envelope, self.response_token)
141    }
142}
143
144/// A client configured for one recipient key and one explicit PSK identity.
145pub struct Client {
146    recipient_public_key: <Kem as KemTrait>::PublicKey,
147    recipient_key_id: Vec<u8>,
148    psk: Zeroizing<Vec<u8>>,
149    psk_id: Vec<u8>,
150    limits: Limits,
151}
152
153impl fmt::Debug for Client {
154    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
155        formatter
156            .debug_struct("Client")
157            .field("recipient_key_id_len", &self.recipient_key_id.len())
158            .field("psk", &"[REDACTED]")
159            .field("psk_id_len", &self.psk_id.len())
160            .field("limits", &self.limits)
161            .finish_non_exhaustive()
162    }
163}
164
165impl Client {
166    /// Validate and create a client. The public PSK ID cannot equal the PSK.
167    ///
168    /// # Errors
169    ///
170    /// Returns [`Error::InvalidConfiguration`] for invalid keys, identifiers,
171    /// credentials, or limits.
172    pub fn new(
173        recipient_public_key: &[u8],
174        recipient_key_id: Vec<u8>,
175        psk: Vec<u8>,
176        psk_id: Vec<u8>,
177        limits: Limits,
178    ) -> Result<Self, Error> {
179        let psk = Zeroizing::new(psk);
180        validate_id(&recipient_key_id)?;
181        validate_credential(psk.as_slice(), &psk_id)?;
182        let limits = limits.validate()?;
183        let recipient_public_key = <Kem as KemTrait>::PublicKey::from_bytes(recipient_public_key)
184            .map_err(|_| Error::InvalidConfiguration)?;
185        Ok(Self {
186            recipient_public_key,
187            recipient_key_id,
188            psk,
189            psk_id,
190            limits,
191        })
192    }
193
194    /// Protect one complete request with the platform entropy source.
195    ///
196    /// # Errors
197    ///
198    /// Returns a validation, clock, entropy, or cryptographic
199    /// error before exposing a partially protected envelope.
200    pub fn protect(&self, request: &Request) -> Result<ProtectedRequest, Error> {
201        self.protect_at_with_entropy(request, unix_time_seconds()?, &mut SystemEntropy)
202    }
203
204    /// Protect one complete request at an injected Unix time.
205    ///
206    /// This entry point exists for hosts with their own trusted clock and for
207    /// deterministic conformance tests. Normal native callers use [`Self::protect`].
208    ///
209    /// # Errors
210    ///
211    /// Returns a validation, entropy, or cryptographic error.
212    pub fn protect_at(
213        &self,
214        request: &Request,
215        now_unix_s: u64,
216    ) -> Result<ProtectedRequest, Error> {
217        self.protect_at_with_entropy(request, now_unix_s, &mut SystemEntropy)
218    }
219
220    /// Protect one complete request with an injected fallible entropy source.
221    ///
222    /// # Errors
223    ///
224    /// Returns a validation, clock, entropy, or cryptographic
225    /// error before exposing a partially protected envelope.
226    pub fn protect_with_entropy(
227        &self,
228        request: &Request,
229        entropy: &mut impl EntropySource,
230    ) -> Result<ProtectedRequest, Error> {
231        self.protect_at_with_entropy(request, unix_time_seconds()?, entropy)
232    }
233
234    /// Protect a request with injected time and entropy.
235    ///
236    /// # Errors
237    ///
238    /// Returns a validation, entropy, or cryptographic error
239    /// before exposing a partial envelope.
240    pub fn protect_at_with_entropy(
241        &self,
242        request: &Request,
243        now_unix_s: u64,
244        entropy: &mut impl EntropySource,
245    ) -> Result<ProtectedRequest, Error> {
246        message::validate_request(request, self.limits)?;
247        let head = RequestHead {
248            method: request.method,
249            authority: request.authority.clone(),
250            path: request.path.clone(),
251            headers: request.headers.clone(),
252        };
253        let (mut writer, mut envelope) =
254            self.begin_stream_at_with_entropy(&head, now_unix_s, entropy)?;
255        let mut remaining = request.body.as_slice();
256        while !remaining.is_empty() {
257            let (used, record) = writer.push(remaining)?;
258            remaining = &remaining[used..];
259            if let Some(record) = record {
260                envelope.extend_from_slice(&record);
261            }
262        }
263        let (end, response_token) = writer.finish()?;
264        envelope.extend_from_slice(&end);
265        Ok(ProtectedRequest {
266            envelope,
267            response_token,
268        })
269    }
270}
271
272/// Public information needed by a host credential resolver.
273#[derive(Clone, Debug, Eq, PartialEq)]
274pub struct CredentialRequest {
275    /// Opaque public PSK identifier authenticated with the request.
276    pub psk_id: Vec<u8>,
277}
278
279/// Result of the bounded public preparse stage.
280#[must_use]
281pub struct PreparsedRequest {
282    /// Input for the host credential resolver.
283    pub credential: CredentialRequest,
284    /// One-use input to [`Server::authenticate`].
285    pub token: StartToken,
286}
287
288/// Opaque one-use continuation after public preparse.
289#[must_use]
290pub struct StartToken {
291    envelope: Vec<u8>,
292}
293
294impl fmt::Debug for StartToken {
295    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
296        formatter
297            .debug_struct("StartToken")
298            .field("envelope_len", &self.envelope.len())
299            .finish()
300    }
301}
302
303/// Stable request identifier passed to an atomic replay-admission provider.
304#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
305pub struct ReplayRequest {
306    /// SHA-256 replay key for this authenticated attempt.
307    pub id: [u8; 32],
308    /// Unix second at which this reservation can be evicted.
309    pub retain_until_exclusive: u64,
310}
311
312impl ReplayRequest {
313    /// Bind one provider result to this exact replay request.
314    #[must_use]
315    pub const fn decision(self, admitted: bool) -> ReplayDecision {
316        ReplayDecision {
317            id: self.id,
318            retain_until_exclusive: self.retain_until_exclusive,
319            admitted,
320        }
321    }
322}
323
324/// One atomic replay-provider result bound to its request ID.
325#[derive(Clone, Copy, Debug, Eq, PartialEq)]
326pub struct ReplayDecision {
327    id: [u8; 32],
328    retain_until_exclusive: u64,
329    admitted: bool,
330}
331
332fn check_replay_decision(
333    replay_id: [u8; 32],
334    retain_until_exclusive: u64,
335    decision: ReplayDecision,
336    now_unix_s: u64,
337) -> Result<(), Error> {
338    if replay_id != decision.id || retain_until_exclusive != decision.retain_until_exclusive {
339        return Err(Error::ReplayDecisionMismatch);
340    }
341    if !decision.admitted {
342        return Err(Error::ReplayRejected);
343    }
344    if now_unix_s >= retain_until_exclusive {
345        return Err(Error::InvalidRequestTime);
346    }
347    Ok(())
348}
349
350/// Result of authentication, paused before replay admission and plaintext release.
351#[must_use]
352pub struct AuthenticatedRequest {
353    /// Input for an atomic replay-admission provider.
354    pub replay: ReplayRequest,
355    /// One-use input to [`ReplayToken::admit`].
356    pub token: ReplayToken,
357}
358
359/// Opaque one-use continuation after request authentication.
360#[must_use]
361pub struct ReplayToken {
362    replay_id: [u8; 32],
363    retain_until_exclusive: u64,
364    request: Request,
365    response: ResponseCapability,
366}
367
368impl fmt::Debug for ReplayToken {
369    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
370        formatter
371            .debug_struct("ReplayToken")
372            .field("replay_id", &self.replay_id)
373            .field("retain_until_exclusive", &self.retain_until_exclusive)
374            .field("request", &"[REDACTED]")
375            .finish_non_exhaustive()
376    }
377}
378
379impl ReplayToken {
380    /// Apply one atomic replay decision before its deadline, then release the
381    /// authenticated request.
382    ///
383    /// # Errors
384    ///
385    /// Returns [`Error::ReplayDecisionMismatch`] when the decision belongs to a
386    /// different request, [`Error::ReplayRejected`] when the provider denies
387    /// admission, [`Error::InvalidRequestTime`] when admission completes after
388    /// the authenticated deadline, or [`Error::ClockUnavailable`] when the
389    /// platform clock cannot be read.
390    pub fn admit(self, decision: ReplayDecision) -> Result<OpenedRequest, Error> {
391        self.admit_at(decision, unix_time_seconds()?)
392    }
393
394    /// Apply one replay decision at a caller-supplied trusted Unix time.
395    ///
396    /// This entry point exists for hosts with their own trusted clock and for
397    /// deterministic conformance tests. Normal native callers use [`Self::admit`].
398    ///
399    /// # Errors
400    ///
401    /// Returns the same decision, freshness, and clock-independent errors as
402    /// [`Self::admit`].
403    pub fn admit_at(
404        self,
405        decision: ReplayDecision,
406        now_unix_s: u64,
407    ) -> Result<OpenedRequest, Error> {
408        check_replay_decision(
409            self.replay_id,
410            self.retain_until_exclusive,
411            decision,
412            now_unix_s,
413        )?;
414        Ok(OpenedRequest {
415            request: self.request,
416            response: self.response,
417        })
418    }
419}
420
421/// A fully authenticated request and its one-use response capability.
422#[must_use]
423pub struct OpenedRequest {
424    /// Verified request data safe for application dispatch.
425    pub request: Request,
426    /// One-use capability bound to this request.
427    pub response: ResponseCapability,
428}
429
430/// A server configured for one static recipient key.
431pub struct Server {
432    recipient_private_key: <Kem as KemTrait>::PrivateKey,
433    recipient_key_id: Vec<u8>,
434    limits: Limits,
435}
436
437impl fmt::Debug for Server {
438    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
439        formatter
440            .debug_struct("Server")
441            .field("recipient_private_key", &"[REDACTED]")
442            .field("recipient_key_id_len", &self.recipient_key_id.len())
443            .field("limits", &self.limits)
444            .finish()
445    }
446}
447
448impl Server {
449    /// Validate and create a server for one static recipient key.
450    ///
451    /// # Errors
452    ///
453    /// Returns [`Error::InvalidConfiguration`] for invalid key material,
454    /// identifiers, or limits.
455    pub fn new(
456        recipient_private_key: &[u8],
457        recipient_key_id: Vec<u8>,
458        limits: Limits,
459    ) -> Result<Self, Error> {
460        validate_id(&recipient_key_id)?;
461        let limits = limits.validate()?;
462        let recipient_private_key =
463            <Kem as KemTrait>::PrivateKey::from_bytes(recipient_private_key)
464                .map_err(|_| Error::InvalidConfiguration)?;
465        Ok(Self {
466            recipient_private_key,
467            recipient_key_id,
468            limits,
469        })
470    }
471
472    /// Return the encoded public key for this server's private key.
473    #[must_use]
474    pub fn public_key(&self) -> Vec<u8> {
475        Kem::sk_to_pk(&self.recipient_private_key)
476            .to_bytes()
477            .to_vec()
478    }
479
480    /// Return the largest complete request envelope accepted by `preparse`.
481    ///
482    /// Bindings use this limit before they copy a complete envelope.
483    ///
484    /// # Errors
485    ///
486    /// Returns [`Error::LimitExceeded`] if the size cannot fit in `usize`.
487    pub fn max_complete_envelope_len(&self) -> Result<usize, Error> {
488        max_complete_envelope_len(self.limits)
489    }
490
491    /// Parse bounded public fields without accepting credentials or plaintext.
492    ///
493    /// # Errors
494    ///
495    /// Returns a stable parse, limit, version, suite, or recipient-key error.
496    pub fn preparse(&self, envelope: &[u8]) -> Result<PreparsedRequest, Error> {
497        let credential = self.preparse_credential(envelope)?;
498        Ok(PreparsedRequest {
499            credential,
500            token: StartToken {
501                envelope: envelope.to_vec(),
502            },
503        })
504    }
505
506    /// Parse an owned envelope without cloning its complete ciphertext.
507    ///
508    /// Bindings which already own their input can move it into the one-shot
509    /// credential-resolution token. The same bounded public checks run before
510    /// the token is created.
511    ///
512    /// # Errors
513    ///
514    /// Returns a stable parse, limit, version, suite, or recipient-key error.
515    pub fn preparse_owned(&self, envelope: Vec<u8>) -> Result<PreparsedRequest, Error> {
516        let credential = self.preparse_credential(&envelope)?;
517        Ok(PreparsedRequest {
518            credential,
519            token: StartToken { envelope },
520        })
521    }
522
523    fn preparse_credential(&self, envelope: &[u8]) -> Result<CredentialRequest, Error> {
524        if envelope.len() > self.max_complete_envelope_len()? {
525            return Err(Error::LimitExceeded);
526        }
527        let first_len = self
528            .stream_start_length(envelope)?
529            .ok_or(Error::MalformedEnvelope)?;
530        if first_len > envelope.len() {
531            return Err(Error::MalformedEnvelope);
532        }
533        let parsed = parse_stream_start(
534            &envelope[..first_len],
535            max_stream_start_ciphertext(self.limits),
536        )?;
537        if parsed.key_id != self.recipient_key_id {
538            return Err(Error::UnknownRecipientKey);
539        }
540        Ok(CredentialRequest {
541            psk_id: parsed.psk_id.to_vec(),
542        })
543    }
544
545    /// Authenticate and parse the full request, then pause before replay admission.
546    ///
547    /// Plaintext stays inside the returned opaque token until admission.
548    ///
549    /// # Errors
550    ///
551    /// Returns a credential, recipient-key, parse, authentication,
552    /// request-time, or clock error.
553    pub fn authenticate(
554        &self,
555        token: StartToken,
556        psk: &[u8],
557    ) -> Result<AuthenticatedRequest, Error> {
558        self.authenticate_at(token, psk, unix_time_seconds()?)
559    }
560
561    /// Authenticate at an injected Unix time, then pause before replay admission.
562    ///
563    /// # Errors
564    ///
565    /// Returns a credential, recipient-key, parse, authentication, or request-time error.
566    pub fn authenticate_at(
567        &self,
568        token: StartToken,
569        psk: &[u8],
570        now_unix_s: u64,
571    ) -> Result<AuthenticatedRequest, Error> {
572        let StartToken { envelope } = token;
573        let first_len = self
574            .stream_start_length(&envelope)?
575            .ok_or(Error::MalformedEnvelope)?;
576        if first_len > envelope.len() {
577            return Err(Error::MalformedEnvelope);
578        }
579        let authenticated = self.authenticate_stream_at(
580            StreamStartToken {
581                first: envelope[..first_len].to_vec(),
582            },
583            psk,
584            now_unix_s,
585        )?;
586        let replay = authenticated.replay;
587        let StreamReplayToken {
588            head, mut reader, ..
589        } = authenticated.token;
590        let mut body = Vec::new();
591        let mut remaining = &envelope[first_len..];
592        while !remaining.is_empty() {
593            let (used, record) = reader.feed(remaining)?;
594            remaining = &remaining[used..];
595            if let Some(StreamRequestRecord::Data(part)) = record {
596                if part.len() > self.limits.max_body_len.saturating_sub(body.len()) {
597                    return Err(Error::LimitExceeded);
598                }
599                body.extend_from_slice(&part);
600            }
601        }
602        let response = reader.finish_eof()?;
603        let request = Request {
604            method: head.method,
605            authority: head.authority,
606            path: head.path,
607            headers: head.headers,
608            body,
609        };
610        Ok(AuthenticatedRequest {
611            replay,
612            token: ReplayToken {
613                replay_id: replay.id,
614                retain_until_exclusive: replay.retain_until_exclusive,
615                request,
616                response,
617            },
618        })
619    }
620}
621
622#[cfg(test)]
623mod stream_tests {
624    use super::STREAM_MAX_DATA_RECORDS;
625    use crate::{Client, Error, Limits, Method, RequestHead, Server, generate_key_pair};
626
627    #[test]
628    fn data_record_count_guard_closes_both_sides() -> Result<(), Error> {
629        let keys = generate_key_pair()?;
630        let psk = b"a 32-byte minimum upload credential";
631        let client = Client::new(
632            keys.public_key(),
633            b"key".to_vec(),
634            psk.to_vec(),
635            b"psk".to_vec(),
636            Limits::default(),
637        )?;
638        let server = Server::new(&keys.into_parts().0, b"key".to_vec(), Limits::default())?;
639        let head = RequestHead {
640            method: Method::Post,
641            authority: b"api.example.test".to_vec(),
642            path: b"/upload".to_vec(),
643            headers: Vec::new(),
644        };
645        let (mut writer, _) = client.begin_stream(&head)?;
646        writer.data_records = STREAM_MAX_DATA_RECORDS;
647        assert_eq!(writer.push(b"x").err(), Some(Error::LimitExceeded));
648        assert!(writer.context.is_none());
649
650        let (mut writer, first) = client.begin_stream(&head)?;
651        let (used, record) = writer.push(b"x")?;
652        assert_eq!(used, 1);
653        assert!(record.is_none());
654        let (data, _) = writer.finish()?;
655        let preparsed = server.preparse_stream(&first)?;
656        let authenticated = server.authenticate_stream(preparsed.token, psk)?;
657        let mut reader = authenticated
658            .token
659            .admit(authenticated.replay.decision(true))?
660            .reader;
661        reader.data_records = STREAM_MAX_DATA_RECORDS;
662        assert_eq!(reader.feed(&data).err(), Some(Error::LimitExceeded));
663        assert!(reader.context.is_none());
664        assert!(reader.finish_eof().is_err());
665        Ok(())
666    }
667}
668
669pub(crate) struct ResponseMaterial {
670    pub(crate) secret: Zeroizing<[u8; RESPONSE_SECRET_LEN]>,
671    pub(crate) enc: [u8; ENC_LEN],
672    pub(crate) method: Method,
673    pub(crate) limits: Limits,
674}
675
676impl fmt::Debug for ResponseMaterial {
677    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
678        formatter
679            .debug_struct("ResponseMaterial")
680            .field("secret", &"[REDACTED]")
681            .field("enc_len", &self.enc.len())
682            .field("method", &self.method)
683            .field("limits", &self.limits)
684            .finish()
685    }
686}
687
688/// One-use client capability for opening the response to one request.
689#[must_use]
690pub struct ResponseToken(ResponseMaterial);
691
692impl fmt::Debug for ResponseToken {
693    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
694        formatter
695            .debug_tuple("ResponseToken")
696            .field(&self.0)
697            .finish()
698    }
699}
700
701impl ResponseToken {
702    /// Consume this token to check response records as they arrive.
703    #[must_use]
704    pub fn into_opener(self) -> ResponseOpener {
705        ResponseOpener::new(self.0)
706    }
707
708    /// Open one complete finite response with the v3 record reader.
709    ///
710    /// # Errors
711    /// Returns a parse, limit, authentication, or response-mode error.
712    pub fn open_finite(self, envelope: &[u8]) -> Result<Response, Error> {
713        let mut opener = self.into_opener();
714        let mut offset = 0;
715        while offset < envelope.len() {
716            let (used, record) = opener.feed(&envelope[offset..])?;
717            offset += used;
718            if matches!(record, Some(ResponseRecord::Start(ref head)) if head.mode == ResponseMode::Sse)
719            {
720                return Err(Error::InvalidConfiguration);
721            }
722            if used == 0 {
723                return Err(Error::MalformedEnvelope);
724            }
725        }
726        opener.finish_eof()?.ok_or(Error::MalformedEnvelope)
727    }
728}
729
730/// One-use server capability for protecting the response to one opened request.
731#[must_use]
732pub struct ResponseCapability(ResponseMaterial);
733
734impl fmt::Debug for ResponseCapability {
735    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
736        formatter
737            .debug_tuple("ResponseCapability")
738            .field(&self.0)
739            .finish()
740    }
741}
742
743impl ResponseCapability {
744    /// Start one checked response. The returned bytes hold the prefix and START.
745    ///
746    /// # Errors
747    /// Returns a validation, entropy, or cryptographic error.
748    pub fn into_sealer(
749        self,
750        status: u16,
751        headers: Vec<HeaderField>,
752    ) -> Result<(ResponseSealer, Vec<u8>), Error> {
753        self.into_sealer_with_entropy(status, headers, &mut SystemEntropy)
754    }
755
756    /// Start one checked response with an injected entropy source.
757    ///
758    /// # Errors
759    /// Returns a validation, entropy, or cryptographic error.
760    pub fn into_sealer_with_entropy(
761        self,
762        status: u16,
763        headers: Vec<HeaderField>,
764        entropy: &mut impl EntropySource,
765    ) -> Result<(ResponseSealer, Vec<u8>), Error> {
766        ResponseSealer::new(&self.0, status, headers, entropy)
767    }
768
769    /// Protect one complete finite response with the v3 record writer.
770    ///
771    /// # Errors
772    /// Returns a validation, limit, entropy, or cryptographic error.
773    pub fn protect_finite(self, response: &Response) -> Result<Vec<u8>, Error> {
774        self.protect_finite_with_entropy(response, &mut SystemEntropy)
775    }
776
777    /// Protect one complete finite response with injected entropy.
778    ///
779    /// # Errors
780    /// Returns a validation, limit, entropy, or cryptographic error.
781    pub fn protect_finite_with_entropy(
782        self,
783        response: &Response,
784        entropy: &mut impl EntropySource,
785    ) -> Result<Vec<u8>, Error> {
786        self.protect_finite_parts_with_entropy(
787            response.status,
788            response.headers.clone(),
789            &response.body,
790            entropy,
791        )
792    }
793
794    /// Protect a finite response from a borrowed body without copying it first.
795    ///
796    /// # Errors
797    /// Returns a validation, limit, entropy, or cryptographic error.
798    pub fn protect_finite_parts(
799        self,
800        status: u16,
801        headers: Vec<HeaderField>,
802        body: &[u8],
803    ) -> Result<Vec<u8>, Error> {
804        self.protect_finite_parts_with_entropy(status, headers, body, &mut SystemEntropy)
805    }
806
807    fn protect_finite_parts_with_entropy(
808        self,
809        status: u16,
810        headers: Vec<HeaderField>,
811        body: &[u8],
812        entropy: &mut impl EntropySource,
813    ) -> Result<Vec<u8>, Error> {
814        let (mut sealer, mut output) = self.into_sealer_with_entropy(status, headers, entropy)?;
815        if sealer.head_mode() != ResponseMode::Finite {
816            return Err(Error::InvalidConfiguration);
817        }
818        let data = sealer.seal_finite_body(body)?;
819        let end = sealer.finish()?;
820        output.reserve(data.as_ref().map_or(0, Vec::len) + end.len());
821        if let Some(data) = data {
822            output.extend_from_slice(&data);
823        }
824        output.extend_from_slice(&end);
825        Ok(output)
826    }
827}
828
829fn validate_id(identifier: &[u8]) -> Result<(), Error> {
830    if identifier.is_empty() || identifier.len() > HARD_MAX_ID_LEN {
831        return Err(Error::InvalidConfiguration);
832    }
833    Ok(())
834}
835
836fn validate_credential(psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
837    validate_id(psk_id)?;
838    if psk.len() < MIN_PSK_LEN || psk == psk_id {
839        return Err(Error::InvalidConfiguration);
840    }
841    Ok(())
842}
843
844fn unix_time_seconds() -> Result<u64, Error> {
845    SystemTime::now()
846        .duration_since(UNIX_EPOCH)
847        .map_err(|_| Error::ClockUnavailable)
848        .map(|duration| duration.as_secs())
849}
850
851fn replay_retention_deadline(issued_at_unix_s: u64, now_unix_s: u64) -> Result<u64, Error> {
852    let latest_issued_at = now_unix_s
853        .checked_add(CLOCK_SKEW_SECS)
854        .ok_or(Error::InvalidRequestTime)?;
855    let retain_until_exclusive = issued_at_unix_s
856        .checked_add(REQUEST_LIFETIME_SECS)
857        .and_then(|value| value.checked_add(CLOCK_SKEW_SECS))
858        .ok_or(Error::InvalidRequestTime)?;
859    if issued_at_unix_s > latest_issued_at || now_unix_s >= retain_until_exclusive {
860        return Err(Error::InvalidRequestTime);
861    }
862    Ok(retain_until_exclusive)
863}
864
865fn replay_id(header: &[u8], enc: &[u8]) -> [u8; 32] {
866    let mut digest = Sha256::new();
867    digest.update(REPLAY_LABEL);
868    digest.update(header);
869    digest.update(enc);
870    digest.finalize().into()
871}
872
873fn export_response_material<C>(
874    context: &C,
875    enc: &[u8],
876    method: Method,
877    limits: Limits,
878) -> Result<ResponseMaterial, Error>
879where
880    C: Exporter,
881{
882    let enc: [u8; ENC_LEN] = enc.try_into().map_err(|_| Error::CryptoFailure)?;
883    let mut secret = Zeroizing::new([0_u8; RESPONSE_SECRET_LEN]);
884    context.export_secret(RESPONSE_EXPORT_LABEL, secret.as_mut())?;
885    Ok(ResponseMaterial {
886        secret,
887        enc,
888        method,
889        limits,
890    })
891}
892
893pub(crate) fn derive_response_key_nonce(
894    material: &ResponseMaterial,
895    response_nonce: &[u8],
896) -> Result<(ResponseKey, ResponseNonce), Error> {
897    if response_nonce.len() != RESPONSE_NONCE_LEN {
898        return Err(Error::MalformedEnvelope);
899    }
900    let mut salt = Zeroizing::new(Vec::with_capacity(ENC_LEN + RESPONSE_NONCE_LEN));
901    salt.extend_from_slice(&material.enc);
902    salt.extend_from_slice(response_nonce);
903    let hkdf = Hkdf::<Sha256>::new(Some(&salt), material.secret.as_ref());
904    let mut key = Zeroizing::new([0_u8; RESPONSE_KEY_LEN]);
905    hkdf.expand(RESPONSE_KEY_INFO, key.as_mut())
906        .map_err(|_| Error::CryptoFailure)?;
907    let mut nonce = Zeroizing::new([0_u8; AEAD_NONCE_LEN]);
908    hkdf.expand(RESPONSE_NONCE_INFO, nonce.as_mut())
909        .map_err(|_| Error::CryptoFailure)?;
910    Ok((key, nonce))
911}
912
913trait Exporter {
914    fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error>;
915}
916
917impl Exporter for hpke::aead::AeadCtxS<HpkeAead, Kdf, Kem> {
918    fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
919        self.export(context, output)
920            .map_err(|_| Error::CryptoFailure)
921    }
922}
923
924impl Exporter for RecipientContext {
925    fn export_secret(&self, context: &[u8], output: &mut [u8]) -> Result<(), Error> {
926        self.export(context, output)
927            .map_err(|_| Error::CryptoFailure)
928    }
929}
930
931fn max_stream_start_ciphertext(limits: Limits) -> usize {
932    limits.max_header_bytes + limits.max_target_len + limits.max_header_count * 16 + 256 + TAG_LEN
933}
934
935fn max_complete_envelope_len(limits: Limits) -> Result<usize, Error> {
936    let records = limits
937        .max_body_len
938        .min(usize::try_from(STREAM_MAX_DATA_RECORDS).map_err(|_| Error::LimitExceeded)?);
939    let data_overhead = records
940        .checked_mul(4 + 2 + TAG_LEN)
941        .ok_or(Error::LimitExceeded)?;
942    REQUEST_FIXED_LEN
943        .checked_add(2 * HARD_MAX_ID_LEN)
944        .and_then(|length| length.checked_add(ENC_LEN + 4))
945        .and_then(|length| length.checked_add(max_stream_start_ciphertext(limits)))
946        .and_then(|length| length.checked_add(limits.max_body_len))
947        .and_then(|length| length.checked_add(data_overhead))
948        .and_then(|length| length.checked_add(4 + 1 + TAG_LEN))
949        .ok_or(Error::LimitExceeded)
950}
951
952fn stream_request_info(header: &[u8]) -> Vec<u8> {
953    let mut info = Vec::with_capacity(STREAM_REQUEST_INFO_LABEL.len() + header.len());
954    info.extend_from_slice(STREAM_REQUEST_INFO_LABEL);
955    info.extend_from_slice(header);
956    info
957}
958
959fn stream_record_aad(header: &[u8], sequence: u64, ciphertext_len: u32) -> Vec<u8> {
960    let mut aad = Vec::with_capacity(STREAM_REQUEST_AAD_LABEL.len() + header.len() + 12);
961    aad.extend_from_slice(STREAM_REQUEST_AAD_LABEL);
962    aad.extend_from_slice(header);
963    aad.extend_from_slice(&sequence.to_be_bytes());
964    aad.extend_from_slice(&ciphertext_len.to_be_bytes());
965    aad
966}
967
968fn seal_stream_record(
969    context: &mut SenderContext,
970    header: &[u8],
971    sequence: u64,
972    kind: u8,
973    coding: Option<u8>,
974    body: &[u8],
975) -> Result<Vec<u8>, Error> {
976    let ciphertext_len = body
977        .len()
978        .checked_add(1 + usize::from(coding.is_some()) + TAG_LEN)
979        .ok_or(Error::LimitExceeded)?;
980    let ciphertext_len = u32::try_from(ciphertext_len).map_err(|_| Error::LimitExceeded)?;
981    let mut frame = Vec::with_capacity(4 + ciphertext_len as usize);
982    frame.extend_from_slice(&ciphertext_len.to_be_bytes());
983    frame.push(kind);
984    if let Some(coding) = coding {
985        frame.push(coding);
986    }
987    frame.extend_from_slice(body);
988    let aad = stream_record_aad(header, sequence, ciphertext_len);
989    let tag = context
990        .seal_inout_detached(InOutBuf::from(&mut frame[4..]), &aad)
991        .map_err(|_| Error::CryptoFailure)?;
992    frame.extend_from_slice(tag.to_bytes().as_ref());
993    Ok(frame)
994}
995
996/// One request writer. A failed call closes it and prevents a valid END.
997pub struct StreamRequestSealer {
998    context: Option<SenderContext>,
999    header: Vec<u8>,
1000    sequence: u64,
1001    data_records: u64,
1002    total_bytes: u64,
1003    content_length: Option<u64>,
1004    max_upload_bytes: u64,
1005    pending: Vec<u8>,
1006    response_token: Option<ResponseToken>,
1007}
1008
1009impl fmt::Debug for StreamRequestSealer {
1010    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1011        formatter
1012            .debug_struct("StreamRequestSealer")
1013            .field("sequence", &self.sequence)
1014            .field("total_bytes", &self.total_bytes)
1015            .finish_non_exhaustive()
1016    }
1017}
1018
1019impl StreamRequestSealer {
1020    /// Add clear bytes. A full 64 KiB DATA record is returned when ready.
1021    /// The returned count can be less than `part.len()`. Pass `&part[used..]`
1022    /// to the next call until all input bytes are used.
1023    ///
1024    /// # Errors
1025    /// Returns a state, length, count, or cryptographic error and closes the writer.
1026    pub fn push(&mut self, part: &[u8]) -> Result<(usize, Option<Vec<u8>>), Error> {
1027        let result = self.push_inner(part);
1028        if result.is_err() {
1029            self.context = None;
1030            self.response_token = None;
1031        }
1032        result
1033    }
1034
1035    fn push_inner(&mut self, part: &[u8]) -> Result<(usize, Option<Vec<u8>>), Error> {
1036        if self.context.is_none() {
1037            return Err(Error::InvalidConfiguration);
1038        }
1039        if part.is_empty() {
1040            return Ok((0, None));
1041        }
1042        if self.data_records >= STREAM_MAX_DATA_RECORDS {
1043            return Err(Error::LimitExceeded);
1044        }
1045        let consumed = part.len().min(STREAM_DATA_LEN - self.pending.len());
1046        let next_total = self
1047            .total_bytes
1048            .checked_add(consumed as u64)
1049            .ok_or(Error::LimitExceeded)?;
1050        if next_total > self.max_upload_bytes
1051            || self
1052                .content_length
1053                .is_some_and(|length| next_total > length)
1054        {
1055            return Err(Error::LimitExceeded);
1056        }
1057        self.pending.extend_from_slice(&part[..consumed]);
1058        self.total_bytes = next_total;
1059        if self.pending.len() == STREAM_DATA_LEN {
1060            let frame = self.seal_pending()?;
1061            Ok((consumed, Some(frame)))
1062        } else {
1063            Ok((consumed, None))
1064        }
1065    }
1066
1067    fn seal_pending(&mut self) -> Result<Vec<u8>, Error> {
1068        if self.pending.is_empty() || self.data_records >= STREAM_MAX_DATA_RECORDS {
1069            return Err(Error::LimitExceeded);
1070        }
1071        let (coding, coded) = compression::encode(&self.pending);
1072        let context = self.context.as_mut().ok_or(Error::InvalidConfiguration)?;
1073        let frame = seal_stream_record(
1074            context,
1075            &self.header,
1076            self.sequence,
1077            STREAM_DATA,
1078            Some(coding),
1079            &coded,
1080        )?;
1081        self.pending.clear();
1082        self.sequence += 1;
1083        self.data_records += 1;
1084        Ok(frame)
1085    }
1086
1087    /// Protect END and release the one-use response right.
1088    ///
1089    /// # Errors
1090    /// Returns a state, logical length, or cryptographic error.
1091    pub fn finish(mut self) -> Result<(Vec<u8>, ResponseToken), Error> {
1092        if self
1093            .content_length
1094            .is_some_and(|length| self.total_bytes != length)
1095        {
1096            return Err(Error::InvalidConfiguration);
1097        }
1098        let mut frame = if self.pending.is_empty() {
1099            Vec::new()
1100        } else {
1101            self.seal_pending()?
1102        };
1103        let context = self.context.as_mut().ok_or(Error::InvalidConfiguration)?;
1104        frame.extend_from_slice(&seal_stream_record(
1105            context,
1106            &self.header,
1107            self.sequence,
1108            STREAM_END,
1109            None,
1110            &[],
1111        )?);
1112        let token = self
1113            .response_token
1114            .take()
1115            .ok_or(Error::InvalidConfiguration)?;
1116        Ok((frame, token))
1117    }
1118}
1119
1120impl Client {
1121    /// Start a protected request and return its bounded prefix and checked START.
1122    ///
1123    /// # Errors
1124    /// Returns a validation, entropy, or cryptographic error before source bytes are read.
1125    pub fn begin_stream(
1126        &self,
1127        head: &RequestHead,
1128    ) -> Result<(StreamRequestSealer, Vec<u8>), Error> {
1129        self.begin_stream_at_with_entropy(head, unix_time_seconds()?, &mut SystemEntropy)
1130    }
1131
1132    /// Start a stream with caller-supplied time and entropy for wire tests.
1133    ///
1134    /// # Errors
1135    /// Returns a validation, entropy, or cryptographic error.
1136    pub fn begin_stream_at_with_entropy(
1137        &self,
1138        head: &RequestHead,
1139        now_unix_s: u64,
1140        entropy: &mut impl EntropySource,
1141    ) -> Result<(StreamRequestSealer, Vec<u8>), Error> {
1142        let max_upload_bytes = self.limits.max_request_bytes;
1143        let content_length = message::stream_content_length(&head.headers)?;
1144        if content_length.is_some_and(|length| length > max_upload_bytes) {
1145            return Err(Error::LimitExceeded);
1146        }
1147        let clear_head = message::encode_stream_head(head, self.limits)?;
1148        let header = encode_request_header(&self.recipient_key_id, &self.psk_id, now_unix_s)?;
1149        let psk_bundle = PskBundle::new(self.psk.as_slice(), &self.psk_id)
1150            .map_err(|_| Error::InvalidConfiguration)?;
1151        let mut random_seed = Zeroizing::new([0_u8; 32]);
1152        entropy.fill(random_seed.as_mut())?;
1153        let mut random = ChaCha20Rng::from_seed(*random_seed);
1154        let (encapped_key, mut context) = setup_sender_with_rng::<HpkeAead, Kdf, Kem>(
1155            &OpModeS::Psk(psk_bundle),
1156            &self.recipient_public_key,
1157            &stream_request_info(&header),
1158            &mut random,
1159        )
1160        .map_err(|_| Error::CryptoFailure)?;
1161        let enc = encapped_key.to_bytes();
1162        let response_material =
1163            export_response_material(&context, enc.as_ref(), head.method, self.limits)?;
1164        let start = seal_stream_record(&mut context, &header, 0, STREAM_START, None, &clear_head)?;
1165        let mut first = Vec::with_capacity(header.len() + ENC_LEN + start.len());
1166        first.extend_from_slice(&header);
1167        first.extend_from_slice(enc.as_ref());
1168        first.extend_from_slice(&start);
1169        Ok((
1170            StreamRequestSealer {
1171                context: Some(context),
1172                header,
1173                sequence: 1,
1174                data_records: 0,
1175                total_bytes: 0,
1176                content_length,
1177                max_upload_bytes,
1178                pending: Vec::with_capacity(STREAM_DATA_LEN),
1179                response_token: Some(ResponseToken(response_material)),
1180            },
1181            first,
1182        ))
1183    }
1184}
1185
1186/// Public credential ID and a one-use bounded START token.
1187#[must_use]
1188pub struct PreparsedStreamRequest {
1189    /// Input for the host credential resolver.
1190    pub credential: CredentialRequest,
1191    /// One-use input for START authentication.
1192    pub token: StreamStartToken,
1193}
1194
1195/// Opaque START bytes before credential resolution.
1196#[must_use]
1197pub struct StreamStartToken {
1198    first: Vec<u8>,
1199}
1200
1201/// Authenticated START, with plaintext withheld until replay admission.
1202#[must_use]
1203pub struct AuthenticatedStreamRequest {
1204    /// Input for an atomic replay-admission provider.
1205    pub replay: ReplayRequest,
1206    /// One-use input for replay admission.
1207    pub token: StreamReplayToken,
1208}
1209
1210/// One-use continuation after START authentication.
1211#[must_use]
1212pub struct StreamReplayToken {
1213    replay_id: [u8; 32],
1214    retain_until_exclusive: u64,
1215    head: RequestHead,
1216    reader: StreamRequestOpener,
1217}
1218
1219impl StreamReplayToken {
1220    /// Apply the replay result before plaintext or DATA is released.
1221    ///
1222    /// # Errors
1223    /// Returns a replay or clock error.
1224    pub fn admit(self, decision: ReplayDecision) -> Result<OpenedStreamRequest, Error> {
1225        self.admit_at(decision, unix_time_seconds()?)
1226    }
1227
1228    /// Apply the replay result at a caller-supplied trusted Unix time.
1229    ///
1230    /// # Errors
1231    /// Returns a replay or freshness error.
1232    pub fn admit_at(
1233        self,
1234        decision: ReplayDecision,
1235        now_unix_s: u64,
1236    ) -> Result<OpenedStreamRequest, Error> {
1237        check_replay_decision(
1238            self.replay_id,
1239            self.retain_until_exclusive,
1240            decision,
1241            now_unix_s,
1242        )?;
1243        Ok(OpenedStreamRequest {
1244            head: self.head,
1245            reader: self.reader,
1246        })
1247    }
1248}
1249
1250/// Checked request head and a live body reader. The response right waits for END and EOF.
1251#[must_use]
1252pub struct OpenedStreamRequest {
1253    /// Authenticated request fields.
1254    pub head: RequestHead,
1255    /// Checks each DATA part before it is released.
1256    pub reader: StreamRequestOpener,
1257}
1258
1259/// One checked upload record.
1260#[derive(Debug, Eq, PartialEq)]
1261pub enum StreamRequestRecord {
1262    /// One checked nonempty clear DATA part.
1263    Data(Vec<u8>),
1264    /// Checked END. True outer EOF must still follow.
1265    End,
1266}
1267
1268/// Incremental request reader with bounded frame storage.
1269pub struct StreamRequestOpener {
1270    context: Option<RecipientContext>,
1271    header: Vec<u8>,
1272    sequence: u64,
1273    data_records: u64,
1274    total_bytes: u64,
1275    content_length: Option<u64>,
1276    max_upload_bytes: u64,
1277    frame: Vec<u8>,
1278    ended: bool,
1279    response_material: Option<ResponseMaterial>,
1280}
1281
1282impl fmt::Debug for StreamRequestOpener {
1283    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
1284        formatter
1285            .debug_struct("StreamRequestOpener")
1286            .field("sequence", &self.sequence)
1287            .field("total_bytes", &self.total_bytes)
1288            .field("ended", &self.ended)
1289            .finish_non_exhaustive()
1290    }
1291}
1292
1293impl StreamRequestOpener {
1294    /// Read at most one record from any byte cut.
1295    /// The returned count is the number of input bytes used. Pass the unread
1296    /// suffix to the next call.
1297    ///
1298    /// # Errors
1299    /// Returns a parse, order, limit, or authentication error and closes the reader.
1300    pub fn feed(&mut self, input: &[u8]) -> Result<(usize, Option<StreamRequestRecord>), Error> {
1301        let result = self.feed_inner(input);
1302        if result.is_err() {
1303            self.context = None;
1304            self.response_material = None;
1305            self.frame.clear();
1306        }
1307        result
1308    }
1309
1310    fn feed_inner(&mut self, input: &[u8]) -> Result<(usize, Option<StreamRequestRecord>), Error> {
1311        if self.context.is_none() || (self.ended && !input.is_empty()) {
1312            return Err(Error::MalformedEnvelope);
1313        }
1314        let mut used = 0;
1315        if self.frame.len() < 4 {
1316            let take = (4 - self.frame.len()).min(input.len());
1317            self.frame.extend_from_slice(&input[..take]);
1318            used += take;
1319            if self.frame.len() < 4 {
1320                return Ok((used, None));
1321            }
1322        }
1323        let ciphertext_len = u32::from_be_bytes(
1324            self.frame[..4]
1325                .try_into()
1326                .map_err(|_| Error::MalformedEnvelope)?,
1327        ) as usize;
1328        if ciphertext_len < 1 + TAG_LEN {
1329            return Err(Error::MalformedEnvelope);
1330        }
1331        if ciphertext_len > STREAM_DATA_LEN + 2 + TAG_LEN {
1332            return Err(Error::LimitExceeded);
1333        }
1334        let frame_len = 4 + ciphertext_len;
1335        let take = (frame_len - self.frame.len()).min(input.len() - used);
1336        self.frame.extend_from_slice(&input[used..used + take]);
1337        used += take;
1338        if self.frame.len() < frame_len {
1339            return Ok((used, None));
1340        }
1341        let plain_end = frame_len - TAG_LEN;
1342        let tag = AeadTag::<HpkeAead>::from_bytes(&self.frame[plain_end..])
1343            .map_err(|_| Error::AuthenticationFailed)?;
1344        let aad = stream_record_aad(
1345            &self.header,
1346            self.sequence,
1347            u32::try_from(ciphertext_len).map_err(|_| Error::LimitExceeded)?,
1348        );
1349        self.context
1350            .as_mut()
1351            .ok_or(Error::MalformedEnvelope)?
1352            .open_inout_detached(InOutBuf::from(&mut self.frame[4..plain_end]), &aad, &tag)
1353            .map_err(|_| Error::AuthenticationFailed)?;
1354        let kind = self.frame[4];
1355        let body = &self.frame[5..plain_end];
1356        let record = match kind {
1357            STREAM_DATA => {
1358                if body.len() < 2 || self.data_records >= STREAM_MAX_DATA_RECORDS {
1359                    return Err(if body.len() < 2 {
1360                        Error::MalformedEnvelope
1361                    } else {
1362                        Error::LimitExceeded
1363                    });
1364                }
1365                let clear = compression::decode(body[0], &body[1..], STREAM_DATA_LEN)?;
1366                let next_total = self
1367                    .total_bytes
1368                    .checked_add(clear.len() as u64)
1369                    .ok_or(Error::LimitExceeded)?;
1370                if next_total > self.max_upload_bytes
1371                    || self
1372                        .content_length
1373                        .is_some_and(|length| next_total > length)
1374                {
1375                    return Err(Error::LimitExceeded);
1376                }
1377                self.total_bytes = next_total;
1378                self.data_records += 1;
1379                self.sequence += 1;
1380                StreamRequestRecord::Data(clear)
1381            }
1382            STREAM_END => {
1383                if !body.is_empty()
1384                    || self
1385                        .content_length
1386                        .is_some_and(|length| self.total_bytes != length)
1387                {
1388                    return Err(Error::MalformedEnvelope);
1389                }
1390                self.ended = true;
1391                StreamRequestRecord::End
1392            }
1393            _ => return Err(Error::MalformedEnvelope),
1394        };
1395        self.frame.clear();
1396        Ok((used, Some(record)))
1397    }
1398
1399    /// Check true outer EOF after END and release the response right.
1400    ///
1401    /// # Errors
1402    /// Returns an error for a missing END or partial frame.
1403    pub fn finish_eof(mut self) -> Result<ResponseCapability, Error> {
1404        if !self.ended || !self.frame.is_empty() || self.context.is_none() {
1405            return Err(Error::MalformedEnvelope);
1406        }
1407        Ok(ResponseCapability(
1408            self.response_material
1409                .take()
1410                .ok_or(Error::MalformedEnvelope)?,
1411        ))
1412    }
1413}
1414
1415impl Server {
1416    /// Return the START boundary once its bounded frame length is known.
1417    ///
1418    /// # Errors
1419    /// Returns a parse, version, suite, or size error.
1420    pub fn stream_start_length(&self, input: &[u8]) -> Result<Option<usize>, Error> {
1421        stream_start_length(input, max_stream_start_ciphertext(self.limits))
1422    }
1423
1424    /// Read the exact public prefix and START frame before credential lookup.
1425    ///
1426    /// # Errors
1427    /// Returns a parse, limit, or recipient-key error.
1428    pub fn preparse_stream(&self, first: &[u8]) -> Result<PreparsedStreamRequest, Error> {
1429        let parsed = parse_stream_start(first, max_stream_start_ciphertext(self.limits))?;
1430        if parsed.key_id != self.recipient_key_id {
1431            return Err(Error::UnknownRecipientKey);
1432        }
1433        Ok(PreparsedStreamRequest {
1434            credential: CredentialRequest {
1435                psk_id: parsed.psk_id.to_vec(),
1436            },
1437            token: StreamStartToken {
1438                first: first.to_vec(),
1439            },
1440        })
1441    }
1442
1443    /// Authenticate START, then pause before replay admission.
1444    ///
1445    /// # Errors
1446    /// Returns a credential, time, authentication, or parse error.
1447    pub fn authenticate_stream(
1448        &self,
1449        token: StreamStartToken,
1450        psk: &[u8],
1451    ) -> Result<AuthenticatedStreamRequest, Error> {
1452        self.authenticate_stream_at(token, psk, unix_time_seconds()?)
1453    }
1454
1455    /// Authenticate START at a caller-supplied trusted Unix time.
1456    ///
1457    /// # Errors
1458    /// Returns a credential, time, authentication, or parse error.
1459    pub fn authenticate_stream_at(
1460        &self,
1461        token: StreamStartToken,
1462        psk: &[u8],
1463        now_unix_s: u64,
1464    ) -> Result<AuthenticatedStreamRequest, Error> {
1465        let StreamStartToken { first } = token;
1466        let max_upload_bytes = self.limits.max_request_bytes;
1467        let parsed = parse_stream_start(&first, max_stream_start_ciphertext(self.limits))?;
1468        if parsed.key_id != self.recipient_key_id {
1469            return Err(Error::UnknownRecipientKey);
1470        }
1471        validate_credential(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
1472        let encapped_key = <Kem as KemTrait>::EncappedKey::from_bytes(parsed.enc)
1473            .map_err(|_| Error::AuthenticationFailed)?;
1474        let psk_bundle =
1475            PskBundle::new(psk, parsed.psk_id).map_err(|_| Error::InvalidCredential)?;
1476        let mut context = setup_receiver::<HpkeAead, Kdf, Kem>(
1477            &OpModeR::Psk(psk_bundle),
1478            &self.recipient_private_key,
1479            &encapped_key,
1480            &stream_request_info(parsed.header),
1481        )
1482        .map_err(|_| Error::AuthenticationFailed)?;
1483        let mut clear = parsed.ciphertext.to_vec();
1484        let plain_end = clear.len() - TAG_LEN;
1485        let tag = AeadTag::<HpkeAead>::from_bytes(&clear[plain_end..])
1486            .map_err(|_| Error::AuthenticationFailed)?;
1487        let aad = stream_record_aad(
1488            parsed.header,
1489            0,
1490            u32::try_from(clear.len()).map_err(|_| Error::LimitExceeded)?,
1491        );
1492        context
1493            .open_inout_detached(InOutBuf::from(&mut clear[..plain_end]), &aad, &tag)
1494            .map_err(|_| Error::AuthenticationFailed)?;
1495        if clear.first() != Some(&STREAM_START) {
1496            return Err(Error::MalformedEnvelope);
1497        }
1498        let head = message::decode_stream_head(&clear[1..plain_end], self.limits)?;
1499        let content_length = message::stream_content_length(&head.headers)?;
1500        if content_length.is_some_and(|length| length > max_upload_bytes) {
1501            return Err(Error::LimitExceeded);
1502        }
1503        let retain_until_exclusive =
1504            replay_retention_deadline(parsed.issued_at_unix_s, now_unix_s)?;
1505        let replay_id = replay_id(parsed.header, parsed.enc);
1506        let response_material =
1507            export_response_material(&context, parsed.enc, head.method, self.limits)?;
1508        Ok(AuthenticatedStreamRequest {
1509            replay: ReplayRequest {
1510                id: replay_id,
1511                retain_until_exclusive,
1512            },
1513            token: StreamReplayToken {
1514                replay_id,
1515                retain_until_exclusive,
1516                head,
1517                reader: StreamRequestOpener {
1518                    context: Some(context),
1519                    header: parsed.header.to_vec(),
1520                    sequence: 1,
1521                    data_records: 0,
1522                    total_bytes: 0,
1523                    content_length,
1524                    max_upload_bytes,
1525                    frame: Vec::new(),
1526                    ended: false,
1527                    response_material: Some(response_material),
1528                },
1529            },
1530        })
1531    }
1532}