haprox-rs 0.3.2

A HaProxy v1/v2 protocol parser.
Documentation
/*-
 * haprox-rs - a HaProxy protocol parser.
 * 
 * Copyright 2025 (c) Aleksandr Morozov
 * The scram-rs crate can be redistributed and/or modified
 * under the terms of either of the following licenses:
 *
 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
 *
 *   2. The MIT License (MIT)
 *                     
 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
 */

pub mod protocol_parser;
pub mod protocol_composer;

use core::fmt;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};

use crate::{HaProxRes, map_error, protocol_raw, return_error};


#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProtocolV1Inet
{
    Tcp4, Tcp6, None
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HapProtoV1
{
    TCP4
    {
        src_addr: Ipv4Addr,
        dst_addr: Ipv4Addr,
        src_port: u16,
        dst_port: u16,
    },
    TCP6
    {
        src_addr: Ipv6Addr,
        dst_addr: Ipv6Addr,
        src_port: u16,
        dst_port: u16,
    },
    UNKNOWN,
}

impl fmt::Display for HapProtoV1
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result 
    {
        let s: String = self.into();

        write!(f, "{}", s)
    }
}

impl From<&HapProtoV1> for String
{
    fn from(value: &HapProtoV1) -> Self 
    {
        match value
        {
            HapProtoV1::TCP4{ src_addr, dst_addr, src_port, dst_port } =>
            {
                let out = 
                    [
                        protocol_raw::HEADER_MAGIC_V1_STR, protocol_raw::HEADER_V1_WSPACE,
                        protocol_raw::HEADER_V1_INET_TCP4, protocol_raw::HEADER_V1_WSPACE,
                        src_addr.to_string().as_str(), protocol_raw::HEADER_V1_WSPACE,
                        dst_addr.to_string().as_str(), protocol_raw::HEADER_V1_WSPACE,
                        src_port.to_string().as_str(), protocol_raw::HEADER_V1_WSPACE,
                        dst_port.to_string().as_str(), protocol_raw::HEADER_V1_EOM
                    ]
                    .concat();

                return out;
            },
            HapProtoV1::TCP6{ src_addr, dst_addr, src_port, dst_port } =>
            {
                let out = 
                    [
                        protocol_raw::HEADER_MAGIC_V1_STR, protocol_raw::HEADER_V1_WSPACE,
                        protocol_raw::HEADER_V1_INET_TCP6, protocol_raw::HEADER_V1_WSPACE,
                        src_addr.segments().iter().map(|v| format!("{:04x}", v)).collect::<Vec<String>>().join(":").as_str(), protocol_raw::HEADER_V1_WSPACE,
                        dst_addr.segments().iter().map(|v| format!("{:04x}", v)).collect::<Vec<String>>().join(":").as_str(), protocol_raw::HEADER_V1_WSPACE,
                        src_port.to_string().as_str(), protocol_raw::HEADER_V1_WSPACE,
                        dst_port.to_string().as_str(), protocol_raw::HEADER_V1_EOM
                    ]
                    .concat();

                return out;
            },
            HapProtoV1::UNKNOWN =>
            {
                let out = 
                    [
                        protocol_raw::HEADER_MAGIC_V1_STR, protocol_raw::HEADER_V1_WSPACE,
                        protocol_raw::HEADER_V1_INET_UNKNWON, protocol_raw::HEADER_V1_EOM
                    ]
                    .concat();

                return out;
            }
        }
        
    }
}


impl From<HapProtoV1> for String
{
    fn from(value: HapProtoV1) -> Self 
    {
        return (&value).into();
    }
}

impl HapProtoV1
{
    pub(super) 
    fn unknown() -> Self
    {
        return Self::UNKNOWN;
    }

    pub(super) 
    fn from_ip_port(
        src_ip: IpAddr, 
        dst_ip: IpAddr, 
        src_port: u16, 
        dst_port: u16
    ) -> HaProxRes<Self>
    {
        
        if src_ip.is_ipv4() == true
        {
            let IpAddr::V4(dst_addr) = dst_ip
                else
                {
                    return_error!(ArgumentEinval, "dst_addr is not IPv4");
                };

            let IpAddr::V4(src_addr) = src_ip
                else
                {
                    return_error!(ArgumentEinval, "src_addr is not IPv4");
                };

            return Ok(
                Self::TCP4{ src_addr, dst_addr, src_port, dst_port }
            )
        }
        else
        {
            let IpAddr::V6(src_addr) = src_ip
                else
                {
                    return_error!(ArgumentEinval, "src_addr is not IPv6");
                };

            let IpAddr::V6(dst_addr) = dst_ip
                else
                {
                    return_error!(ArgumentEinval, "dst_addr is not IPv6");
                };

            return Ok(
                Self::TCP6{ src_addr, dst_addr, src_port, dst_port }
            )
        }
    }

    pub 
    fn from_raw(
        inet: &str, 
        src_ip: Option<&str>, 
        dst_ip: Option<&str>, 
        src_port: Option<&str>, 
        dst_port: Option<&str>
    ) -> HaProxRes<Self>
    {
        if inet == protocol_raw::HEADER_V1_INET_UNKNWON
        {
            return Ok(Self::UNKNOWN);
        }

        if src_ip.is_none() == true || dst_ip.is_none() == true ||
            src_port.is_none() == true || dst_port.is_none() == true
        {
            return_error!(MalformedData, "missing field/s '{:?}' '{:?}' '{:?}' '{:?}'", 
                src_ip, dst_ip, src_port, dst_port);
        }

        if inet == protocol_raw::HEADER_V1_INET_TCP4
        {
            return Ok(
                Self::TCP4 
                { 
                    src_addr: 
                        src_ip.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse src_addr '{}' err: '{}", src_ip.unwrap(), e)
                            )?,
                    dst_addr: 
                        dst_ip.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse dst_addr '{}' err: '{}", dst_ip.unwrap(), e)
                            )?, 
                    src_port: 
                        src_port.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse src_port '{}' err: '{}", src_port.unwrap(), e)
                            )?, 
                    dst_port: 
                        dst_port.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse dst_port '{}' err: '{}", dst_port.unwrap(), e)
                            )?
                }
            );
        }
        else if inet == protocol_raw::HEADER_V1_INET_TCP6
        {
            return Ok(
                Self::TCP6 
                { 
                    src_addr: 
                        src_ip.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse src_addr '{}' err: '{}", src_ip.unwrap(), e)
                            )?,
                    dst_addr: 
                        dst_ip.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse dst_addr '{}' err: '{}", dst_ip.unwrap(), e)
                            )?, 
                    src_port: 
                        src_port.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse src_port '{}' err: '{}", src_port.unwrap(), e)
                            )?, 
                    dst_port: 
                        dst_port.as_ref().unwrap().parse().map_err(|e| 
                                map_error!(MalformedData, "cannot parse dst_port '{}' err: '{}", dst_port.unwrap(), e)
                            )?
                }
            );
        }
        else
        {
            return_error!(MalformedData, "unknown INET: '{}'", inet);
        }
    }

    pub 
    fn get_src_addr(&self) -> Option<IpAddr>
    {
        match self
        {
            Self::TCP4 { src_addr, .. } => 
                return Some(IpAddr::V4(*src_addr)),
            Self::TCP6 { src_addr, .. } => 
                return Some(IpAddr::V6(*src_addr)),
            Self::UNKNOWN => 
                return None,
        }
    }

    pub 
    fn get_dst_addr(&self) -> Option<IpAddr>
    {
        match self
        {
            Self::TCP4 { dst_addr, .. } => 
                return Some(IpAddr::V4(*dst_addr)),
            Self::TCP6 { dst_addr, .. } => 
                return Some(IpAddr::V6(*dst_addr)),
            Self::UNKNOWN => 
                return None,
        }
    }

    pub 
    fn get_src_port(&self) -> Option<u16>
    {
        match self
        {
            Self::TCP4 { src_port, .. } => 
                return Some(*src_port),
            Self::TCP6 { src_port, .. } => 
                return Some(*src_port),
            Self::UNKNOWN => 
                return None,
        }
    }

    pub 
    fn get_dst_port(&self) -> Option<u16>
    {
        match self
        {
            Self::TCP4 { dst_port, .. } => 
                return Some(*dst_port),
            Self::TCP6 { dst_port, .. } => 
                return Some(*dst_port),
            Self::UNKNOWN => 
                return None,
        }
    }

    pub 
    fn get_inet(&self) -> ProtocolV1Inet
    {
        match self
        {
            Self::TCP4{ .. } => 
                return ProtocolV1Inet::Tcp4,
            Self::TCP6 { .. } => 
                return ProtocolV1Inet::Tcp6,
            Self::UNKNOWN => 
                return ProtocolV1Inet::None
        }
    }
}

#[cfg(test)]
mod tests_proto
{
    use crate::HapProtoV1;

    #[test]
    fn simple_test1()
    {
        let s: String = 
            HapProtoV1::TCP4 
            { 
                src_addr: "255.255.255.255".parse().unwrap(), 
                dst_addr: "255.255.255.255".parse().unwrap(), 
                src_port: 65535, 
                dst_port: 65535 
            }
            .into();

        assert_eq!(s.as_str(), "PROXY TCP4 255.255.255.255 255.255.255.255 65535 65535\r\n");
    }

    #[test]
    fn simple_test2()
    {
        let s: String = 
            HapProtoV1::TCP4 
            { 
                src_addr: "192.168.1.1".parse().unwrap(), 
                dst_addr: "10.8.0.1".parse().unwrap(), 
                src_port: 23456, 
                dst_port: 12345 
            }
            .into();

        assert_eq!(s.as_str(), "PROXY TCP4 192.168.1.1 10.8.0.1 23456 12345\r\n");
    }

    #[test]
    fn simple_test3()
    {
        let s: String = 
            HapProtoV1::TCP6
            { 
                src_addr: "0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f".parse().unwrap(), 
                dst_addr: "4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393".parse().unwrap(), 
                src_port: 23456, 
                dst_port: 12345 
            }
            .into();

        assert_eq!(s.as_str(), "PROXY TCP6 0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f 4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393 23456 12345\r\n");
    }

    #[test]
    fn simple_test4()
    {
        let s: String = 
            HapProtoV1::TCP6
            { 
                src_addr: "0acf:5d35:b4c4:0000:2442:2f17:c6f9:5b7f".parse().unwrap(), 
                dst_addr: "4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393".parse().unwrap(), 
                src_port: 23456, 
                dst_port: 12345 
            }
            .into();

        assert_eq!(s.as_str(), "PROXY TCP6 0acf:5d35:b4c4:0000:2442:2f17:c6f9:5b7f 4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393 23456 12345\r\n");
    }

    #[test]
    fn simple_test5()
    {
        let s: String = 
            HapProtoV1::UNKNOWN.into();

        assert_eq!(s.as_str(), "PROXY UNKNOWN\r\n");
    }
}