Skip to main content

hpke_http/
message.rs

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