ipp-server 0.3.0

Helpers to make IPP servers
Documentation
#![allow(unused)]

use std::{
    env,
    fs::OpenOptions,
    io::{self, Cursor},
    mem,
    path::PathBuf,
    sync::{atomic, Arc},
    time,
};

use futures::{future::Future, stream::Stream};
use hyper::{service::service_fn, Body, Chunk, Request, Response, Server};
use log::debug;
use tempfile::NamedTempFile;

use ipp_proto::{
    attribute::*,
    ipp::*,
    request::{IppRequestResponse, PayloadKind},
    AsyncIppParser, IppHeader, IppParser, IppValue,
};
use ipp_server::{handler::*, server::IppServerBuilder};
use lazy_static::lazy_static;

struct TestServer {
    name: String,
    start_time: time::SystemTime,
    printing: atomic::AtomicBool,
    spooler_dir: PathBuf,
}

impl TestServer {
    fn get_printer_attribute(&self, attr: &str) -> IppAttribute {
        match attr {
            PRINTER_NAME => IppAttribute::new(attr, IppValue::NameWithoutLanguage(self.name.clone())),
            PRINTER_INFO => IppAttribute::new(attr, IppValue::TextWithoutLanguage("Project Typemetal".to_string())),
            PRINTER_STATE_MESSAGE => {
                IppAttribute::new(attr, IppValue::TextWithoutLanguage("Being awesome".to_string()))
            }
            PRINTER_MAKE_AND_MODEL => {
                IppAttribute::new(attr, IppValue::TextWithoutLanguage("Rust Printer".to_string()))
            }
            IPP_VERSIONS_SUPPORTED => {
                let versions = vec![IppValue::Keyword("1.1".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(versions))
            }
            PRINTER_STATE => {
                let state = if self.printing.load(atomic::Ordering::Relaxed) {
                    self.printing.store(false, atomic::Ordering::Relaxed);
                    IppValue::Enum(PrinterState::Processing as i32)
                } else {
                    IppValue::Enum(PrinterState::Idle as i32)
                };
                IppAttribute::new(attr, state)
            }
            PRINTER_IS_ACCEPTING_JOBS => IppAttribute::new(attr, IppValue::Boolean(true)),
            FINISHINGS_DEFAULT => IppAttribute::new(attr, IppValue::Enum(Finishings::None as i32)),
            FINISHINGS_SUPPORTED => {
                let finishings = vec![IppValue::Enum(Finishings::None as i32)];
                IppAttribute::new(attr, IppValue::ListOf(finishings))
            }
            QUEUED_JOB_COUNT => IppAttribute::new(attr, IppValue::Integer(0)),
            PRINTER_UP_TIME => IppAttribute::new(
                attr,
                IppValue::Integer(self.start_time.elapsed().unwrap().as_secs() as i32),
            ),
            PDL_OVERRIDE_SUPPORTED => IppAttribute::new(attr, IppValue::Keyword("not-attempted".to_string())),
            CHARSET_CONFIGURED => IppAttribute::new(attr, IppValue::Charset("utf-8".to_string())),
            DOCUMENT_FORMAT_DEFAULT => IppAttribute::new(attr, IppValue::MimeMediaType("image/pwg-raster".to_string())),
            DOCUMENT_FORMAT_SUPPORTED => {
                let formats = vec![
                    IppValue::MimeMediaType("image/pwg-raster".to_string()),
                    IppValue::MimeMediaType("image/jpeg".to_string()),
                ];
                IppAttribute::new(attr, IppValue::ListOf(formats))
            }
            COMPRESSION_SUPPORTED => {
                let compressions = vec![IppValue::Keyword("none".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(compressions))
            }
            URI_AUTHENTICATION_SUPPORTED => {
                let auths = vec![IppValue::Keyword("none".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(auths))
            }
            NATURAL_LANGUAGE_CONFIGURED => IppAttribute::new(attr, IppValue::NaturalLanguage("en".to_string())),
            GENERATED_NATURAL_LANGUAGE_SUPPORTED => {
                let langs = vec![IppValue::NaturalLanguage("en".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(langs))
            }
            CHARSET_SUPPORTED => {
                let charsets = vec![IppValue::Charset("utf-8".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(charsets))
            }
            OPERATIONS_SUPPORTED => {
                let operations = vec![
                    IppValue::Enum(Operation::PrintJob as i32),
                    IppValue::Enum(Operation::ValidateJob as i32),
                    IppValue::Enum(Operation::GetPrinterAttributes as i32),
                ];
                IppAttribute::new(attr, IppValue::ListOf(operations))
            }
            PRINTER_STATE_REASONS => IppAttribute::new(attr, IppValue::Keyword("none".to_string())),
            PRINTER_URI_SUPPORTED => {
                let uris = vec![IppValue::Uri("ipp://192.168.1.217".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(uris))
            }
            URI_SECURITY_SUPPORTED => {
                let securities = vec![IppValue::Keyword("none".to_string())];
                IppAttribute::new(attr, IppValue::ListOf(securities))
            }
            _ => panic!("Got an unsupported attribute in get_printer_attribute!"),
        }
    }
}

impl IppRequestHandler for TestServer {
    fn print_job(&self, mut req: IppRequestResponse) -> IppServerResult {
        println!("Print-Job");
        println!("{:?}", req.header());
        println!("{:?}", req.attributes());

        match req.payload_mut().take() {
            Some(PayloadKind::ReceivedData(file)) => {
                let new_path = self.spooler_dir.join(format!(
                    "{}.spl",
                    self.start_time
                        .duration_since(time::SystemTime::UNIX_EPOCH)
                        .unwrap()
                        .as_millis()
                ));
                match file.persist(&new_path) {
                    Ok(file) => println!("Saved ipp payload to {}", new_path.display()),
                    Err(e) => println!("Error while saving payload: {}", e),
                }
            }
            _ => println!("No payload!"),
        }

        let mut resp =
            IppRequestResponse::new_response(self.version(), StatusCode::SuccessfulOK, req.header().request_id);

        resp.attributes_mut().add(
            DelimiterTag::JobAttributes,
            IppAttribute::new(JOB_URI, IppValue::Uri("ipp://192.168.1.217/jobs/foo".to_string())),
        );
        resp.attributes_mut().add(
            DelimiterTag::JobAttributes,
            IppAttribute::new(JOB_ID, IppValue::Integer(1)),
        );
        resp.attributes_mut().add(
            DelimiterTag::JobAttributes,
            IppAttribute::new(JOB_STATE, IppValue::Enum(JobState::Processing as i32)),
        );
        resp.attributes_mut().add(
            DelimiterTag::JobAttributes,
            IppAttribute::new(
                JOB_STATE_REASONS,
                IppValue::Keyword("completed-successfully".to_string()),
            ),
        );

        self.printing.store(true, atomic::Ordering::Relaxed);

        Ok(resp)
    }

    fn validate_job(&self, req: IppRequestResponse) -> IppServerResult {
        println!("Validate-Job");
        println!("{:?}", req.header());
        println!("{:?}", req.attributes());

        let resp = IppRequestResponse::new_response(self.version(), StatusCode::SuccessfulOK, req.header().request_id);

        Ok(resp)
    }

    fn get_printer_attributes(&self, req: IppRequestResponse) -> IppServerResult {
        static SUPPORTED_ATTRIBUTES: &[&'static str] = &[
            PRINTER_URI_SUPPORTED,
            URI_SECURITY_SUPPORTED,
            URI_AUTHENTICATION_SUPPORTED,
            PRINTER_NAME,
            PRINTER_STATE,
            PRINTER_STATE_REASONS,
            IPP_VERSIONS_SUPPORTED,
            OPERATIONS_SUPPORTED,
            CHARSET_CONFIGURED,
            CHARSET_SUPPORTED,
            NATURAL_LANGUAGE_CONFIGURED,
            GENERATED_NATURAL_LANGUAGE_SUPPORTED,
            DOCUMENT_FORMAT_DEFAULT,
            DOCUMENT_FORMAT_SUPPORTED,
            PRINTER_IS_ACCEPTING_JOBS,
            QUEUED_JOB_COUNT,
            PDL_OVERRIDE_SUPPORTED,
            PRINTER_UP_TIME,
            COMPRESSION_SUPPORTED,
            PRINTER_STATE_MESSAGE,
            PRINTER_MAKE_AND_MODEL,
            FINISHINGS_DEFAULT,
            FINISHINGS_SUPPORTED,
        ];

        let mut resp =
            IppRequestResponse::new_response(self.version(), StatusCode::SuccessfulOK, req.header().request_id);

        let requested_attributes = req
            .attributes()
            .groups_of(DelimiterTag::OperationAttributes)
            .get(0)
            .and_then(|g| g.attributes().get(REQUESTED_ATTRIBUTES))
            .map(|attr| {
                attr.value()
                    .into_iter()
                    .filter_map(|e| e.as_keyword())
                    .map(AsRef::as_ref)
                    .collect::<Vec<_>>()
            })
            .unwrap_or(SUPPORTED_ATTRIBUTES.to_vec());

        for attr in requested_attributes {
            if SUPPORTED_ATTRIBUTES.contains(&attr) {
                resp.attributes_mut()
                    .add(DelimiterTag::PrinterAttributes, self.get_printer_attribute(attr));
            } else {
                println!("Unsupported attribute {}", attr);
            }
        }

        Ok(resp)
    }
}

fn main() {
    let args = env::args().collect::<Vec<String>>();
    if args.len() < 2 {
        eprintln!("Usage: {} spooler_dir", args[0]);
        std::process::exit(1);
    }

    env_logger::init();

    let _ = std::fs::create_dir_all(&args[1]);

    let test_server = TestServer {
        name: "IPP server example".to_string(),
        start_time: time::SystemTime::now(),
        printing: atomic::AtomicBool::new(false),
        spooler_dir: env::args().nth(1).unwrap().into(),
    };

    let fut = IppServerBuilder::new(([0, 0, 0, 0], 7631))
        .handler(Arc::new(test_server))
        .build()
        .map_err(|e| {
            eprintln!("ERROR: {:?}", e);
        });

    hyper::rt::run(fut.map(|_| ()));
}