use std::str;
use std::time::Duration;
use byteorder::{BigEndian, WriteBytesExt};
use failure::Error;
use nom::{rest, be_u32, be_u8};
use nom::{ErrorKind, IResult};
use nom::Err as NomErr;
use types::NomError;
named!(binary_tag, tag!("b"));
named!(text_tag, alt!(tag!("t") | tag!("u")));
named!(
read_timestamp<Duration>,
map!(be_u32, |t| Duration::from_secs(u64::from(t)))
);
#[cfg_attr(rustfmt, rustfmt_skip)]
named!(
literal_data<Result<LiteralPacket, Error>>,
do_parse!(
tag: map_res!(alt!(binary_tag | text_tag), str::from_utf8) >>
filename: map_res!(length_data!(be_u8), str::from_utf8) >>
timestamp: read_timestamp >>
contents: rest >>
(LiteralData::new(tag, contents).map(|data| {
LiteralPacket {
filename: String::from(filename),
timestamp,
contents: data,
}
}))
)
);
#[derive(Clone, Debug)]
pub struct LiteralPacket {
pub filename: String,
pub timestamp: Duration,
pub contents: LiteralData,
}
impl LiteralPacket {
pub fn from_bytes(bytes: &[u8]) -> Result<LiteralPacket, Error> {
match literal_data(bytes) {
IResult::Done(_, sig) => sig,
IResult::Error(NomErr::Code(ErrorKind::Custom(e))) => {
let e = NomError::from(e);
bail!(LiteralError::InvalidFormat {
reason: format!("{:?}", e),
})
}
IResult::Error(e) => bail!(LiteralError::InvalidFormat {
reason: format!("{}", e),
}),
IResult::Incomplete(i) => bail!(LiteralError::InvalidFormat {
reason: format!("{:?}", i),
}),
}
}
pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
let mut out = Vec::new();
match self.contents {
LiteralData::Binary(_) => out.push(0x62), LiteralData::Text(_) => out.push(0x75), };
let filename = self.filename.as_bytes();
if filename.len() > 255 {
bail!(LiteralError::FilenameTooLong);
}
out.push(filename.len() as u8);
out.extend(filename);
out.write_u32::<BigEndian>(self.timestamp.as_secs() as u32)?;
match self.contents {
LiteralData::Binary(ref data) => out.extend(data),
LiteralData::Text(ref data) => out.extend(data.as_bytes()),
};
Ok(out)
}
}
#[derive(Clone, Debug)]
pub enum LiteralData {
Binary(Vec<u8>),
Text(String),
}
impl LiteralData {
fn new(tag: &str, contents: &[u8]) -> Result<LiteralData, Error> {
match tag {
"b" => Ok(LiteralData::Binary(Vec::from(contents))),
"t" | "u" => Ok(LiteralData::Text(String::from(str::from_utf8(contents)?))),
_ => bail!(LiteralError::InvalidTag {
tag: String::from(tag),
}),
}
}
}
#[derive(Debug, Fail)]
pub enum LiteralError {
#[fail(display = "Invalid literal format: {}", reason)]
InvalidFormat { reason: String },
#[fail(display = "Literal filename too long")]
FilenameTooLong,
#[fail(display = "Invalid literal type tag: {}", tag)]
InvalidTag { tag: String },
}