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