1use std::str;
2use std::time::Duration;
3
4use byteorder::{BigEndian, WriteBytesExt};
5use failure::Error;
6use nom::{rest, be_u32, be_u8};
7use nom::{ErrorKind, IResult};
8use nom::Err as NomErr;
9
10use types::NomError;
11
12named!(binary_tag, tag!("b"));
13named!(text_tag, alt!(tag!("t") | tag!("u")));
14
15named!(
16 read_timestamp<Duration>,
17 map!(be_u32, |t| Duration::from_secs(u64::from(t)))
18);
19
20#[cfg_attr(rustfmt, rustfmt_skip)]
21named!(
22 literal_data<Result<LiteralPacket, Error>>,
23 do_parse!(
24 tag: map_res!(alt!(binary_tag | text_tag), str::from_utf8) >>
25 filename: map_res!(length_data!(be_u8), str::from_utf8) >>
26 timestamp: read_timestamp >>
27 contents: rest >>
28 (LiteralData::new(tag, contents).map(|data| {
29 LiteralPacket {
30 filename: String::from(filename),
31 timestamp,
32 contents: data,
33 }
34 }))
35 )
36);
37
38#[derive(Clone, Debug)]
42pub struct LiteralPacket {
43 pub filename: String,
44 pub timestamp: Duration,
45 pub contents: LiteralData,
46}
47
48impl LiteralPacket {
49 pub fn from_bytes(bytes: &[u8]) -> Result<LiteralPacket, Error> {
50 match literal_data(bytes) {
51 IResult::Done(_, sig) => sig,
52 IResult::Error(NomErr::Code(ErrorKind::Custom(e))) => {
53 let e = NomError::from(e);
54
55 bail!(LiteralError::InvalidFormat {
56 reason: format!("{:?}", e),
57 })
58 }
59 IResult::Error(e) => bail!(LiteralError::InvalidFormat {
60 reason: format!("{}", e),
61 }),
62 IResult::Incomplete(i) => bail!(LiteralError::InvalidFormat {
63 reason: format!("{:?}", i),
64 }),
65 }
66 }
67
68 pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
69 let mut out = Vec::new();
70
71 match self.contents {
72 LiteralData::Binary(_) => out.push(0x62), LiteralData::Text(_) => out.push(0x75), };
75
76 let filename = self.filename.as_bytes();
77 if filename.len() > 255 {
78 bail!(LiteralError::FilenameTooLong);
79 }
80 out.push(filename.len() as u8);
81 out.extend(filename);
82
83 out.write_u32::<BigEndian>(self.timestamp.as_secs() as u32)?;
84
85 match self.contents {
86 LiteralData::Binary(ref data) => out.extend(data),
87 LiteralData::Text(ref data) => out.extend(data.as_bytes()),
88 };
89
90 Ok(out)
91 }
92}
93
94#[derive(Clone, Debug)]
98pub enum LiteralData {
99 Binary(Vec<u8>),
100 Text(String),
101}
102
103impl LiteralData {
104 fn new(tag: &str, contents: &[u8]) -> Result<LiteralData, Error> {
105 match tag {
106 "b" => Ok(LiteralData::Binary(Vec::from(contents))),
107 "t" | "u" => Ok(LiteralData::Text(String::from(str::from_utf8(contents)?))),
108 _ => bail!(LiteralError::InvalidTag {
109 tag: String::from(tag),
110 }),
111 }
112 }
113}
114
115#[derive(Debug, Fail)]
119pub enum LiteralError {
120 #[fail(display = "Invalid literal format: {}", reason)]
121 InvalidFormat { reason: String },
122 #[fail(display = "Literal filename too long")]
123 FilenameTooLong,
124 #[fail(display = "Invalid literal type tag: {}", tag)]
125 InvalidTag { tag: String },
126}