1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
//!
//! IPP print protocol implementation for Rust. This crate can be used in several ways:
//! * using the low-level request/response API and building the requests manually.
//! * using the higher-level operations API with builders. Currently only a subset of all IPP operations is supported.
//! * using the built-in IPP client.
//! * using any third-party HTTP client and send the serialized request manually.
//!
//! This crate supports both synchronous and asynchronous operations. The following feature flags are supported:
//! * `async` - enables asynchronous APIs
//! * `async-client` - enables asynchronous IPP client based on `reqwest` crate, implies `async` feature
//! * `client` - enables blocking IPP client based on `ureq` crate
//! * `async-client-tls` - enables asynchronous IPP client with TLS support
//! * `client-tls` - enables blocking IPP client with TLS support
//!
//! By default, all features are enabled.
//!
//!
//! Implementation notes:
//! * all RFC IPP values are supported including arrays and collections, for both de- and serialization.
//! * this crate is also suitable for building IPP servers, however the example is not provided yet.
//! * some operations (e.g. CUPS-specific) require authorization which can be supplied in the printer URI.
//!
//! Usage examples:
//!
//!```rust,no_run
//! // using low-level async API
//! use ipp::prelude::*;
//!
//! #[tokio::main]
//! async fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let uri: Uri = "http://localhost:631/printers/test-printer".parse()?;
//!     let req = IppRequestResponse::new(
//!         IppVersion::v1_1(),
//!         Operation::GetPrinterAttributes,
//!         Some(uri.clone())
//!     );
//!     let client = AsyncIppClient::new(uri);
//!     let resp = client.send(req).await?;
//!     if resp.header().status_code().is_success() {
//!         println!("{:?}", resp.attributes());
//!     }
//!     Ok(())
//! }
//!```
//!```rust,no_run
//! // using blocking operations API
//! use ipp::prelude::*;
//!
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
//!     let uri: Uri = "http://localhost:631/printers/test-printer".parse()?;
//!     let operation = IppOperationBuilder::get_printer_attributes(uri.clone()).build();
//!     let client = IppClient::new(uri);
//!     let resp = client.send(operation)?;
//!     if resp.header().status_code().is_success() {
//!         println!("{:?}", resp.attributes());
//!     }
//!     Ok(())
//! }
//!```

#![allow(clippy::result_large_err)]

use bytes::{BufMut, Bytes, BytesMut};
use num_traits::FromPrimitive;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use crate::model::{IppVersion, StatusCode};

pub mod attribute;
#[cfg(any(feature = "client", feature = "async-client"))]
pub mod client;
pub mod error;
pub mod model;
pub mod operation;
pub mod parser;
pub mod payload;
pub mod reader;
pub mod request;
pub mod util;
pub mod value;

pub mod prelude {
    //!
    //! Common imports
    //!
    pub use http::Uri;
    pub use num_traits::FromPrimitive as _;

    pub use crate::{
        attribute::{IppAttribute, IppAttributeGroup, IppAttributes},
        model::*,
        operation::builder::IppOperationBuilder,
        payload::IppPayload,
        request::IppRequestResponse,
        value::IppValue,
    };

    pub use super::error::IppError;

    #[cfg(feature = "async-client")]
    pub use super::client::non_blocking::AsyncIppClient;

    #[cfg(feature = "client")]
    pub use super::client::blocking::IppClient;

    pub use super::IppHeader;
}

/// IPP request and response header
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug)]
pub struct IppHeader {
    /// IPP protocol version
    pub version: IppVersion,
    /// Operation tag for requests, status for responses
    pub operation_or_status: u16,
    /// ID of the request
    pub request_id: u32,
}

impl IppHeader {
    /// Create IPP header
    pub fn new(version: IppVersion, operation_or_status: u16, request_id: u32) -> IppHeader {
        IppHeader {
            version,
            operation_or_status,
            request_id,
        }
    }

    /// Write header to a given writer
    pub fn to_bytes(&self) -> Bytes {
        let mut buffer = BytesMut::new();
        buffer.put_u16(self.version.0);
        buffer.put_u16(self.operation_or_status);
        buffer.put_u32(self.request_id);

        buffer.freeze()
    }

    /// Decode and get IPP status code from the header
    pub fn status_code(&self) -> StatusCode {
        StatusCode::from_u16(self.operation_or_status).unwrap_or(StatusCode::UnknownStatusCode)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_header_to_bytes() {
        let header = IppHeader::new(IppVersion::v2_1(), 0x1234, 0xaa55_aa55);
        let buf = header.to_bytes();
        assert_eq!(buf, vec![0x02, 0x01, 0x12, 0x34, 0xaa, 0x55, 0xaa, 0x55]);
    }
}