haprox_rs/protocol_v1/
protocol_parser.rs

1/*-
2 * haprox-rs - a HaProxy protocol parser.
3 * 
4 * Copyright 2025 (c) Aleksandr Morozov
5 * The scram-rs crate can be redistributed and/or modified
6 * under the terms of either of the following licenses:
7 *
8 *   1. the Mozilla Public License Version 2.0 (the “MPL”) OR
9 *
10 *   2. The MIT License (MIT)
11 *                     
12 *   3. EUROPEAN UNION PUBLIC LICENCE v. 1.2 EUPL © the European Union 2007, 2016
13 */
14
15use crate::{HaProxRes, HapProtoV1, common, map_error, protocol_raw, return_error};
16
17
18/// A HaProxy protocol V1 parser. 
19/// 
20/// It is assumed that the upstream has received the packet and splitted each header
21/// line. Input funtions expects that the input complies with the HaProxy protocl 
22/// specs i.e 
23/// 
24/// * the message starts from identifier
25/// 
26/// * separated with exactly one ASCII space 
27/// 
28/// * consists from ASCII printable chars only 
29/// 
30/// * ends with `\r\n` sequence and does not contain anything else.
31/// 
32/// > Human-readable header format (Version 1)
33/// > 
34/// > This is the format specified in version 1 of the protocol. It consists in one
35/// > line of US-ASCII text matching exactly the following block
36/// > 
37/// > So a 108-byte buffer is always enough to store all the line and a trailing zero
38/// > for string processing.
39/// > The receiver must wait for the CRLF sequence before starting to decode the
40/// > addresses in order to ensure they are complete and properly parsed. If the CRLF
41/// > sequence is not found in the first 107 characters, the receiver should declare
42/// > the line invalid.
43/// 
44/// ## Example
45/// 
46/// ```ignore
47/// let pv1 = 
48///     ProxyV1Parser
49///         ::try_from_slice(b"PROXY TCP6 0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f 4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393 23456 12345\r\n", false)
50///            .unwrap();
51/// ```
52#[derive(Debug)]
53pub struct ProxyV1Parser;
54
55impl ProxyV1Parser
56{
57    /// Attempts to parse the `value` which was already converted to [str].
58    /// 
59    /// It is assumed that a caller has verified that the `value` provided
60    /// to function contains valid UTF8 char sequences and the message ends 
61    /// with `\r\n` and this string does not contain any other data after EOM.
62    /// 
63    /// # Arguments
64    /// 
65    /// * `value` - a message to parse
66    /// 
67    /// * `skip_strict_size_check` - if set to `true`, the max message length 
68    ///     verification will be disabled.
69    /// 
70    /// # Returns
71    /// 
72    /// A [HapProtoV1] is returned which contains parsed data.
73    /// 
74    /// The following error codes are returned:
75    /// 
76    /// * [crate::error::HaProxErrType::IncorrectBanner] - incorrect header
77    /// 
78    /// * [crate::error::HaProxErrType::ProtocolMsgIncomplete] - `value` does not end
79    ///     with `\r\n` seq.
80    pub 
81    fn try_from_str(value: &str, skip_strict_size_check: bool) -> HaProxRes<HapProtoV1>
82    {
83        if value.starts_with(protocol_raw::HEADER_MAGIC_V1_STR) == false
84        {
85            return_error!(IncorrectBanner, "unknown proto identifier '{:02X?}'", 
86                &value[0..protocol_raw::HEADER_MAGIC_V1.len()]);
87        }
88        else if skip_strict_size_check == false && value.as_bytes().len() >= protocol_raw::HEADER_V1_MAX_LEN
89        {
90            return_error!(IncorrectBanner, "size of the messagge: '{}' larger '{}' for '{:02X?}'", 
91                value.len(), protocol_raw::HEADER_V1_MAX_LEN, value);
92        }
93
94        // chech that it is ASCII only
95        let Some(res_value) = common::check_printable_ascii_single_wp(value, "HEADER")?
96            else
97            {
98                // incomplete message
99                return_error!(ProtocolMsgIncomplete, "protocol message is incomplite '{}'", value);
100            };
101
102        return Self::parse(res_value);
103    }
104
105    /// Attempts to parse the `value` from a slice which contails a HaProxy
106    /// related field.
107    /// 
108    /// It is assumed that a caller has split the header and provides a slice
109    /// with the header only which  ends with `\r\n` and this string does not 
110    /// contain any other data after EOM.
111    /// 
112    /// # Arguments
113    /// 
114    /// * `value` - a message to parse
115    /// 
116    /// * `skip_strict_size_check` - if set to `true`, the max message length 
117    ///     verification will be disabled.
118    /// 
119    /// # Returns
120    /// 
121    /// # Returns
122    /// 
123    /// A [HapProtoV1] is returned which contains parsed data.
124    /// 
125    /// The following error codes are returned:
126    /// 
127    /// * [crate::error::HaProxErrType::IncorrectBanner] - incorrect header
128    /// 
129    /// * [crate::error::HaProxErrType::MalformedData] - contains non UTF-8 seq or
130    ///     not ASCII or non printable ASCII.
131    /// 
132    /// * [crate::error::HaProxErrType::ProtocolMsgIncomplete] - `value` does not end
133    ///     with `\r\n` seq.
134    pub 
135    fn try_from_slice(value: &[u8], skip_strict_size_check: bool) -> HaProxRes<HapProtoV1>
136    {
137        let pre_parsed_msg = Self::new_from(value, skip_strict_size_check)?;
138
139        return Self::parse(pre_parsed_msg);
140    }
141
142    /// Internal function.
143    /// 
144    /// Checks the header for the specific pattern to determine if this is a HaProxy
145    /// mesage and if it initial header bits are valid.
146    /// 
147    /// Eliminates the header and trailing \r\n.
148    /// 
149    /// # Returns
150    /// 
151    /// Error ProtocolMsgIncomplete if msg is incomplete
152    fn new_from(value: &[u8], skip_strict_size_check: bool) -> HaProxRes<&str>
153    {
154        if value.len() <= protocol_raw::HEADER_MAGIC_V1.len()
155        {
156            return_error!(IncorrectBanner, "protocol with footprint '{:02X?}' unknown", 
157                value);
158        }
159        else if skip_strict_size_check == false && value.len() >= protocol_raw::HEADER_V1_MAX_LEN
160        {
161            return_error!(IncorrectBanner, "size of the messagge: '{}' larger '{}' for '{:02X?}'", 
162                value.len(), protocol_raw::HEADER_V1_MAX_LEN, value);
163        }
164        else if &value[0..protocol_raw::HEADER_MAGIC_V1.len()] != protocol_raw::HEADER_MAGIC_V1
165        {
166            return_error!(IncorrectBanner, "unknown proto identifier '{:02X?}'", 
167                &value[0..protocol_raw::HEADER_MAGIC_V1.len()]);
168        }
169        
170        // try to convert into UTF8, skipping the header
171        let str_val = 
172            str::from_utf8(value)
173                .map_err(|e|
174                    map_error!(MalformedData, "UTF8 decode error {}", e)
175                )?;
176
177        // chech that it is ASCII only
178        let Some(res_value) = common::check_printable_ascii_single_wp(str_val, "HEADER")?
179            else
180            {
181                // incomplete message
182                return_error!(ProtocolMsgIncomplete, "protocol message is incomplite '{}'", 
183                    str_val);
184            };
185        
186        return Ok(res_value);
187    }
188
189    fn parse(pre_parsed_msg: &str) -> HaProxRes<HapProtoV1>
190    {
191        let mut parsed_iter = pre_parsed_msg.split(protocol_raw::HEADER_V1_WSPACE);
192
193        // skip header version
194        let _ = 
195            parsed_iter.next().ok_or_else(||
196                    map_error!(MalformedData, "no INET exists in '{}'", pre_parsed_msg)
197                )?;
198
199        // inet
200        let inet = 
201            parsed_iter
202                .next()
203                .ok_or_else(||
204                    map_error!(MalformedData, "no INET exists in '{}'", pre_parsed_msg)
205                )?;
206
207        // src ip
208        let src_ip= parsed_iter.next();
209        let dst_ip = parsed_iter.next();
210        let src_port = parsed_iter.next();
211        let dst_port = parsed_iter.next();
212
213        return HapProtoV1::from_raw(inet, src_ip, dst_ip, src_port, dst_port);
214    }
215}
216
217#[cfg(test)]
218mod tests_parser
219{
220    use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
221
222    use crate::{ProtocolV1Inet, ProxyV1Parser};
223
224    #[test]
225    fn test_v1_parser_0()
226    {
227        let pv1 = 
228            ProxyV1Parser
229                ::try_from_slice(b"PROXY TCP4 192.168.2.1 10.8.0.1 4567 1234\r\n", false)
230                    .unwrap();
231
232        assert_eq!(pv1.get_inet(), ProtocolV1Inet::Tcp4);
233        assert_eq!(pv1.get_src_addr(), Some("192.168.2.1".parse().unwrap()));
234        assert_eq!(pv1.get_src_port(), Some(4567));
235        assert_eq!(pv1.get_dst_addr(), Some("10.8.0.1".parse().unwrap()));
236        assert_eq!(pv1.get_dst_port(), Some(1234));
237    }
238
239    #[test]
240    fn test_v1_parser_1()
241    {
242        let pv1 = 
243            ProxyV1Parser
244                ::try_from_slice(b"PROXY TCP6 0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f 4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393 23456 12345\r\n", false)
245                    .unwrap();
246
247        assert_eq!(pv1.get_inet(), ProtocolV1Inet::Tcp6);
248        assert_eq!(pv1.get_src_addr(), Some(IpAddr::V6("0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f".parse::<Ipv6Addr>().unwrap())));
249        assert_eq!(pv1.get_src_port(), Some(23456));
250        assert_eq!(pv1.get_dst_addr(), Some(IpAddr::V6("4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393".parse::<Ipv6Addr>().unwrap())));
251        assert_eq!(pv1.get_dst_port(), Some(12345));
252    }
253
254    #[test]
255    fn test_v1_parser_2()
256    {
257        let pv1 = 
258            ProxyV1Parser
259                ::try_from_slice(b"PROXY UNKNOWN\r\n", false)
260                    .unwrap();
261
262        assert_eq!(pv1.get_inet(), ProtocolV1Inet::None);
263        assert_eq!(pv1.get_src_addr(), None);
264        assert_eq!(pv1.get_src_port(), None);
265        assert_eq!(pv1.get_dst_addr(), None);
266        assert_eq!(pv1.get_dst_port(), None);
267    }
268
269    #[test]
270    fn test_v1_parser_3()
271    {
272        let pv1 = 
273            ProxyV1Parser
274                ::try_from_slice(b"PROXY UNKNOWN 255.255.255.255 255.255.255.255 65535 65535\r\n", false)
275                    .unwrap();
276
277        assert_eq!(pv1.get_inet(), ProtocolV1Inet::None);
278        assert_eq!(pv1.get_src_addr(), None);
279        assert_eq!(pv1.get_src_port(), None);
280        assert_eq!(pv1.get_dst_addr(), None);
281        assert_eq!(pv1.get_dst_port(), None);
282    }
283
284    #[should_panic]
285    #[test]
286    fn test_v1_parser_4()
287    {
288        let _pv1 = 
289            ProxyV1Parser
290                ::try_from_slice(b"PROXY TCP4  192.168.1.1 10.8.0.1 23456 12345\r\n", false)
291                    .unwrap();          
292    }
293
294    #[should_panic]
295    #[test]
296    fn test_v1_parser_5()
297    {
298        let _pv1 = 
299            ProxyV1Parser
300                ::try_from_slice(b"PROXY TCP5 192.168.1.1 10.8.0.1 23456 12345\r\n", false)
301                    .unwrap();          
302    }
303
304    #[should_panic]
305    #[test]
306    fn test_v1_parser_6()
307    {
308        let _pv1 = 
309            ProxyV1Parser
310                ::try_from_slice(b"PROXY TCP4 192.168.1.1 10.8.0.1 23456 12345\r\n\r\n", false)
311                    .unwrap();          
312    }
313
314    #[should_panic]
315    #[test]
316    fn test_v1_parser_7()
317    {
318        let _pv1 = 
319            ProxyV1Parser
320                ::try_from_slice(b"PROXY TCP6 192.168.1.1 10.8.0.1 23456 12345\r\n", false)
321                    .unwrap();          
322    }
323
324    #[should_panic]
325    #[test]
326    fn test_v1_parser_8()
327    {
328        let _pv1 = 
329            ProxyV1Parser
330                ::try_from_slice(b"PROXY TCP4 349.168.1.1 10.8.0.1 23456 12345\r\n", false)
331                    .unwrap();          
332    }
333
334    #[should_panic]
335    #[test]
336    fn test_v1_parser_9()
337    {
338        let _pv1 = 
339            ProxyV1Parser
340                ::try_from_slice(b"PROXY TCP4 \0149.168.1.1 10.8.0.1 23456 12345\r\n", false)
341                    .unwrap();          
342    }
343
344    #[should_panic]
345    #[test]
346    fn test_v1_parser_10()
347    {
348        let _pv1 = 
349            ProxyV1Parser
350                ::try_from_slice(b"PROXY TCP4 149.168.1.1 10.8.0.1 6787765 12345\r\n", false)
351                    .unwrap();          
352    }
353
354    #[should_panic]
355    #[test]
356    fn test_v1_parser_11()
357    {
358        let _pv1 = 
359            ProxyV1Parser
360                ::try_from_slice(b"PROXY TCP4 149.168.1.1 10.8.0.1 1 12345\r", false)
361                    .unwrap();          
362    }
363
364    #[test]
365    fn test_v1_parser_0_str()
366    {
367        let pv1 = 
368            ProxyV1Parser
369                ::try_from_str("PROXY TCP4 192.168.1.1 10.8.0.1 23456 12345\r\n", false)
370                    .unwrap();
371
372        assert_eq!(pv1.get_inet(), ProtocolV1Inet::Tcp4);
373        assert_eq!(pv1.get_src_addr(), Some(IpAddr::V4("192.168.1.1".parse::<Ipv4Addr>().unwrap())));
374        assert_eq!(pv1.get_src_port(), Some(23456));
375        assert_eq!(pv1.get_dst_addr(), Some(IpAddr::V4("10.8.0.1".parse::<Ipv4Addr>().unwrap())));
376        assert_eq!(pv1.get_dst_port(), Some(12345));
377    }
378
379    #[test]
380    fn test_v1_parser_1_str()
381    {
382        let pv1 = 
383            ProxyV1Parser
384                ::try_from_str("PROXY TCP6 0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f 4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393 23456 12345\r\n", false)
385                    .unwrap();
386
387        assert_eq!(pv1.get_inet(), ProtocolV1Inet::Tcp6);
388        assert_eq!(pv1.get_src_addr(), Some(IpAddr::V6("0acf:5d35:b4c4:731c:2442:2f17:c6f9:5b7f".parse::<Ipv6Addr>().unwrap())));
389        assert_eq!(pv1.get_src_port(), Some(23456));
390        assert_eq!(pv1.get_dst_addr(), Some(IpAddr::V6("4d7f:8980:38d6:e0c3:7301:70e9:f8ef:e393".parse::<Ipv6Addr>().unwrap())));
391        assert_eq!(pv1.get_dst_port(), Some(12345));
392    }
393
394    #[should_panic]
395    #[test]
396    fn test_v1_parser_2_str()
397    {
398        let _pv1 = 
399            ProxyV1Parser
400                ::try_from_str("PROXY TCP4 192.168.1.1 10.8.0.1 23456 12345\r\n\r\n", false)
401                    .unwrap();          
402    }
403
404    #[should_panic]
405    #[test]
406    fn test_v1_parser_3_str()
407    {
408        let _pv1 = 
409            ProxyV1Parser
410                ::try_from_str("PROXY TCP4\0 192.168.1.1 10.8.0.1 23456 12345\r\n\r\n", false)
411                    .unwrap();          
412    }
413}