use std::convert::TryInto;
use nom::bytes::complete::take_until;
use nom::character::complete::{crlf, one_of};
use nom::combinator::all_consuming;
use nom::sequence::{terminated, tuple};
use nom::IResult;
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct InitialResponseLine<'a> {
pub code: &'a [u8; 3],
pub data: &'a [u8],
pub buffer: &'a [u8],
}
fn one_of_digit(b: &[u8]) -> IResult<&[u8], char> {
one_of("0123456789")(b)
}
fn take_line(b: &[u8]) -> IResult<&[u8], &[u8]> {
let (rest, line) = terminated(take_until("\r\n"), crlf)(b)?;
Ok((rest, line))
}
fn take_response_code(b: &[u8]) -> IResult<&[u8], &[u8]> {
let res: IResult<_, (char, char, char)> =
tuple((one_of("12345"), one_of_digit, one_of_digit))(b);
let (rest, _) = res?;
Ok((rest, &b[0..3]))
}
pub(crate) fn is_end_of_datablock(b: &[u8]) -> bool {
b == b"."
}
pub(crate) fn parse_first_line(b: &[u8]) -> IResult<&[u8], InitialResponseLine<'_>> {
let res = all_consuming(tuple((
take_response_code,
nom::character::complete::char(' '),
take_until("\r\n"),
crlf,
)))(b)?;
let (rest, (code, _, data, _crlf)) = res;
let code = code
.try_into()
.expect("Code should be three bytes, there is likely a bug in the parser.");
Ok((
rest,
InitialResponseLine {
code,
data,
buffer: b,
},
))
}
pub(crate) fn parse_data_block_line(b: &[u8]) -> IResult<&[u8], &[u8]> {
all_consuming(take_line)(b)
}
#[cfg(test)]
mod tests {
use super::*;
use nom::error::ErrorKind;
use nom::Err;
const MOTD: &[u8] =
b"200 news.example.com InterNetNews server INN 2.5.5 ready (transit mode)\r\n";
const MOTD_NO_CRLF: &[u8] =
b"200 news.example.com InterNetNews server INN 2.5.5 ready (transit mode)";
mod test_parse_initial_response {
use super::*;
#[test]
fn happy_path() {
let (_remainder, raw_response) = parse_first_line(MOTD).unwrap();
let expected_resp = InitialResponseLine {
code: b"200",
data: &b"news.example.com InterNetNews server INN 2.5.5 ready (transit mode)"[..],
buffer: &MOTD,
};
assert_eq!(raw_response, expected_resp)
}
#[test]
fn test_remaining_data() {
let data = [MOTD, &b"SOME MORE DATA\r\n"[..]].concat();
assert!(parse_first_line(&data).is_err());
}
}
mod test_take_line {
use super::*;
#[test]
fn happy_path() {
assert_eq!(take_line(MOTD), Ok((&b""[..], MOTD_NO_CRLF)));
}
#[test]
fn test_gzip() {
let header = include_bytes!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/xover_gzip_header"
));
let (rest, data) = take_line(header).unwrap();
assert_eq!(rest.len(), 0);
assert_eq!(data, &header[..header.len() - 2]);
}
}
mod test_parse_data_block {
use super::*;
#[test]
fn happy_path() {
let msg = b"101 Capability list:\r\n";
let (_remainder, block) = parse_data_block_line(msg).unwrap();
assert_eq!(block, b"101 Capability list:")
}
}
mod test_parse_response_code {
use super::*;
#[test]
fn happy_path() {
[
&b"200"[..],
&b"200 "[..],
&b"2000"[..],
&b"200000"[..],
&b"200123"[..],
&b"200abc"[..],
]
.iter()
.for_each(|input| {
let res = take_response_code(input);
assert!(res.is_ok());
let (_rest, code) = res.unwrap();
assert_eq!(code, b"200")
});
}
#[test]
fn too_short() {
println!("Testing {:?}", b"5");
assert_eq!(
take_response_code(b"5"),
Err(Err::Error((&b""[..], ErrorKind::OneOf)))
)
}
#[test]
fn not_enough_digits() {
assert_eq!(
take_response_code(b"5ab500"),
Err(Err::Error((&b"ab500"[..], ErrorKind::OneOf)))
)
}
}
}