use std::{collections::BTreeMap, convert::Infallible, fmt, io, str::FromStr};
use bytes::{Buf, BufMut, Bytes, BytesMut};
use enum_as_inner::EnumAsInner;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::{model::ValueTag, FromPrimitive as _};
#[inline]
fn get_len_string(data: &mut Bytes) -> String {
let len = data.get_u16() as usize;
let s = String::from_utf8_lossy(&data[0..len]).into_owned();
data.advance(len);
s
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Clone, Debug, PartialEq, Eq, Hash, EnumAsInner)]
pub enum IppValue {
Integer(i32),
Enum(i32),
OctetString(String),
TextWithoutLanguage(String),
NameWithoutLanguage(String),
TextWithLanguage {
language: String,
text: String,
},
NameWithLanguage {
language: String,
name: String,
},
Charset(String),
NaturalLanguage(String),
Uri(String),
UriScheme(String),
RangeOfInteger {
min: i32,
max: i32,
},
Boolean(bool),
Keyword(String),
Array(Vec<IppValue>),
Collection(BTreeMap<String, IppValue>),
MimeMediaType(String),
DateTime {
year: u16,
month: u8,
day: u8,
hour: u8,
minutes: u8,
seconds: u8,
deci_seconds: u8,
utc_dir: char,
utc_hours: u8,
utc_mins: u8,
},
MemberAttrName(String),
Resolution {
cross_feed: i32,
feed: i32,
units: i8,
},
NoValue,
Other {
tag: u8,
data: Bytes,
},
}
impl IppValue {
pub fn to_tag(&self) -> u8 {
match *self {
IppValue::Integer(_) => ValueTag::Integer as u8,
IppValue::Enum(_) => ValueTag::Enum as u8,
IppValue::RangeOfInteger { .. } => ValueTag::RangeOfInteger as u8,
IppValue::Boolean(_) => ValueTag::Boolean as u8,
IppValue::Keyword(_) => ValueTag::Keyword as u8,
IppValue::OctetString(_) => ValueTag::OctetStringUnspecified as u8,
IppValue::TextWithoutLanguage(_) => ValueTag::TextWithoutLanguage as u8,
IppValue::NameWithoutLanguage(_) => ValueTag::NameWithoutLanguage as u8,
IppValue::TextWithLanguage { .. } => ValueTag::TextWithLanguage as u8,
IppValue::NameWithLanguage { .. } => ValueTag::NameWithLanguage as u8,
IppValue::Charset(_) => ValueTag::Charset as u8,
IppValue::NaturalLanguage(_) => ValueTag::NaturalLanguage as u8,
IppValue::Uri(_) => ValueTag::Uri as u8,
IppValue::UriScheme(_) => ValueTag::UriScheme as u8,
IppValue::MimeMediaType(_) => ValueTag::MimeMediaType as u8,
IppValue::Array(ref array) => array.get(0).map(|v| v.to_tag()).unwrap_or(ValueTag::Unknown as u8),
IppValue::Collection(_) => ValueTag::BegCollection as u8,
IppValue::DateTime { .. } => ValueTag::DateTime as u8,
IppValue::MemberAttrName(_) => ValueTag::MemberAttrName as u8,
IppValue::Resolution { .. } => ValueTag::Resolution as u8,
IppValue::Other { tag, .. } => tag,
IppValue::NoValue => ValueTag::NoValue as u8,
}
}
pub fn parse(value_tag: u8, mut data: Bytes) -> io::Result<IppValue> {
let ipp_tag = match ValueTag::from_u8(value_tag) {
Some(x) => x,
None => {
return Ok(IppValue::Other { tag: value_tag, data });
}
};
let value = match ipp_tag {
ValueTag::Integer => IppValue::Integer(data.get_i32()),
ValueTag::Enum => IppValue::Enum(data.get_i32()),
ValueTag::OctetStringUnspecified => IppValue::OctetString(String::from_utf8_lossy(&data).into_owned()),
ValueTag::TextWithoutLanguage => IppValue::TextWithoutLanguage(String::from_utf8_lossy(&data).into_owned()),
ValueTag::NameWithoutLanguage => IppValue::NameWithoutLanguage(String::from_utf8_lossy(&data).into_owned()),
ValueTag::TextWithLanguage => IppValue::TextWithLanguage {
language: get_len_string(&mut data),
text: get_len_string(&mut data),
},
ValueTag::NameWithLanguage => IppValue::NameWithLanguage {
language: get_len_string(&mut data),
name: get_len_string(&mut data),
},
ValueTag::Charset => IppValue::Charset(String::from_utf8_lossy(&data).into_owned()),
ValueTag::NaturalLanguage => IppValue::NaturalLanguage(String::from_utf8_lossy(&data).into_owned()),
ValueTag::Uri => IppValue::Uri(String::from_utf8_lossy(&data).into_owned()),
ValueTag::UriScheme => IppValue::UriScheme(String::from_utf8_lossy(&data).into_owned()),
ValueTag::RangeOfInteger => IppValue::RangeOfInteger {
min: data.get_i32(),
max: data.get_i32(),
},
ValueTag::Boolean => IppValue::Boolean(data.get_u8() != 0),
ValueTag::Keyword => IppValue::Keyword(String::from_utf8_lossy(&data).into_owned()),
ValueTag::MimeMediaType => IppValue::MimeMediaType(String::from_utf8_lossy(&data).into_owned()),
ValueTag::DateTime => IppValue::DateTime {
year: data.get_u16(),
month: data.get_u8(),
day: data.get_u8(),
hour: data.get_u8(),
minutes: data.get_u8(),
seconds: data.get_u8(),
deci_seconds: data.get_u8(),
utc_dir: data.get_u8() as char,
utc_hours: data.get_u8(),
utc_mins: data.get_u8(),
},
ValueTag::MemberAttrName => IppValue::MemberAttrName(String::from_utf8_lossy(&data).into_owned()),
ValueTag::Resolution => IppValue::Resolution {
cross_feed: data.get_i32(),
feed: data.get_i32(),
units: data.get_i8(),
},
ValueTag::NoValue => IppValue::NoValue,
_ => IppValue::Other { tag: value_tag, data },
};
Ok(value)
}
pub fn to_bytes(&self) -> Bytes {
let mut buffer = BytesMut::new();
match *self {
IppValue::Integer(i) | IppValue::Enum(i) => {
buffer.put_u16(4);
buffer.put_i32(i);
}
IppValue::RangeOfInteger { min, max } => {
buffer.put_u16(8);
buffer.put_i32(min);
buffer.put_i32(max);
}
IppValue::Boolean(b) => {
buffer.put_u16(1);
buffer.put_u8(b as u8);
}
IppValue::Keyword(ref s)
| IppValue::OctetString(ref s)
| IppValue::TextWithoutLanguage(ref s)
| IppValue::NameWithoutLanguage(ref s)
| IppValue::Charset(ref s)
| IppValue::NaturalLanguage(ref s)
| IppValue::Uri(ref s)
| IppValue::UriScheme(ref s)
| IppValue::MimeMediaType(ref s)
| IppValue::MemberAttrName(ref s) => {
buffer.put_u16(s.len() as u16);
buffer.put_slice(s.as_bytes());
}
IppValue::TextWithLanguage { ref language, ref text } => {
buffer.put_u16((language.len() + text.len() + 4) as u16);
buffer.put_u16(language.len() as u16);
buffer.put_slice(language.as_bytes());
buffer.put_u16(text.len() as u16);
buffer.put_slice(text.as_bytes());
}
IppValue::NameWithLanguage { ref language, ref name } => {
buffer.put_u16((language.len() + name.len() + 4) as u16);
buffer.put_u16(language.len() as u16);
buffer.put_slice(language.as_bytes());
buffer.put_u16(name.len() as u16);
buffer.put_slice(name.as_bytes());
}
IppValue::Array(ref list) => {
for (i, item) in list.iter().enumerate() {
buffer.put(item.to_bytes());
if i < list.len() - 1 {
buffer.put_u8(self.to_tag());
buffer.put_u16(0);
}
}
}
IppValue::Collection(ref list) => {
buffer.put_u16(0);
for item in list.iter() {
let atr_name = IppValue::MemberAttrName(item.0.to_string());
buffer.put_u8(atr_name.to_tag());
buffer.put_u16(0);
buffer.put(atr_name.to_bytes());
buffer.put_u8(item.1.to_tag());
buffer.put_u16(0);
buffer.put(item.1.to_bytes());
}
buffer.put_u8(ValueTag::EndCollection as u8);
buffer.put_u32(0);
}
IppValue::DateTime {
year,
month,
day,
hour,
minutes,
seconds,
deci_seconds,
utc_dir,
utc_hours,
utc_mins,
} => {
buffer.put_u16(11);
buffer.put_u16(year);
buffer.put_u8(month);
buffer.put_u8(day);
buffer.put_u8(hour);
buffer.put_u8(minutes);
buffer.put_u8(seconds);
buffer.put_u8(deci_seconds);
buffer.put_u8(utc_dir as u8);
buffer.put_u8(utc_hours);
buffer.put_u8(utc_mins);
}
IppValue::Resolution {
cross_feed,
feed,
units,
} => {
buffer.put_u16(9);
buffer.put_i32(cross_feed);
buffer.put_i32(feed);
buffer.put_u8(units as u8);
}
IppValue::NoValue => buffer.put_u16(0),
IppValue::Other { ref data, .. } => {
buffer.put_u16(data.len() as u16);
buffer.put_slice(data);
}
}
buffer.freeze()
}
}
impl fmt::Display for IppValue {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
IppValue::Integer(i) | IppValue::Enum(i) => write!(f, "{i}"),
IppValue::RangeOfInteger { min, max } => write!(f, "{min}..{max}"),
IppValue::Boolean(b) => write!(f, "{}", if b { "true" } else { "false" }),
IppValue::Keyword(ref s)
| IppValue::OctetString(ref s)
| IppValue::TextWithoutLanguage(ref s)
| IppValue::NameWithoutLanguage(ref s)
| IppValue::Charset(ref s)
| IppValue::NaturalLanguage(ref s)
| IppValue::Uri(ref s)
| IppValue::UriScheme(ref s)
| IppValue::MimeMediaType(ref s)
| IppValue::MemberAttrName(ref s) => write!(f, "{s}"),
IppValue::TextWithLanguage { ref language, ref text } => write!(f, "{language}:{text}"),
IppValue::NameWithLanguage { ref language, ref name } => write!(f, "{language}:{name}"),
IppValue::Array(ref array) => {
let s: Vec<String> = array.iter().map(|v| format!("{v}")).collect();
write!(f, "[{}]", s.join(", "))
}
IppValue::Collection(ref coll) => {
let s: Vec<String> = coll.iter().map(|(k, v)| format!("{k}={v}")).collect();
write!(f, "<{}>", s.join(", "))
}
IppValue::DateTime {
year,
month,
day,
hour,
minutes,
seconds,
deci_seconds,
utc_dir,
utc_hours,
..
} => write!(
f,
"{year}-{month}-{day},{hour}:{minutes}:{seconds}.{deci_seconds},{utc_dir}{utc_hours}utc"
),
IppValue::Resolution {
cross_feed,
feed,
units,
} => {
write!(f, "{cross_feed}x{feed}{}", if units == 3 { "in" } else { "cm" })
}
IppValue::NoValue => Ok(()),
IppValue::Other { tag, ref data } => write!(f, "{tag:0x}: {data:?}"),
}
}
}
impl FromStr for IppValue {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let value = match s {
"true" => IppValue::Boolean(true),
"false" => IppValue::Boolean(false),
other => {
if let Ok(iv) = other.parse::<i32>() {
IppValue::Integer(iv)
} else {
IppValue::Keyword(other.to_owned())
}
}
};
Ok(value)
}
}
impl<'a> IntoIterator for &'a IppValue {
type Item = &'a IppValue;
type IntoIter = IppValueIterator<'a>;
fn into_iter(self) -> Self::IntoIter {
IppValueIterator { value: self, index: 0 }
}
}
pub struct IppValueIterator<'a> {
value: &'a IppValue,
index: usize,
}
impl<'a> Iterator for IppValueIterator<'a> {
type Item = &'a IppValue;
fn next(&mut self) -> Option<Self::Item> {
match self.value {
IppValue::Array(ref array) => {
if self.index < array.len() {
self.index += 1;
Some(&array[self.index - 1])
} else {
None
}
}
IppValue::Collection(ref map) => {
if let Some(entry) = map.iter().nth(self.index) {
self.index += 1;
Some(entry.1)
} else {
None
}
}
_ => {
if self.index == 0 {
self.index += 1;
Some(self.value)
} else {
None
}
}
}
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use crate::attribute::IppAttribute;
use crate::model::DelimiterTag;
use crate::parser::IppParser;
use crate::reader::IppReader;
use super::*;
fn value_check(value: IppValue) {
let mut b = value.to_bytes();
b.advance(2); assert_eq!(IppValue::parse(value.to_tag() as u8, b).unwrap(), value);
}
#[test]
fn test_value_single() {
value_check(IppValue::Integer(1234));
value_check(IppValue::Enum(4321));
value_check(IppValue::OctetString("octet-string".to_owned()));
value_check(IppValue::TextWithoutLanguage("text-without".to_owned()));
value_check(IppValue::NameWithoutLanguage("name-without".to_owned()));
value_check(IppValue::TextWithLanguage {
language: "en".to_owned(),
text: "text-with".to_owned(),
});
value_check(IppValue::NameWithLanguage {
language: "en".to_owned(),
name: "name-with".to_owned(),
});
value_check(IppValue::Charset("charset".to_owned()));
value_check(IppValue::NaturalLanguage("natural".to_owned()));
value_check(IppValue::Uri("uri".to_owned()));
value_check(IppValue::UriScheme("urischeme".to_owned()));
value_check(IppValue::RangeOfInteger { min: -12, max: 45 });
value_check(IppValue::Boolean(true));
value_check(IppValue::Boolean(false));
value_check(IppValue::Keyword("keyword".to_owned()));
value_check(IppValue::MimeMediaType("mime".to_owned()));
value_check(IppValue::DateTime {
year: 2020,
month: 2,
day: 13,
hour: 12,
minutes: 34,
seconds: 22,
deci_seconds: 1,
utc_dir: 'c',
utc_hours: 1,
utc_mins: 30,
});
value_check(IppValue::MemberAttrName("member".to_owned()));
value_check(IppValue::Resolution {
cross_feed: 800,
feed: 600,
units: 2,
});
value_check(IppValue::NoValue);
value_check(IppValue::Other {
tag: 123,
data: "foo".into(),
});
}
#[test]
fn test_value_iterator_single() {
let val = IppValue::Integer(1234);
for v in &val {
assert_eq!(*v, val);
}
}
#[test]
fn test_value_iterator_multiple() {
let list = vec![IppValue::Integer(1234), IppValue::Integer(5678)];
let val = IppValue::Array(list.clone());
for v in val.into_iter().enumerate() {
assert_eq!(*v.1, list[v.0]);
}
}
#[test]
fn test_array() {
let attr = IppAttribute::new(
"list",
IppValue::Array(vec![IppValue::Integer(0x1111_1111), IppValue::Integer(0x2222_2222)]),
);
let buf = attr.to_bytes().to_vec();
assert_eq!(
buf,
vec![
0x21, 0, 4, b'l', b'i', b's', b't', 0, 4, 0x11, 0x11, 0x11, 0x11, 0x21, 0, 0, 0, 4, 0x22, 0x22, 0x22,
0x22
],
);
let mut data = vec![1, 1, 0, 0, 0, 0, 0, 0, 4];
data.extend(buf);
data.push(3);
let result = IppParser::new(IppReader::new(io::Cursor::new(data))).parse();
assert!(result.is_ok());
let res = result.ok().unwrap();
let attrs = res
.attributes
.groups_of(DelimiterTag::PrinterAttributes)
.next()
.unwrap()
.attributes();
let attr = attrs.get("list").unwrap();
assert_eq!(
attr.value().as_array(),
Some(&vec![IppValue::Integer(0x1111_1111), IppValue::Integer(0x2222_2222)])
);
}
#[test]
fn test_collection() {
let attr = IppAttribute::new(
"coll",
IppValue::Collection(BTreeMap::from([("abcd".to_string(), IppValue::Integer(0x2222_2222))])),
);
let buf = attr.to_bytes();
assert_eq!(
vec![
0x34, 0, 4, b'c', b'o', b'l', b'l', 0, 0, 0x4a, 0, 0, 0, 4, b'a', b'b', b'c', b'd', 0x21, 0, 0, 0, 4,
0x22, 0x22, 0x22, 0x22, 0x37, 0, 0, 0, 0,
],
buf
);
let mut data = vec![1, 1, 0, 0, 0, 0, 0, 0, 4];
data.extend(buf);
data.push(3);
let result = IppParser::new(IppReader::new(io::Cursor::new(data))).parse();
assert!(result.is_ok());
let res = result.ok().unwrap();
let attrs = res
.attributes
.groups_of(DelimiterTag::PrinterAttributes)
.next()
.unwrap()
.attributes();
let attr = attrs.get("coll").unwrap();
assert_eq!(
attr.value(),
&IppValue::Collection(BTreeMap::from([("abcd".to_string(), IppValue::Integer(0x2222_2222))]))
);
}
}