Skip to main content

rama_haproxy/protocol/
mod.rs

1//! A Proxy Protocol Parser written in Rust.
2//! Supports both text and binary versions of the header protocol.
3//!
4//! Forked from <https://github.com/misalcedo/ppp>; see
5//! `docs/thirdparty/fork/README.md`.
6
7mod ip;
8
9pub mod v1;
10pub mod v2;
11
12/// The canonical way to determine when a streamed header should be retried in a streaming context.
13/// The protocol states that servers may choose to support partial headers or to close the connection if the header is not present all at once.
14pub trait PartialResult {
15    /// Tests whether this `Result` is successful or whether the error is terminal.
16    /// A terminal error will not result in a success even with more bytes.
17    /// Retrying with the same -- or more -- input will not change the result.
18    fn is_complete(&self) -> bool {
19        !self.is_incomplete()
20    }
21
22    /// Tests whether this `Result` is incomplete.
23    /// An action that leads to an incomplete result may have a different result with more bytes.
24    /// Retrying with the same input will not change the result.
25    fn is_incomplete(&self) -> bool;
26}
27
28impl<T, E: PartialResult> PartialResult for Result<T, E> {
29    fn is_incomplete(&self) -> bool {
30        match self {
31            Ok(_) => false,
32            Err(error) => error.is_incomplete(),
33        }
34    }
35}
36
37impl PartialResult for v1::ParseError {
38    fn is_incomplete(&self) -> bool {
39        matches!(
40            self,
41            Self::Partial
42                | Self::MissingPrefix
43                | Self::MissingProtocol
44                | Self::MissingSourceAddress
45                | Self::MissingDestinationAddress
46                | Self::MissingSourcePort
47                | Self::MissingDestinationPort
48                | Self::MissingNewLine
49        )
50    }
51}
52
53impl PartialResult for v1::BinaryParseError {
54    fn is_incomplete(&self) -> bool {
55        match self {
56            Self::Parse(error) => error.is_incomplete(),
57            Self::InvalidUtf8(_) => false,
58        }
59    }
60}
61
62impl PartialResult for v2::ParseError {
63    fn is_incomplete(&self) -> bool {
64        matches!(self, Self::Incomplete(..) | Self::Partial(..))
65    }
66}
67
68/// An enumeration of the supported header version's parse results.
69/// Useful for parsing either version 1 or version 2 of the PROXY protocol.
70///
71/// ## Examples
72/// ```rust
73/// use rama_haproxy::protocol::{HeaderResult, PartialResult, v1, v2};
74///
75/// let input = "PROXY UNKNOWN\r\n";
76/// let header = HeaderResult::parse(input.as_bytes());
77///
78/// assert_eq!(header, Ok(v1::Header::new(input, v1::Addresses::Unknown)).into());
79/// ```
80#[derive(Debug, Clone, PartialEq, Eq)]
81#[must_use = "this `HeaderResult` may contain a V1 or V2 `Err` variant, which should be handled"]
82pub enum HeaderResult<'a> {
83    /// Version 1 of the PROXY protocol header.
84    V1(Result<v1::Header<'a>, v1::BinaryParseError>),
85    /// Version 2 of the PROXY protocol header.
86    V2(Result<v2::Header<'a>, v2::ParseError>),
87}
88
89impl<'a> From<Result<v1::Header<'a>, v1::BinaryParseError>> for HeaderResult<'a> {
90    fn from(result: Result<v1::Header<'a>, v1::BinaryParseError>) -> Self {
91        HeaderResult::V1(result)
92    }
93}
94
95impl<'a> From<Result<v2::Header<'a>, v2::ParseError>> for HeaderResult<'a> {
96    fn from(result: Result<v2::Header<'a>, v2::ParseError>) -> Self {
97        HeaderResult::V2(result)
98    }
99}
100
101impl PartialResult for HeaderResult<'_> {
102    fn is_incomplete(&self) -> bool {
103        match self {
104            Self::V1(result) => result.is_incomplete(),
105            Self::V2(result) => result.is_incomplete(),
106        }
107    }
108}
109
110impl<'a> HeaderResult<'a> {
111    /// Parses a PROXY protocol version 2 `Header`.
112    /// If the input is not a valid version 2 `Header`, attempts to parse a version 1 `Header`.
113    pub fn parse(input: &'a [u8]) -> Self {
114        let header = v2::Header::try_from(input);
115
116        if header.is_complete() && header.is_err() {
117            v1::Header::try_from(input).into()
118        } else {
119            header.into()
120        }
121    }
122}