use crate::cbor::value::Value;
use crate::nostd_prelude::*;
pub const TAG_INTEL_EXPRESSION: u64 = 60010;
const OP_GT: i64 = 1;
const OP_GE: i64 = 2;
const OP_LT: i64 = 3;
const OP_LE: i64 = 4;
const OP_MASK_EQ: i64 = 1;
const OP_MEMBER: i64 = 6;
const OP_NOT_MEMBER: i64 = 7;
const OP_SUBSET: i64 = 8;
const OP_SUPERSET: i64 = 9;
const OP_DISJOINT: i64 = 10;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NumericOp {
Gt,
Ge,
Lt,
Le,
}
impl NumericOp {
fn from_code(code: i64) -> Option<Self> {
Some(match code {
OP_GT => Self::Gt,
OP_GE => Self::Ge,
OP_LT => Self::Lt,
OP_LE => Self::Le,
_ => return None,
})
}
pub fn as_str(self) -> &'static str {
match self {
Self::Gt => "gt",
Self::Ge => "ge",
Self::Lt => "lt",
Self::Le => "le",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SetOp {
Member,
NotMember,
}
impl SetOp {
fn from_code(code: i64) -> Option<Self> {
Some(match code {
OP_MEMBER => Self::Member,
OP_NOT_MEMBER => Self::NotMember,
_ => return None,
})
}
pub fn as_str(self) -> &'static str {
match self {
Self::Member => "member",
Self::NotMember => "not-member",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum SetOfSetOp {
Subset,
Superset,
Disjoint,
}
impl SetOfSetOp {
fn from_code(code: i64) -> Option<Self> {
Some(match code {
OP_SUBSET => Self::Subset,
OP_SUPERSET => Self::Superset,
OP_DISJOINT => Self::Disjoint,
_ => return None,
})
}
pub fn as_str(self) -> &'static str {
match self {
Self::Subset => "subset",
Self::Superset => "superset",
Self::Disjoint => "disjoint",
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Numeric {
Int(i128),
Float(f64),
}
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub enum Expression {
Numeric {
op: NumericOp,
value: Numeric,
},
Mask {
value: Vec<u8>,
mask: Vec<u8>,
},
Set {
op: SetOp,
members: Vec<Value>,
},
SetOfSet {
op: SetOfSetOp,
sets: Vec<Vec<Value>>,
},
Tdate {
op: NumericOp,
date: String,
},
Epoch {
op: NumericOp,
grace_period: i64,
epoch_id: Option<Value>,
},
}
impl Expression {
pub fn from_tag(value: &Value) -> Result<Self, ExpressionDecodeError> {
match value {
Value::Tag(t, inner) if *t == TAG_INTEL_EXPRESSION => Self::from_body(inner.as_ref()),
Value::Tag(t, _) => Err(ExpressionDecodeError::WrongTag(*t)),
_ => Err(ExpressionDecodeError::NotTagged),
}
}
pub fn from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
let items = match body {
Value::Array(a) => a,
_ => return Err(ExpressionDecodeError::NotArray),
};
if items.len() < 2 || items.len() > 3 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let op_code = match &items[0] {
Value::Integer(n) => {
i64::try_from(*n).map_err(|_| ExpressionDecodeError::OperatorOutOfRange(*n))?
}
_ => return Err(ExpressionDecodeError::OperatorNotInteger),
};
if let Some(op) = SetOp::from_code(op_code) {
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let members = match &items[1] {
Value::Array(a) => a.clone(),
_ => return Err(ExpressionDecodeError::SetOperandNotArray),
};
return Ok(Self::Set { op, members });
}
if let Some(op) = SetOfSetOp::from_code(op_code) {
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let outer = match &items[1] {
Value::Array(a) => a,
_ => return Err(ExpressionDecodeError::SetOperandNotArray),
};
let mut sets: Vec<Vec<Value>> = Vec::with_capacity(outer.len());
for inner in outer {
match inner {
Value::Array(a) => sets.push(a.clone()),
_ => return Err(ExpressionDecodeError::SetOfSetMemberNotArray),
}
}
return Ok(Self::SetOfSet { op, sets });
}
let nop =
NumericOp::from_code(op_code).ok_or(ExpressionDecodeError::UnknownOperator(op_code))?;
if items.len() == 3 {
if op_code == OP_MASK_EQ {
if let (Value::Bytes(value), Value::Bytes(mask)) = (&items[1], &items[2]) {
return Ok(Self::Mask {
value: value.clone(),
mask: mask.clone(),
});
}
}
if let Value::Integer(n) = &items[1] {
let grace_period = i64::try_from(*n)
.map_err(|_| ExpressionDecodeError::EpochGraceOutOfRange(*n))?;
return Ok(Self::Epoch {
op: nop,
grace_period,
epoch_id: Some(items[2].clone()),
});
}
return Err(ExpressionDecodeError::UnrecognizedShape);
}
match &items[1] {
Value::Text(t) => Ok(Self::Tdate {
op: nop,
date: t.clone(),
}),
Value::Tag(0, inner) => match inner.as_ref() {
Value::Text(t) => Ok(Self::Tdate {
op: nop,
date: t.clone(),
}),
_ => Err(ExpressionDecodeError::TdateNotText),
},
Value::Tag(1, inner) => match inner.as_ref() {
Value::Integer(n) => Ok(Self::Numeric {
op: nop,
value: Numeric::Int(*n),
}),
Value::Float(f) => Ok(Self::Numeric {
op: nop,
value: Numeric::Float(*f),
}),
_ => Err(ExpressionDecodeError::NumericOperandWrongType),
},
Value::Integer(n) => Ok(Self::Numeric {
op: nop,
value: Numeric::Int(*n),
}),
Value::Float(f) => Ok(Self::Numeric {
op: nop,
value: Numeric::Float(*f),
}),
_ => Err(ExpressionDecodeError::UnrecognizedShape),
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExpressionDecodeError {
NotTagged,
WrongTag(u64),
NotArray,
WrongArity(usize),
OperatorNotInteger,
OperatorOutOfRange(i128),
UnknownOperator(i64),
SetOperandNotArray,
SetOfSetMemberNotArray,
TdateNotText,
NumericOperandWrongType,
EpochGraceOutOfRange(i128),
UnrecognizedShape,
}
impl core::fmt::Display for ExpressionDecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotTagged => write!(f, "expected `#6.60010(...)` CBOR tag"),
Self::WrongTag(n) => write!(f, "expected CBOR tag 60010, got tag {}", n),
Self::NotArray => write!(f, "expression tag body must be a CBOR array"),
Self::WrongArity(n) => {
write!(f, "expression array must have 2 or 3 elements, got {}", n)
}
Self::OperatorNotInteger => write!(f, "expression operator must be an integer"),
Self::OperatorOutOfRange(n) => write!(f, "operator code {} does not fit in i64", n),
Self::UnknownOperator(n) => write!(f, "unknown operator code {}", n),
Self::SetOperandNotArray => write!(f, "set / set-of-set operand must be an array"),
Self::SetOfSetMemberNotArray => write!(f, "set-of-set member must itself be an array"),
Self::TdateNotText => write!(f, "tdate operand must be a text string"),
Self::NumericOperandWrongType => {
write!(f, "numeric operand must be an integer or float")
}
Self::EpochGraceOutOfRange(n) => {
write!(f, "epoch grace-period {} does not fit in i64", n)
}
Self::UnrecognizedShape => {
write!(f, "operand shape does not match any §8.1 expression")
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for ExpressionDecodeError {}
pub fn display_expression(e: &Expression) -> String {
match e {
Expression::Numeric { op, value } => format!("{} {}", op.as_str(), display_numeric(value)),
Expression::Mask { value, mask } => format!(
"mask-eq <{}-byte value, {}-byte mask>",
value.len(),
mask.len()
),
Expression::Set { op, members } => {
format!(
"{} ({} item{})",
op.as_str(),
members.len(),
s_plural(members.len())
)
}
Expression::SetOfSet { op, sets } => {
format!(
"{} ({} set{})",
op.as_str(),
sets.len(),
s_plural(sets.len())
)
}
Expression::Tdate { op, date } => format!("{} \"{}\"", op.as_str(), date),
Expression::Epoch {
op,
grace_period,
epoch_id,
} => match epoch_id {
Some(_) => format!("{} grace={}s +epoch-id", op.as_str(), grace_period),
None => format!("{} grace={}s", op.as_str(), grace_period),
},
}
}
fn display_numeric(n: &Numeric) -> String {
match n {
Numeric::Int(i) => format!("{}", i),
Numeric::Float(f) => format!("{}", f),
}
}
fn s_plural(n: usize) -> &'static str {
if n == 1 {
""
} else {
"s"
}
}
#[cfg(test)]
mod tests {
use super::*;
fn expr(items: Vec<Value>) -> Value {
Value::Tag(TAG_INTEL_EXPRESSION, Box::new(Value::Array(items)))
}
#[test]
fn decodes_numeric_ge_int() {
let v = expr(vec![Value::Integer(2), Value::Integer(5)]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Numeric {
op: NumericOp::Ge,
value: Numeric::Int(5),
}
);
assert_eq!(display_expression(&e), "ge 5");
}
#[test]
fn decodes_numeric_lt_float() {
let v = expr(vec![Value::Integer(3), Value::Float(1.5)]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Numeric {
op: NumericOp::Lt,
value: Numeric::Float(1.5),
}
);
}
#[test]
fn unwraps_tagged_time_61() {
let inner = Value::Tag(1, Box::new(Value::Integer(1700000000)));
let v = expr(vec![Value::Integer(2), inner]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Numeric {
op: NumericOp::Ge,
value: Numeric::Int(1700000000),
}
);
}
#[test]
fn decodes_mask_eq() {
let v = expr(vec![
Value::Integer(1),
Value::Bytes(vec![0xAA, 0xBB]),
Value::Bytes(vec![0xFF, 0x00]),
]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Mask {
value: vec![0xAA, 0xBB],
mask: vec![0xFF, 0x00],
}
);
assert_eq!(
display_expression(&e),
"mask-eq <2-byte value, 2-byte mask>"
);
}
#[test]
fn decodes_set_member() {
let v = expr(vec![
Value::Integer(6),
Value::Array(vec![
Value::Integer(1),
Value::Integer(2),
Value::Integer(3),
]),
]);
let e = Expression::from_tag(&v).unwrap();
match e {
Expression::Set { op, ref members } => {
assert_eq!(op, SetOp::Member);
assert_eq!(members.len(), 3);
}
other => panic!("expected Set, got {:?}", other),
}
assert_eq!(
display_expression(&Expression::from_tag(&v).unwrap()),
"member (3 items)"
);
}
#[test]
fn decodes_set_not_member_singleton_uses_singular() {
let v = expr(vec![
Value::Integer(7),
Value::Array(vec![Value::Text("CVE-1".into())]),
]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(display_expression(&e), "not-member (1 item)");
}
#[test]
fn decodes_set_of_set_subset() {
let v = expr(vec![
Value::Integer(8),
Value::Array(vec![
Value::Array(vec![Value::Integer(1)]),
Value::Array(vec![Value::Integer(2), Value::Integer(3)]),
]),
]);
let e = Expression::from_tag(&v).unwrap();
match e {
Expression::SetOfSet { op, ref sets } => {
assert_eq!(op, SetOfSetOp::Subset);
assert_eq!(sets.len(), 2);
assert_eq!(sets[1].len(), 2);
}
other => panic!("expected SetOfSet, got {:?}", other),
}
}
#[test]
fn set_of_set_rejects_non_array_member() {
let v = expr(vec![
Value::Integer(9),
Value::Array(vec![Value::Integer(42)]),
]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::SetOfSetMemberNotArray,
);
}
#[test]
fn decodes_tdate_bare_text() {
let v = expr(vec![
Value::Integer(2),
Value::Text("2024-01-01T00:00:00Z".into()),
]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Tdate {
op: NumericOp::Ge,
date: "2024-01-01T00:00:00Z".into(),
}
);
}
#[test]
fn decodes_tdate_with_tag_0() {
let inner = Value::Tag(0, Box::new(Value::Text("2024-06-01T00:00:00Z".into())));
let v = expr(vec![Value::Integer(2), inner]);
let e = Expression::from_tag(&v).unwrap();
assert!(matches!(
e,
Expression::Tdate {
op: NumericOp::Ge,
..
}
));
}
#[test]
fn decodes_epoch_with_id() {
let v = expr(vec![
Value::Integer(1),
Value::Integer(60),
Value::Text("custom-epoch".into()),
]);
let e = Expression::from_tag(&v).unwrap();
match e {
Expression::Epoch {
op,
grace_period,
epoch_id,
} => {
assert_eq!(op, NumericOp::Gt);
assert_eq!(grace_period, 60);
assert!(epoch_id.is_some());
}
other => panic!("expected Epoch, got {:?}", other),
}
}
#[test]
fn rejects_non_tag() {
assert_eq!(
Expression::from_tag(&Value::Integer(1)).unwrap_err(),
ExpressionDecodeError::NotTagged,
);
}
#[test]
fn rejects_wrong_tag() {
let v = Value::Tag(999, Box::new(Value::Array(vec![])));
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::WrongTag(999),
);
}
#[test]
fn rejects_wrong_arity() {
let v = expr(vec![Value::Integer(2)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::WrongArity(1),
);
let v = expr(vec![
Value::Integer(2),
Value::Integer(0),
Value::Integer(0),
Value::Integer(0),
]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::WrongArity(4),
);
}
#[test]
fn rejects_unknown_operator() {
let v = expr(vec![Value::Integer(99), Value::Integer(0)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::UnknownOperator(99),
);
}
#[test]
fn rejects_non_integer_operator() {
let v = expr(vec![Value::Text("gt".into()), Value::Integer(0)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::OperatorNotInteger,
);
}
#[test]
fn rejects_set_with_non_array_operand() {
let v = expr(vec![Value::Integer(6), Value::Integer(1)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::SetOperandNotArray,
);
}
#[test]
fn rejects_unrecognized_2elem_shape() {
let v = expr(vec![Value::Integer(2), Value::Bool(true)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::UnrecognizedShape,
);
}
#[test]
fn from_body_skips_tag_unwrap() {
let body = Value::Array(vec![Value::Integer(2), Value::Integer(5)]);
let e = Expression::from_body(&body).unwrap();
assert_eq!(
e,
Expression::Numeric {
op: NumericOp::Ge,
value: Numeric::Int(5),
}
);
}
}