Skip to main content

hpke_http/
message.rs

1//! Canonical RFC 9292 known-length messages used by protocol version 1.
2//!
3//! This module implements only the small protocol subset that the engine uses.
4//! It does not depend on a general OHTTP/BHTTP package, accept indeterminate
5//! messages, informational responses, trailers, padding, or non-minimal integer
6//! encodings. Decode limits apply before any field or body allocation. The
7//! private body-coding extension uses a separately validated wire body; its
8//! logical content length is checked only after bounded decompression.
9
10use crate::{Error, Limits, Method};
11
12const HTTPS_SCHEME: &[u8] = b"https";
13const REQUEST_MODE: u64 = 0;
14const RESPONSE_MODE: u64 = 1;
15// Space for fixed framing, vector lengths, and the caller's AEAD tag.
16const ENCODING_CAPACITY_ALLOWANCE: usize = 128;
17const FORBIDDEN_HEADERS: &[&[u8]] = &[
18    b"connection",
19    b"expect",
20    b"host",
21    b"keep-alive",
22    b"proxy-authenticate",
23    b"proxy-authentication-info",
24    b"proxy-authorization",
25    b"proxy-connection",
26    b"te",
27    b"trailer",
28    b"transfer-encoding",
29    b"upgrade",
30];
31
32/// One canonical end-to-end HTTP field in an ordered field section.
33#[derive(Clone, Debug, Eq, PartialEq)]
34pub struct HeaderField {
35    /// Lower-case HTTP field name. Repeated names preserve their original order.
36    pub name: Vec<u8>,
37    /// Canonical ASCII field value without outer optional whitespace.
38    pub value: Vec<u8>,
39}
40
41/// A complete plaintext request supplied to or returned by the engine.
42#[derive(Clone, Debug, Eq, PartialEq)]
43pub struct Request {
44    /// Supported HTTP method.
45    pub method: Method,
46    /// URI authority, such as `api.example.com` or `api.example.com:8443`.
47    pub authority: Vec<u8>,
48    /// Path and optional query, beginning with `/`, or `*` for `OPTIONS`.
49    pub path: Vec<u8>,
50    /// Ordered end-to-end HTTP fields.
51    pub headers: Vec<HeaderField>,
52    /// Complete bounded request body. Empty bodies remain authenticated.
53    pub body: Vec<u8>,
54}
55
56/// A complete plaintext response supplied to or returned by the engine.
57#[derive(Clone, Debug, Eq, PartialEq)]
58pub struct Response {
59    /// Final HTTP status code in the inclusive range 200 through 599.
60    pub status: u16,
61    /// Ordered end-to-end HTTP fields.
62    pub headers: Vec<HeaderField>,
63    /// Complete bounded response body. Empty bodies remain authenticated.
64    pub body: Vec<u8>,
65}
66
67pub(crate) fn encode_request(request: &Request, limits: Limits) -> Result<Vec<u8>, Error> {
68    encode_request_with_body(request, &request.body, limits)
69}
70
71/// Encode a validated logical message with a separately coded wire body.
72/// The private compression frame must be decoded before this becomes an HTTP message.
73pub(crate) fn encode_request_with_body(
74    request: &Request,
75    wire_body: &[u8],
76    limits: Limits,
77) -> Result<Vec<u8>, Error> {
78    validate_request(request, limits)?;
79    if wire_body.len() > limits.max_body_len {
80        return Err(Error::LimitExceeded);
81    }
82    let fields = encode_fields(&request.headers)?;
83    let mut output = Vec::with_capacity(
84        wire_body.len()
85            + fields.len()
86            + request.authority.len()
87            + request.path.len()
88            + ENCODING_CAPACITY_ALLOWANCE,
89    );
90    write_varint(&mut output, REQUEST_MODE)?;
91    write_vector(&mut output, request.method.as_bytes())?;
92    write_vector(&mut output, HTTPS_SCHEME)?;
93    write_vector(&mut output, &request.authority)?;
94    write_vector(&mut output, &request.path)?;
95    write_vector(&mut output, &fields)?;
96    write_vector(&mut output, wire_body)?;
97    write_vector(&mut output, &[])?;
98    Ok(output)
99}
100
101pub(crate) fn decode_request(input: &[u8], limits: Limits) -> Result<Request, Error> {
102    decode_request_inner(input, limits, false)
103}
104
105pub(crate) fn decode_coded_request(input: &[u8], limits: Limits) -> Result<Request, Error> {
106    decode_request_inner(input, limits, true)
107}
108
109fn decode_request_inner(input: &[u8], limits: Limits, coded_body: bool) -> Result<Request, Error> {
110    validate_encoded_length(input, limits)?;
111    let mut reader = SliceReader::new(input);
112    if reader.read_varint()? != REQUEST_MODE {
113        return Err(Error::MalformedEnvelope);
114    }
115    let method = Method::from_bytes(reader.read_vector()?)?;
116    if reader.read_vector()? != HTTPS_SCHEME {
117        return Err(Error::MalformedEnvelope);
118    }
119    let authority = reader.read_vector()?;
120    let path = reader.read_vector()?;
121    validate_target(method, authority, path, limits).map_err(decode_validation_error)?;
122    let headers = decode_fields(reader.read_vector()?, limits).map_err(decode_validation_error)?;
123    let body = reader.read_vector()?;
124    if body.len() > limits.max_body_len {
125        return Err(Error::LimitExceeded);
126    }
127    if !reader.read_vector()?.is_empty() || !reader.is_finished() {
128        return Err(Error::MalformedEnvelope);
129    }
130    let request = Request {
131        method,
132        authority: authority.to_vec(),
133        path: path.to_vec(),
134        headers,
135        body: body.to_vec(),
136    };
137    if !coded_body {
138        validate_decoded_request_body(&request)?;
139    }
140    Ok(request)
141}
142
143pub(crate) fn validate_decoded_request_body(request: &Request) -> Result<(), Error> {
144    validate_content_length(
145        &request.headers,
146        request.body.len(),
147        ContentLengthRule::Exact,
148    )
149    .map_err(decode_validation_error)
150}
151
152pub(crate) fn encode_response(
153    response: &Response,
154    method: Method,
155    limits: Limits,
156) -> Result<Vec<u8>, Error> {
157    encode_response_with_body(response, &response.body, method, limits)
158}
159
160pub(crate) fn encode_response_with_body(
161    response: &Response,
162    wire_body: &[u8],
163    method: Method,
164    limits: Limits,
165) -> Result<Vec<u8>, Error> {
166    validate_response(response, method, limits)?;
167    if wire_body.len() > limits.max_body_len {
168        return Err(Error::LimitExceeded);
169    }
170    let fields = encode_fields(&response.headers)?;
171    let mut output =
172        Vec::with_capacity(wire_body.len() + fields.len() + ENCODING_CAPACITY_ALLOWANCE);
173    write_varint(&mut output, RESPONSE_MODE)?;
174    write_varint(&mut output, u64::from(response.status))?;
175    write_vector(&mut output, &fields)?;
176    write_vector(&mut output, wire_body)?;
177    write_vector(&mut output, &[])?;
178    Ok(output)
179}
180
181pub(crate) fn decode_response(
182    input: &[u8],
183    method: Method,
184    limits: Limits,
185) -> Result<Response, Error> {
186    decode_response_inner(input, method, limits, false)
187}
188
189pub(crate) fn decode_coded_response(
190    input: &[u8],
191    method: Method,
192    limits: Limits,
193) -> Result<Response, Error> {
194    decode_response_inner(input, method, limits, true)
195}
196
197fn decode_response_inner(
198    input: &[u8],
199    method: Method,
200    limits: Limits,
201    coded_body: bool,
202) -> Result<Response, Error> {
203    validate_encoded_length(input, limits)?;
204    let mut reader = SliceReader::new(input);
205    if reader.read_varint()? != RESPONSE_MODE {
206        return Err(Error::MalformedEnvelope);
207    }
208    let status = u16::try_from(reader.read_varint()?).map_err(|_| Error::MalformedEnvelope)?;
209    if !(200..=599).contains(&status) {
210        return Err(Error::MalformedEnvelope);
211    }
212    let headers = decode_fields(reader.read_vector()?, limits).map_err(decode_validation_error)?;
213    let body = reader.read_vector()?;
214    if body.len() > limits.max_body_len {
215        return Err(Error::LimitExceeded);
216    }
217    if !reader.read_vector()?.is_empty() || !reader.is_finished() {
218        return Err(Error::MalformedEnvelope);
219    }
220    let response = Response {
221        status,
222        headers,
223        body: body.to_vec(),
224    };
225    if !coded_body {
226        validate_decoded_response_body(&response, method)?;
227    }
228    Ok(response)
229}
230
231pub(crate) fn validate_decoded_response_body(
232    response: &Response,
233    method: Method,
234) -> Result<(), Error> {
235    validate_content_length(
236        &response.headers,
237        response.body.len(),
238        response_content_length_rule(method, response.status),
239    )
240    .map_err(decode_validation_error)
241}
242
243const fn decode_validation_error(error: Error) -> Error {
244    match error {
245        Error::InvalidConfiguration => Error::MalformedEnvelope,
246        other => other,
247    }
248}
249
250fn encode_fields(headers: &[HeaderField]) -> Result<Vec<u8>, Error> {
251    let mut output = Vec::new();
252    for field in headers {
253        write_vector(&mut output, &field.name)?;
254        write_vector(&mut output, &field.value)?;
255    }
256    Ok(output)
257}
258
259fn decode_fields(input: &[u8], limits: Limits) -> Result<Vec<HeaderField>, Error> {
260    let mut reader = SliceReader::new(input);
261    let mut headers: Vec<HeaderField> = Vec::new();
262    let mut header_bytes = 0_usize;
263    while !reader.is_finished() {
264        if headers.len() >= limits.max_header_count {
265            return Err(Error::LimitExceeded);
266        }
267        let name = reader.read_vector()?;
268        let value = reader.read_vector()?;
269        header_bytes = header_bytes
270            .checked_add(name.len())
271            .and_then(|length| length.checked_add(value.len()))
272            .ok_or(Error::LimitExceeded)?;
273        if header_bytes > limits.max_header_bytes {
274            return Err(Error::LimitExceeded);
275        }
276        validate_header_parts(name, value)?;
277        headers.push(HeaderField {
278            name: name.to_vec(),
279            value: value.to_vec(),
280        });
281    }
282    Ok(headers)
283}
284
285fn validate_encoded_length(input: &[u8], limits: Limits) -> Result<(), Error> {
286    if input.len() > limits.max_encoded_message_len() {
287        return Err(Error::LimitExceeded);
288    }
289    Ok(())
290}
291
292fn validate_request(request: &Request, limits: Limits) -> Result<(), Error> {
293    validate_target(request.method, &request.authority, &request.path, limits)?;
294    validate_payload(&request.headers, &request.body, limits)?;
295    validate_content_length(
296        &request.headers,
297        request.body.len(),
298        ContentLengthRule::Exact,
299    )
300}
301
302fn validate_response(response: &Response, method: Method, limits: Limits) -> Result<(), Error> {
303    if !(200..=599).contains(&response.status) {
304        return Err(Error::InvalidConfiguration);
305    }
306    validate_payload(&response.headers, &response.body, limits)?;
307    validate_content_length(
308        &response.headers,
309        response.body.len(),
310        response_content_length_rule(method, response.status),
311    )
312}
313
314fn validate_target(
315    method: Method,
316    authority: &[u8],
317    path: &[u8],
318    limits: Limits,
319) -> Result<(), Error> {
320    let target_len = authority
321        .len()
322        .checked_add(path.len())
323        .ok_or(Error::LimitExceeded)?;
324    if target_len > limits.max_target_len {
325        return Err(Error::LimitExceeded);
326    }
327    if !valid_authority(authority) || !valid_request_target(method, path) {
328        return Err(Error::InvalidConfiguration);
329    }
330    Ok(())
331}
332
333fn valid_authority(authority: &[u8]) -> bool {
334    if authority.is_empty() || !authority.iter().all(u8::is_ascii_graphic) {
335        return false;
336    }
337    if authority[0] == b'[' {
338        return valid_ip_literal_authority(authority);
339    }
340
341    let (host, port) = authority
342        .iter()
343        .rposition(|byte| *byte == b':')
344        .map_or((authority, None), |separator| {
345            (&authority[..separator], Some(&authority[separator + 1..]))
346        });
347    if host.contains(&b':') || !valid_reg_name(host) || !port.is_none_or(valid_port) {
348        return false;
349    }
350    let numeric_ipv4_candidate = host.split(|byte| *byte == b'.').count() == 4
351        && host
352            .iter()
353            .all(|byte| byte.is_ascii_digit() || *byte == b'.');
354    !numeric_ipv4_candidate
355        || std::str::from_utf8(host)
356            .ok()
357            .and_then(|value| value.parse::<std::net::Ipv4Addr>().ok())
358            .is_some()
359}
360
361fn valid_ip_literal_authority(authority: &[u8]) -> bool {
362    let Some(close) = authority.iter().position(|byte| *byte == b']') else {
363        return false;
364    };
365    let literal = &authority[1..close];
366    let suffix = &authority[close + 1..];
367    let valid_suffix = suffix.is_empty() || suffix.strip_prefix(b":").is_some_and(valid_port);
368    if literal.is_empty() || !valid_suffix {
369        return false;
370    }
371    let Ok(value) = std::str::from_utf8(literal) else {
372        return false;
373    };
374    value.parse::<std::net::Ipv6Addr>().is_ok() || valid_ipv_future(literal)
375}
376
377fn valid_ipv_future(value: &[u8]) -> bool {
378    let Some(version_and_address) = value
379        .strip_prefix(b"v")
380        .or_else(|| value.strip_prefix(b"V"))
381    else {
382        return false;
383    };
384    let Some(separator) = version_and_address.iter().position(|byte| *byte == b'.') else {
385        return false;
386    };
387    let version = &version_and_address[..separator];
388    let address = &version_and_address[separator + 1..];
389    !version.is_empty()
390        && version.iter().all(u8::is_ascii_hexdigit)
391        && !address.is_empty()
392        && address
393            .iter()
394            .copied()
395            .all(|byte| is_unreserved(byte) || is_sub_delim(byte) || byte == b':')
396}
397
398fn valid_reg_name(value: &[u8]) -> bool {
399    if value.is_empty() {
400        return false;
401    }
402    let mut position = 0;
403    while position < value.len() {
404        let byte = value[position];
405        if is_unreserved(byte) || is_sub_delim(byte) {
406            position += 1;
407        } else if byte == b'%'
408            && value
409                .get(position + 1..position + 3)
410                .is_some_and(|encoded| encoded.iter().all(u8::is_ascii_hexdigit))
411        {
412            position += 3;
413        } else {
414            return false;
415        }
416    }
417    true
418}
419
420fn valid_port(value: &[u8]) -> bool {
421    if value.is_empty() || !value.iter().all(u8::is_ascii_digit) {
422        return false;
423    }
424    let significant = value
425        .iter()
426        .position(|byte| *byte != b'0')
427        .map_or(&[][..], |position| &value[position..]);
428    significant.is_empty()
429        || (significant.len() <= 5
430            && std::str::from_utf8(significant)
431                .ok()
432                .and_then(|port| port.parse::<u16>().ok())
433                .is_some())
434}
435
436const fn is_unreserved(byte: u8) -> bool {
437    byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'.' | b'_' | b'~')
438}
439
440const fn is_sub_delim(byte: u8) -> bool {
441    matches!(
442        byte,
443        b'!' | b'$' | b'&' | b'\'' | b'(' | b')' | b'*' | b'+' | b',' | b';' | b'='
444    )
445}
446
447fn validate_payload(headers: &[HeaderField], body: &[u8], limits: Limits) -> Result<(), Error> {
448    if headers.len() > limits.max_header_count || body.len() > limits.max_body_len {
449        return Err(Error::LimitExceeded);
450    }
451    let mut header_bytes = 0_usize;
452    for field in headers {
453        validate_header_parts(&field.name, &field.value)?;
454        header_bytes = header_bytes
455            .checked_add(field.name.len())
456            .and_then(|length| length.checked_add(field.value.len()))
457            .ok_or(Error::LimitExceeded)?;
458    }
459    if header_bytes > limits.max_header_bytes {
460        return Err(Error::LimitExceeded);
461    }
462    Ok(())
463}
464
465#[derive(Clone, Copy)]
466enum ContentLengthRule {
467    Exact,
468    Metadata,
469    Forbidden,
470}
471
472fn response_content_length_rule(method: Method, status: u16) -> ContentLengthRule {
473    if status == 204 {
474        ContentLengthRule::Forbidden
475    } else if method == Method::Head || status == 304 {
476        ContentLengthRule::Metadata
477    } else {
478        ContentLengthRule::Exact
479    }
480}
481
482fn validate_content_length(
483    headers: &[HeaderField],
484    body_len: usize,
485    rule: ContentLengthRule,
486) -> Result<(), Error> {
487    let mut values = headers
488        .iter()
489        .filter(|field| field.name == b"content-length")
490        .map(|field| field.value.as_slice());
491    let Some(value) = values.next() else {
492        return Ok(());
493    };
494    if values.next().is_some() || value.is_empty() || !value.iter().all(u8::is_ascii_digit) {
495        return Err(Error::InvalidConfiguration);
496    }
497    match rule {
498        ContentLengthRule::Forbidden => Err(Error::InvalidConfiguration),
499        ContentLengthRule::Metadata => Ok(()),
500        ContentLengthRule::Exact => {
501            let significant = value
502                .iter()
503                .position(|byte| *byte != b'0')
504                .map_or(&[][..], |position| &value[position..]);
505            let expected = body_len.to_string();
506            let matches = if significant.is_empty() {
507                body_len == 0
508            } else {
509                significant == expected.as_bytes()
510            };
511            if matches {
512                Ok(())
513            } else {
514                Err(Error::InvalidConfiguration)
515            }
516        }
517    }
518}
519
520fn validate_header_parts(name: &[u8], value: &[u8]) -> Result<(), Error> {
521    if name.is_empty()
522        || !name.iter().copied().all(is_lower_tchar)
523        || FORBIDDEN_HEADERS.contains(&name)
524        || !value
525            .iter()
526            .all(|byte| *byte == b'\t' || (b' '..=b'~').contains(byte))
527        || value.first().is_some_and(|byte| is_ows(*byte))
528        || value.last().is_some_and(|byte| is_ows(*byte))
529    {
530        return Err(Error::InvalidConfiguration);
531    }
532    Ok(())
533}
534
535fn valid_request_target(method: Method, path: &[u8]) -> bool {
536    if path == b"*" {
537        return method == Method::Options;
538    }
539    if !path.starts_with(b"/")
540        || path.contains(&b'#')
541        || path.contains(&b'\\')
542        || !path.iter().all(u8::is_ascii_graphic)
543        || !valid_percent_encoding(path)
544    {
545        return false;
546    }
547    let path_only = path.split(|byte| *byte == b'?').next().unwrap_or_default();
548    let Some(decoded_path) = decode_path(path_only) else {
549        return false;
550    };
551    std::str::from_utf8(&decoded_path).is_ok()
552        && !decoded_path
553            .iter()
554            .any(|byte| byte.is_ascii_control() || *byte == b'\\')
555        && !decoded_path.split(|byte| *byte == b'/').any(is_dot_segment)
556}
557
558fn decode_path(path: &[u8]) -> Option<Vec<u8>> {
559    let mut decoded = Vec::with_capacity(path.len());
560    let mut position = 0;
561    while position < path.len() {
562        if path[position] == b'%' {
563            let high = hex_value(*path.get(position + 1)?)?;
564            let low = hex_value(*path.get(position + 2)?)?;
565            let byte = high.checked_mul(16)?.checked_add(low)?;
566            if byte == b'/' {
567                return None;
568            }
569            decoded.push(byte);
570            position += 3;
571        } else {
572            decoded.push(path[position]);
573            position += 1;
574        }
575    }
576    Some(decoded)
577}
578
579const fn hex_value(byte: u8) -> Option<u8> {
580    match byte {
581        b'0'..=b'9' => Some(byte - b'0'),
582        b'a'..=b'f' => Some(byte - b'a' + 10),
583        b'A'..=b'F' => Some(byte - b'A' + 10),
584        _ => None,
585    }
586}
587
588fn valid_percent_encoding(value: &[u8]) -> bool {
589    let mut position = 0;
590    while position < value.len() {
591        if value[position] == b'%' {
592            let Some(encoded) = value.get(position + 1..position + 3) else {
593                return false;
594            };
595            if !encoded.iter().all(u8::is_ascii_hexdigit) {
596                return false;
597            }
598            position += 3;
599        } else {
600            position += 1;
601        }
602    }
603    true
604}
605
606fn is_dot_segment(segment: &[u8]) -> bool {
607    segment == b"." || segment == b".."
608}
609
610const fn is_ows(byte: u8) -> bool {
611    matches!(byte, b' ' | b'\t')
612}
613
614const fn is_lower_tchar(byte: u8) -> bool {
615    byte.is_ascii_lowercase()
616        || byte.is_ascii_digit()
617        || matches!(
618            byte,
619            b'!' | b'#'
620                | b'$'
621                | b'%'
622                | b'&'
623                | b'\''
624                | b'*'
625                | b'+'
626                | b'-'
627                | b'.'
628                | b'^'
629                | b'_'
630                | b'`'
631                | b'|'
632                | b'~'
633        )
634}
635
636fn write_vector(output: &mut Vec<u8>, value: &[u8]) -> Result<(), Error> {
637    let length = u64::try_from(value.len()).map_err(|_| Error::LimitExceeded)?;
638    write_varint(output, length)?;
639    output.extend_from_slice(value);
640    Ok(())
641}
642
643fn write_varint(output: &mut Vec<u8>, value: u64) -> Result<(), Error> {
644    match value {
645        0..=0x3f => output.push(u8::try_from(value).map_err(|_| Error::LimitExceeded)?),
646        0x40..=0x3fff => {
647            let encoded = u16::try_from(value).map_err(|_| Error::LimitExceeded)? | 0x4000;
648            output.extend_from_slice(&encoded.to_be_bytes());
649        }
650        0x4000..=0x3fff_ffff => {
651            let encoded = u32::try_from(value).map_err(|_| Error::LimitExceeded)? | 0x8000_0000;
652            output.extend_from_slice(&encoded.to_be_bytes());
653        }
654        0x4000_0000..=0x3fff_ffff_ffff_ffff => {
655            output.extend_from_slice(&(value | 0xc000_0000_0000_0000).to_be_bytes());
656        }
657        _ => return Err(Error::LimitExceeded),
658    }
659    Ok(())
660}
661
662struct SliceReader<'a> {
663    input: &'a [u8],
664    position: usize,
665}
666
667impl<'a> SliceReader<'a> {
668    const fn new(input: &'a [u8]) -> Self {
669        Self { input, position: 0 }
670    }
671
672    fn read_varint(&mut self) -> Result<u64, Error> {
673        let first = *self
674            .input
675            .get(self.position)
676            .ok_or(Error::MalformedEnvelope)?;
677        let length = 1_usize << (first >> 6);
678        let end = self
679            .position
680            .checked_add(length)
681            .ok_or(Error::MalformedEnvelope)?;
682        let encoded = self
683            .input
684            .get(self.position..end)
685            .ok_or(Error::MalformedEnvelope)?;
686        let mut value = u64::from(first & 0x3f);
687        for byte in &encoded[1..] {
688            value = (value << 8) | u64::from(*byte);
689        }
690        if varint_length(value) != length {
691            return Err(Error::MalformedEnvelope);
692        }
693        self.position = end;
694        Ok(value)
695    }
696
697    fn read_vector(&mut self) -> Result<&'a [u8], Error> {
698        let length = usize::try_from(self.read_varint()?).map_err(|_| Error::MalformedEnvelope)?;
699        let end = self
700            .position
701            .checked_add(length)
702            .ok_or(Error::MalformedEnvelope)?;
703        let value = self
704            .input
705            .get(self.position..end)
706            .ok_or(Error::MalformedEnvelope)?;
707        self.position = end;
708        Ok(value)
709    }
710
711    const fn is_finished(&self) -> bool {
712        self.position == self.input.len()
713    }
714}
715
716const fn varint_length(value: u64) -> usize {
717    match value {
718        0..=0x3f => 1,
719        0x40..=0x3fff => 2,
720        0x4000..=0x3fff_ffff => 4,
721        _ => 8,
722    }
723}
724
725#[cfg(test)]
726mod tests {
727    use super::{SliceReader, decode_fields, decode_request, encode_request, write_varint};
728    use crate::{Error, HeaderField, Limits, Method, Request};
729
730    #[test]
731    fn field_decoder_stops_at_the_configured_count() {
732        let limits = Limits {
733            max_header_count: 2,
734            ..Limits::default()
735        };
736        let encoded = [1, b'a', 0, 1, b'b', 0, 1, b'c', 0];
737        assert_eq!(decode_fields(&encoded, limits), Err(Error::LimitExceeded));
738    }
739
740    #[test]
741    fn varint_round_trips_boundaries_and_rejects_non_minimal_values() -> Result<(), Error> {
742        let cases: &[(u64, &[u8])] = &[
743            (0, &[0x00]),
744            (63, &[0x3f]),
745            (64, &[0x40, 0x40]),
746            (16_383, &[0x7f, 0xff]),
747            (16_384, &[0x80, 0x00, 0x40, 0x00]),
748            (0x3fff_ffff, &[0xbf, 0xff, 0xff, 0xff]),
749            (
750                0x4000_0000,
751                &[0xc0, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00],
752            ),
753        ];
754        for (value, expected) in cases {
755            let mut encoded = Vec::new();
756            write_varint(&mut encoded, *value)?;
757            assert_eq!(&encoded, expected);
758            let mut reader = SliceReader::new(expected);
759            assert_eq!(reader.read_varint()?, *value);
760            assert!(reader.is_finished());
761        }
762        let mut non_minimal = SliceReader::new(&[0x40, 0x01]);
763        assert_eq!(non_minimal.read_varint(), Err(Error::MalformedEnvelope));
764        Ok(())
765    }
766
767    #[test]
768    fn request_decoder_rechecks_authenticated_content_length() -> Result<(), Error> {
769        let request = Request {
770            method: Method::Post,
771            authority: b"api.example.test".to_vec(),
772            path: b"/items".to_vec(),
773            headers: vec![HeaderField {
774                name: b"content-length".to_vec(),
775                value: b"4".to_vec(),
776            }],
777            body: b"body".to_vec(),
778        };
779        let mut encoded = encode_request(&request, Limits::default())?;
780        let name_offset = encoded
781            .windows(b"content-length".len())
782            .position(|window| window == b"content-length")
783            .ok_or(Error::MalformedEnvelope)?;
784        let value_offset = name_offset + b"content-length".len() + 1;
785        encoded[value_offset] = b'5';
786        assert_eq!(
787            decode_request(&encoded, Limits::default()).err(),
788            Some(Error::MalformedEnvelope)
789        );
790        Ok(())
791    }
792
793    #[test]
794    fn request_decoder_rechecks_authenticated_authority_syntax() -> Result<(), Error> {
795        let request = Request {
796            method: Method::Get,
797            authority: b"api.example".to_vec(),
798            path: b"/items".to_vec(),
799            headers: Vec::new(),
800            body: Vec::new(),
801        };
802        let mut encoded = encode_request(&request, Limits::default())?;
803        let authority_offset = encoded
804            .windows(b"api.example".len())
805            .position(|window| window == b"api.example")
806            .ok_or(Error::MalformedEnvelope)?;
807        encoded[authority_offset] = b'[';
808        assert_eq!(
809            decode_request(&encoded, Limits::default()).err(),
810            Some(Error::MalformedEnvelope)
811        );
812        Ok(())
813    }
814}