ipp_proto/
lib.rs

1use std::io::{self, Read, Write};
2
3use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
4use bytes::{Bytes, BytesMut};
5use futures::{try_ready, Async, Poll, Stream};
6use num_traits::FromPrimitive;
7use tokio::io::AsyncRead;
8
9pub use crate::{
10    attribute::{IppAttribute, IppAttributeGroup, IppAttributes},
11    builder::{
12        CreateJobBuilder, GetPrinterAttributesBuilder, IppOperationBuilder, PrintJobBuilder, SendDocumentBuilder,
13    },
14    ipp::{IppVersion, Operation, StatusCode},
15    parser::{AsyncIppParser, IppParser, ParseError},
16    request::{IppRequestResponse, PayloadKind},
17    value::IppValue,
18};
19
20pub mod attribute;
21pub mod builder;
22pub mod ipp;
23pub mod operation;
24pub mod parser;
25pub mod request;
26pub mod value;
27
28/// Source for IPP data stream (job file)
29pub struct IppJobSource {
30    inner: Box<AsyncRead + Send>,
31    buffer: Vec<u8>,
32}
33
34impl IppJobSource {
35    const CHUNK_SIZE: usize = 32768;
36}
37
38impl Stream for IppJobSource {
39    type Item = Bytes;
40    type Error = io::Error;
41
42    fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
43        let size = try_ready!(self.inner.poll_read(&mut self.buffer));
44        if size > 0 {
45            Ok(Async::Ready(Some(self.buffer[0..size].into())))
46        } else {
47            Ok(Async::Ready(None))
48        }
49    }
50}
51
52impl<T> From<T> for IppJobSource
53where
54    T: 'static + AsyncRead + Send,
55{
56    /// Create job source from AsyncRead
57    fn from(r: T) -> Self {
58        IppJobSource {
59            inner: Box::new(r),
60            buffer: vec![0; IppJobSource::CHUNK_SIZE],
61        }
62    }
63}
64
65pub(crate) trait IppWriter {
66    fn write(&self, writer: &mut Write) -> io::Result<usize>;
67}
68
69/// Trait which adds two methods to Read implementations: `read_string` and `read_bytes`
70pub(crate) trait IppReadExt: Read {
71    fn read_string(&mut self, len: usize) -> std::io::Result<String> {
72        Ok(String::from_utf8_lossy(&self.read_bytes(len)?).to_string())
73    }
74
75    fn read_bytes(&mut self, len: usize) -> std::io::Result<Bytes> {
76        let mut buf = BytesMut::with_capacity(len);
77        buf.resize(len, 0);
78        self.read_exact(&mut buf)?;
79
80        Ok(buf.freeze())
81    }
82}
83impl<R: io::Read + ?Sized> IppReadExt for R {}
84
85/// IPP request and response header
86#[derive(Clone, Debug)]
87pub struct IppHeader {
88    /// IPP protocol version
89    pub version: IppVersion,
90    /// Operation tag for requests, status for responses
91    pub operation_status: u16,
92    /// ID of the request
93    pub request_id: u32,
94}
95
96impl IppHeader {
97    /// Create IppHeader from the reader
98    pub fn from_reader(reader: &mut Read) -> Result<IppHeader, ParseError> {
99        let retval = IppHeader::new(
100            IppVersion::from_u16(reader.read_u16::<BigEndian>()?).ok_or_else(|| ParseError::InvalidVersion)?,
101            reader.read_u16::<BigEndian>()?,
102            reader.read_u32::<BigEndian>()?,
103        );
104        Ok(retval)
105    }
106
107    /// Create IPP header
108    pub fn new(version: IppVersion, operation_status: u16, request_id: u32) -> IppHeader {
109        IppHeader {
110            version,
111            operation_status,
112            request_id,
113        }
114    }
115
116    /// Get operation_status field as Operation enum. If no match found returns error status code
117    pub fn operation(&self) -> Result<Operation, StatusCode> {
118        Operation::from_u16(self.operation_status).ok_or(StatusCode::ServerErrorOperationNotSupported)
119    }
120}
121
122impl IppWriter for IppHeader {
123    /// Write header to a given writer
124    fn write(&self, writer: &mut Write) -> io::Result<usize> {
125        writer.write_u16::<BigEndian>(self.version as u16)?;
126        writer.write_u16::<BigEndian>(self.operation_status)?;
127        writer.write_u32::<BigEndian>(self.request_id)?;
128
129        Ok(8)
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use std::io::Cursor;
136
137    use super::*;
138
139    #[test]
140    fn test_read_header_ok() {
141        let data = &[0x01, 0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
142
143        let header = IppHeader::from_reader(&mut Cursor::new(data));
144        assert!(header.is_ok());
145
146        let header = header.ok().unwrap();
147        assert_eq!(header.version, IppVersion::Ipp11);
148        assert_eq!(header.operation_status, 0x1122);
149        assert_eq!(header.request_id, 0x33445566);
150    }
151
152    #[test]
153    fn test_read_header_error() {
154        let data = &[0xff, 0, 0, 0, 0, 0, 0, 0];
155
156        let header = IppHeader::from_reader(&mut Cursor::new(data));
157        assert!(header.is_err());
158        if let Some(ParseError::InvalidVersion) = header.err() {
159        } else {
160            assert!(false);
161        }
162    }
163
164    #[test]
165    fn test_write_header() {
166        let header = IppHeader::new(IppVersion::Ipp21, 0x1234, 0xaa55aa55);
167        let mut buf = Vec::new();
168        assert!(header.write(&mut Cursor::new(&mut buf)).is_ok());
169        assert_eq!(buf, vec![0x02, 0x01, 0x12, 0x34, 0xaa, 0x55, 0xaa, 0x55]);
170    }
171}