use std::io::{self, Read, Write};
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use bytes::{Bytes, BytesMut};
use futures::{try_ready, Async, Poll, Stream};
use num_traits::FromPrimitive;
use tokio::io::AsyncRead;
pub use crate::{
attribute::{IppAttribute, IppAttributeGroup, IppAttributes},
builder::{
CreateJobBuilder, GetPrinterAttributesBuilder, IppOperationBuilder, PrintJobBuilder, SendDocumentBuilder,
},
ipp::{IppVersion, Operation, StatusCode},
parser::{AsyncIppParser, IppParser, ParseError},
request::{IppRequestResponse, PayloadKind},
value::IppValue,
};
pub mod attribute;
pub mod builder;
pub mod ipp;
pub mod operation;
pub mod parser;
pub mod request;
pub mod value;
pub struct IppJobSource {
inner: Box<AsyncRead + Send>,
buffer: Vec<u8>,
}
impl IppJobSource {
const CHUNK_SIZE: usize = 32768;
}
impl Stream for IppJobSource {
type Item = Bytes;
type Error = io::Error;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let size = try_ready!(self.inner.poll_read(&mut self.buffer));
if size > 0 {
Ok(Async::Ready(Some(self.buffer[0..size].into())))
} else {
Ok(Async::Ready(None))
}
}
}
impl<T> From<T> for IppJobSource
where
T: 'static + AsyncRead + Send,
{
fn from(r: T) -> Self {
IppJobSource {
inner: Box::new(r),
buffer: vec![0; IppJobSource::CHUNK_SIZE],
}
}
}
pub(crate) trait IppWriter {
fn write(&self, writer: &mut Write) -> io::Result<usize>;
}
pub(crate) trait IppReadExt: Read {
fn read_string(&mut self, len: usize) -> std::io::Result<String> {
Ok(String::from_utf8_lossy(&self.read_bytes(len)?).to_string())
}
fn read_bytes(&mut self, len: usize) -> std::io::Result<Bytes> {
let mut buf = BytesMut::with_capacity(len);
buf.resize(len, 0);
self.read_exact(&mut buf)?;
Ok(buf.freeze())
}
}
impl<R: io::Read + ?Sized> IppReadExt for R {}
#[derive(Clone, Debug)]
pub struct IppHeader {
pub version: IppVersion,
pub operation_status: u16,
pub request_id: u32,
}
impl IppHeader {
pub fn from_reader(reader: &mut Read) -> Result<IppHeader, ParseError> {
let retval = IppHeader::new(
IppVersion::from_u16(reader.read_u16::<BigEndian>()?).ok_or_else(|| ParseError::InvalidVersion)?,
reader.read_u16::<BigEndian>()?,
reader.read_u32::<BigEndian>()?,
);
Ok(retval)
}
pub fn new(version: IppVersion, operation_status: u16, request_id: u32) -> IppHeader {
IppHeader {
version,
operation_status,
request_id,
}
}
pub fn operation(&self) -> Result<Operation, StatusCode> {
Operation::from_u16(self.operation_status).ok_or(StatusCode::ServerErrorOperationNotSupported)
}
}
impl IppWriter for IppHeader {
fn write(&self, writer: &mut Write) -> io::Result<usize> {
writer.write_u16::<BigEndian>(self.version as u16)?;
writer.write_u16::<BigEndian>(self.operation_status)?;
writer.write_u32::<BigEndian>(self.request_id)?;
Ok(8)
}
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use super::*;
#[test]
fn test_read_header_ok() {
let data = &[0x01, 0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
let header = IppHeader::from_reader(&mut Cursor::new(data));
assert!(header.is_ok());
let header = header.ok().unwrap();
assert_eq!(header.version, IppVersion::Ipp11);
assert_eq!(header.operation_status, 0x1122);
assert_eq!(header.request_id, 0x33445566);
}
#[test]
fn test_read_header_error() {
let data = &[0xff, 0, 0, 0, 0, 0, 0, 0];
let header = IppHeader::from_reader(&mut Cursor::new(data));
assert!(header.is_err());
if let Some(ParseError::InvalidVersion) = header.err() {
} else {
assert!(false);
}
}
#[test]
fn test_write_header() {
let header = IppHeader::new(IppVersion::Ipp21, 0x1234, 0xaa55aa55);
let mut buf = Vec::new();
assert!(header.write(&mut Cursor::new(&mut buf)).is_ok());
assert_eq!(buf, vec![0x02, 0x01, 0x12, 0x34, 0xaa, 0x55, 0xaa, 0x55]);
}
}