proxy_protocol_rs/parse/mod.rs
1// Copyright (C) 2025-2026 Michael S. Klishin and Contributors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15pub(crate) mod tlv;
16mod v1;
17mod v2;
18
19use crate::error::ParseError;
20use crate::types::ProxyInfo;
21
22/// The 12-byte v2 Proxy Protocol signature
23pub const V2_SIGNATURE: &[u8; 12] = b"\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A";
24
25/// Parse a Proxy Protocol header (v1 or v2) from a byte slice
26///
27/// Returns the parsed `ProxyInfo` and the number of bytes consumed.
28///
29/// # Errors
30///
31/// - [`ParseError::Incomplete`] — the buffer does not yet contain a full header;
32/// the caller should read more data and retry
33/// - [`ParseError::NotProxyProtocol`] — the first bytes do not match any Proxy
34/// Protocol signature
35/// - [`ParseError::Invalid`] — the header is structurally malformed (bad version,
36/// unknown command, address family mismatch, truncated TLV, etc)
37/// - [`ParseError::CrcMismatch`] — a CRC32c TLV is present and the checksum
38/// does not match
39#[inline]
40#[must_use = "the parsed ProxyInfo contains the client address and metadata"]
41pub fn parse(buf: &[u8]) -> Result<(ProxyInfo, usize), ParseError> {
42 if buf.is_empty() {
43 return Err(ParseError::Incomplete);
44 }
45
46 match buf[0] {
47 0x0D => {
48 if buf.len() < 12 {
49 return Err(ParseError::Incomplete);
50 }
51 if buf[..12] == *V2_SIGNATURE {
52 v2::parse_v2(buf)
53 } else {
54 Err(ParseError::NotProxyProtocol)
55 }
56 }
57 b'P' => {
58 if buf.len() < 6 {
59 return Err(ParseError::Incomplete);
60 }
61 if buf.starts_with(b"PROXY ") {
62 v1::parse_v1(buf)
63 } else {
64 Err(ParseError::NotProxyProtocol)
65 }
66 }
67 _ => Err(ParseError::NotProxyProtocol),
68 }
69}