pretty_good/
literal.rs

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/// A Literal data packet as specified in [RFC4880 &sect;5.9].
39///
40/// [RFC4880 &sect;5.9]: https://tools.ietf.org/html/rfc4880#section-5.9
41#[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), // 0x62 == 'b'
73            LiteralData::Text(_) => out.push(0x75),   // 0x75 == 'u'
74        };
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/// The contents of a [`LiteralPacket`].
95///
96/// [`LiteralPacket`]: struct.LiteralPacket.html
97#[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/// Error type for [`LiteralPacket`]-level errors.
116///
117/// [`LiteralPacket`]: struct.LiteralPacket.html
118#[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}