use crate::cbor::value::Value;
use crate::nostd_prelude::*;
use crate::types::tags::{TAG_INT_RANGE, TAG_MASKED_RAW_VALUE, TAG_MIN_SVN};
pub const TAG_INTEL_EXPRESSION: u64 = 60010;
pub const TAG_INTEL_SET_DIGEST_EXPRESSION: u64 = 60020;
pub const TAG_INTEL_SET_TSTR_EXPRESSION: u64 = 60021;
const OP_EQ: i64 = 0;
const OP_GT: i64 = 1;
const OP_GE: i64 = 2;
const OP_LT: i64 = 3;
const OP_LE: i64 = 4;
const OP_MEMBER: i64 = 6;
const OP_NOT_MEMBER: i64 = 7;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NumericOp {
Eq,
Gt,
Ge,
Lt,
Le,
}
impl NumericOp {
fn from_code(code: i64) -> Option<Self> {
Some(match code {
OP_EQ => Self::Eq,
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::Eq => "eq",
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, 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,
},
SetOfDigests {
op: SetOp,
members: Vec<Value>,
},
SetOfTstr {
op: SetOp,
members: Vec<String>,
},
IntRange {
min: Option<i128>,
max: Option<i128>,
},
MinSvn(u64),
MaskedRawValue {
value: Vec<u8>,
mask: Vec<u8>,
},
}
impl Expression {
pub fn from_tag(value: &Value) -> Result<Self, ExpressionDecodeError> {
match value {
Value::Tag(t, inner) if *t == TAG_INTEL_EXPRESSION => {
Self::numeric_from_body(inner.as_ref())
}
Value::Tag(t, inner) if *t == TAG_INTEL_SET_DIGEST_EXPRESSION => {
Self::set_digest_from_body(inner.as_ref())
}
Value::Tag(t, inner) if *t == TAG_INTEL_SET_TSTR_EXPRESSION => {
Self::set_tstr_from_body(inner.as_ref())
}
Value::Tag(t, inner) if *t == TAG_INT_RANGE => {
Self::int_range_from_body(inner.as_ref())
}
Value::Tag(t, inner) if *t == TAG_MIN_SVN => Self::min_svn_from_body(inner.as_ref()),
Value::Tag(t, inner) if *t == TAG_MASKED_RAW_VALUE => {
Self::masked_raw_value_from_body(inner.as_ref())
}
Value::Tag(t, _) => Err(ExpressionDecodeError::WrongTag(*t)),
_ => Err(ExpressionDecodeError::NotTagged),
}
}
pub fn is_intel_expression_tag(tag: u64) -> bool {
matches!(
tag,
TAG_INTEL_EXPRESSION
| TAG_INTEL_SET_DIGEST_EXPRESSION
| TAG_INTEL_SET_TSTR_EXPRESSION
| TAG_INT_RANGE
| TAG_MIN_SVN
| TAG_MASKED_RAW_VALUE
)
}
fn numeric_from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
let items = expect_array(body)?;
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let op = numeric_op(&items[0])?;
let value = numeric_value(&items[1])?;
Ok(Self::Numeric { op, value })
}
fn set_digest_from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
let items = expect_array(body)?;
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let op = set_op(&items[0])?;
let members = match &items[1] {
Value::Array(a) => a.clone(),
_ => return Err(ExpressionDecodeError::SetOperandNotArray),
};
Ok(Self::SetOfDigests { op, members })
}
fn set_tstr_from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
let items = expect_array(body)?;
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let op = set_op(&items[0])?;
let members = match &items[1] {
Value::Array(a) => {
let mut out = Vec::with_capacity(a.len());
for v in a {
match v {
Value::Text(s) => out.push(s.clone()),
_ => return Err(ExpressionDecodeError::SetTstrMemberNotText),
}
}
out
}
_ => return Err(ExpressionDecodeError::SetOperandNotArray),
};
Ok(Self::SetOfTstr { op, members })
}
fn int_range_from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
let items = expect_array(body)?;
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let min = match &items[0] {
Value::Null => None,
Value::Integer(n) => Some(*n),
_ => return Err(ExpressionDecodeError::IntRangeBoundType),
};
let max = match &items[1] {
Value::Null => None,
Value::Integer(n) => Some(*n),
_ => return Err(ExpressionDecodeError::IntRangeBoundType),
};
if let (Some(lo), Some(hi)) = (min, max) {
if lo > hi {
return Err(ExpressionDecodeError::IntRangeReversed);
}
}
Ok(Self::IntRange { min, max })
}
fn min_svn_from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
match body {
Value::Integer(n) if *n >= 0 => {
let v = u64::try_from(*n).map_err(|_| ExpressionDecodeError::MinSvnOutOfRange)?;
Ok(Self::MinSvn(v))
}
Value::Integer(_) => Err(ExpressionDecodeError::MinSvnOutOfRange),
_ => Err(ExpressionDecodeError::MinSvnNotUint),
}
}
fn masked_raw_value_from_body(body: &Value) -> Result<Self, ExpressionDecodeError> {
let items = expect_array(body)?;
if items.len() != 2 {
return Err(ExpressionDecodeError::WrongArity(items.len()));
}
let value = match &items[0] {
Value::Bytes(b) => b.clone(),
_ => return Err(ExpressionDecodeError::MaskedRawValueNotBytes),
};
let mask = match &items[1] {
Value::Bytes(b) => b.clone(),
_ => return Err(ExpressionDecodeError::MaskedRawValueNotBytes),
};
Ok(Self::MaskedRawValue { value, mask })
}
}
fn expect_array(body: &Value) -> Result<&Vec<Value>, ExpressionDecodeError> {
match body {
Value::Array(a) => Ok(a),
_ => Err(ExpressionDecodeError::NotArray),
}
}
fn numeric_op(v: &Value) -> Result<NumericOp, ExpressionDecodeError> {
let code = op_code(v)?;
NumericOp::from_code(code).ok_or(ExpressionDecodeError::UnknownOperator(code))
}
fn set_op(v: &Value) -> Result<SetOp, ExpressionDecodeError> {
let code = op_code(v)?;
SetOp::from_code(code).ok_or(ExpressionDecodeError::UnknownOperator(code))
}
fn op_code(v: &Value) -> Result<i64, ExpressionDecodeError> {
match v {
Value::Integer(n) => {
i64::try_from(*n).map_err(|_| ExpressionDecodeError::OperatorOutOfRange(*n))
}
_ => Err(ExpressionDecodeError::OperatorNotInteger),
}
}
fn numeric_value(v: &Value) -> Result<Numeric, ExpressionDecodeError> {
match v {
Value::Integer(n) => Ok(Numeric::Int(*n)),
Value::Float(f) => Ok(Numeric::Float(*f)),
Value::Tag(1, inner) => match inner.as_ref() {
Value::Integer(n) => Ok(Numeric::Int(*n)),
Value::Float(f) => Ok(Numeric::Float(*f)),
_ => Err(ExpressionDecodeError::NumericOperandWrongType),
},
_ => Err(ExpressionDecodeError::NumericOperandWrongType),
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum ExpressionDecodeError {
NotTagged,
WrongTag(u64),
NotArray,
WrongArity(usize),
OperatorNotInteger,
OperatorOutOfRange(i128),
UnknownOperator(i64),
SetOperandNotArray,
SetTstrMemberNotText,
NumericOperandWrongType,
IntRangeBoundType,
IntRangeReversed,
MinSvnOutOfRange,
MinSvnNotUint,
MaskedRawValueNotBytes,
}
impl core::fmt::Display for ExpressionDecodeError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::NotTagged => write!(f, "expected an Intel expression CBOR tag"),
Self::WrongTag(n) => write!(
f,
"expected tag 60010 / 60020 / 60021 / 564 / 553, got tag {}",
n
),
Self::NotArray => write!(f, "expression tag body must be a CBOR array"),
Self::WrongArity(n) => write!(f, "expression array has wrong arity: {}", 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, "operator code {} not permitted by this tag", n),
Self::SetOperandNotArray => write!(f, "set operand must be an array"),
Self::SetTstrMemberNotText => write!(f, "set-tstr member must be a text string"),
Self::NumericOperandWrongType => {
write!(f, "numeric operand must be an integer or float")
}
Self::IntRangeBoundType => write!(f, "int-range bound must be int or null"),
Self::IntRangeReversed => write!(f, "int-range has min > max"),
Self::MinSvnOutOfRange => write!(f, "tagged-min-svn value does not fit in u64"),
Self::MinSvnNotUint => write!(f, "tagged-min-svn body must be an unsigned integer"),
Self::MaskedRawValueNotBytes => {
write!(f, "tagged-masked-raw-value element must be a byte string")
}
}
}
}
impl core::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::SetOfDigests { op, members } => {
format!(
"{} ({} digest{})",
op.as_str(),
members.len(),
s_plural(members.len())
)
}
Expression::SetOfTstr { op, members } => {
format!(
"{} ({} string{})",
op.as_str(),
members.len(),
s_plural(members.len())
)
}
Expression::IntRange { min, max } => {
let lo = min
.map(|n| n.to_string())
.unwrap_or_else(|| "-∞".to_string());
let hi = max
.map(|n| n.to_string())
.unwrap_or_else(|| "+∞".to_string());
format!("range [{}..{}]", lo, hi)
}
Expression::MinSvn(v) => format!("min-svn {}", v),
Expression::MaskedRawValue { value, mask } => format!(
"masked-bstr <{}-byte value, {}-byte mask>",
value.len(),
mask.len()
),
}
}
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 numeric_expr(items: Vec<Value>) -> Value {
Value::Tag(TAG_INTEL_EXPRESSION, Box::new(Value::Array(items)))
}
fn set_digest_expr(items: Vec<Value>) -> Value {
Value::Tag(
TAG_INTEL_SET_DIGEST_EXPRESSION,
Box::new(Value::Array(items)),
)
}
fn set_tstr_expr(items: Vec<Value>) -> Value {
Value::Tag(TAG_INTEL_SET_TSTR_EXPRESSION, Box::new(Value::Array(items)))
}
#[test]
fn decodes_numeric_ge_int() {
let v = numeric_expr(vec![Value::Integer(OP_GE as i128), 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_eq() {
let v = numeric_expr(vec![Value::Integer(OP_EQ as i128), Value::Integer(7)]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Numeric {
op: NumericOp::Eq,
value: Numeric::Int(7),
}
);
}
#[test]
fn decodes_numeric_lt_float() {
let v = numeric_expr(vec![Value::Integer(OP_LT as i128), 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 = numeric_expr(vec![Value::Integer(OP_GE as i128), inner]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::Numeric {
op: NumericOp::Ge,
value: Numeric::Int(1700000000),
}
);
}
#[test]
fn decodes_set_tstr_member() {
let v = set_tstr_expr(vec![
Value::Integer(OP_MEMBER as i128),
Value::Array(vec![
Value::Text("UpToDate".into()),
Value::Text("OutOfDate".into()),
]),
]);
let e = Expression::from_tag(&v).unwrap();
match &e {
Expression::SetOfTstr { op, members } => {
assert_eq!(*op, SetOp::Member);
assert_eq!(members.len(), 2);
}
other => panic!("expected SetOfTstr, got {:?}", other),
}
assert_eq!(display_expression(&e), "member (2 strings)");
}
#[test]
fn decodes_set_tstr_not_member_singleton_uses_singular() {
let v = set_tstr_expr(vec![
Value::Integer(OP_NOT_MEMBER as i128),
Value::Array(vec![Value::Text("CVE-1".into())]),
]);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(display_expression(&e), "not-member (1 string)");
}
#[test]
fn set_tstr_rejects_non_text_member() {
let v = set_tstr_expr(vec![
Value::Integer(OP_MEMBER as i128),
Value::Array(vec![Value::Integer(1)]),
]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::SetTstrMemberNotText
);
}
#[test]
fn decodes_set_digest_member() {
let digest = Value::Array(vec![Value::Integer(1), Value::Bytes(vec![0u8; 32])]);
let v = set_digest_expr(vec![
Value::Integer(OP_MEMBER as i128),
Value::Array(vec![digest]),
]);
let e = Expression::from_tag(&v).unwrap();
match &e {
Expression::SetOfDigests { op, members } => {
assert_eq!(*op, SetOp::Member);
assert_eq!(members.len(), 1);
}
other => panic!("expected SetOfDigests, got {:?}", other),
}
assert_eq!(display_expression(&e), "member (1 digest)");
}
#[test]
fn decodes_int_range_closed() {
let v = Value::Tag(
TAG_INT_RANGE,
Box::new(Value::Array(vec![Value::Integer(0), Value::Integer(15)])),
);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::IntRange {
min: Some(0),
max: Some(15),
}
);
assert_eq!(display_expression(&e), "range [0..15]");
}
#[test]
fn decodes_int_range_half_open_min() {
let v = Value::Tag(
TAG_INT_RANGE,
Box::new(Value::Array(vec![Value::Null, Value::Integer(10)])),
);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::IntRange {
min: None,
max: Some(10),
}
);
assert_eq!(display_expression(&e), "range [-∞..10]");
}
#[test]
fn decodes_int_range_half_open_max() {
let v = Value::Tag(
TAG_INT_RANGE,
Box::new(Value::Array(vec![Value::Integer(5), Value::Null])),
);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(display_expression(&e), "range [5..+∞]");
}
#[test]
fn int_range_rejects_reversed() {
let v = Value::Tag(
TAG_INT_RANGE,
Box::new(Value::Array(vec![Value::Integer(10), Value::Integer(0)])),
);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::IntRangeReversed
);
}
#[test]
fn int_range_rejects_non_int_non_null_bound() {
let v = Value::Tag(
TAG_INT_RANGE,
Box::new(Value::Array(vec![
Value::Text("0".into()),
Value::Integer(10),
])),
);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::IntRangeBoundType
);
}
#[test]
fn decodes_min_svn() {
let v = Value::Tag(TAG_MIN_SVN, Box::new(Value::Integer(5)));
let e = Expression::from_tag(&v).unwrap();
assert_eq!(e, Expression::MinSvn(5));
assert_eq!(display_expression(&e), "min-svn 5");
}
#[test]
fn min_svn_rejects_negative() {
let v = Value::Tag(TAG_MIN_SVN, Box::new(Value::Integer(-1)));
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::MinSvnOutOfRange
);
}
#[test]
fn min_svn_rejects_non_int() {
let v = Value::Tag(TAG_MIN_SVN, Box::new(Value::Text("5".into())));
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::MinSvnNotUint
);
}
#[test]
fn decodes_masked_raw_value() {
let v = Value::Tag(
TAG_MASKED_RAW_VALUE,
Box::new(Value::Array(vec![
Value::Bytes(vec![0xAA, 0xBB]),
Value::Bytes(vec![0xFF, 0x00]),
])),
);
let e = Expression::from_tag(&v).unwrap();
assert_eq!(
e,
Expression::MaskedRawValue {
value: vec![0xAA, 0xBB],
mask: vec![0xFF, 0x00],
}
);
assert_eq!(
display_expression(&e),
"masked-bstr <2-byte value, 2-byte mask>"
);
}
#[test]
fn masked_raw_value_rejects_wrong_arity() {
let v = Value::Tag(
TAG_MASKED_RAW_VALUE,
Box::new(Value::Array(vec![Value::Bytes(vec![0])])),
);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::WrongArity(1)
);
}
#[test]
fn masked_raw_value_rejects_non_bytes() {
let v = Value::Tag(
TAG_MASKED_RAW_VALUE,
Box::new(Value::Array(vec![
Value::Text("oops".into()),
Value::Bytes(vec![0xFF]),
])),
);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::MaskedRawValueNotBytes
);
}
#[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 numeric_rejects_wrong_arity() {
let v = numeric_expr(vec![Value::Integer(OP_GE as i128)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::WrongArity(1),
);
let v = numeric_expr(vec![
Value::Integer(OP_GE as i128),
Value::Integer(0),
Value::Integer(0),
]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::WrongArity(3),
);
}
#[test]
fn numeric_rejects_set_operator() {
let v = numeric_expr(vec![Value::Integer(OP_MEMBER as i128), Value::Integer(0)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::UnknownOperator(OP_MEMBER),
);
}
#[test]
fn set_rejects_numeric_operator() {
let v = set_tstr_expr(vec![Value::Integer(OP_GE as i128), Value::Array(vec![])]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::UnknownOperator(OP_GE),
);
}
#[test]
fn rejects_non_integer_operator() {
let v = numeric_expr(vec![Value::Text("ge".into()), Value::Integer(0)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::OperatorNotInteger,
);
}
#[test]
fn rejects_unknown_operator_code() {
let v = numeric_expr(vec![Value::Integer(99), Value::Integer(0)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::UnknownOperator(99),
);
}
#[test]
fn set_rejects_non_array_operand() {
let v = set_tstr_expr(vec![Value::Integer(OP_MEMBER as i128), Value::Integer(1)]);
assert_eq!(
Expression::from_tag(&v).unwrap_err(),
ExpressionDecodeError::SetOperandNotArray,
);
}
#[test]
fn is_intel_expression_tag_check() {
assert!(Expression::is_intel_expression_tag(60010));
assert!(Expression::is_intel_expression_tag(60020));
assert!(Expression::is_intel_expression_tag(60021));
assert!(Expression::is_intel_expression_tag(563));
assert!(Expression::is_intel_expression_tag(564));
assert!(Expression::is_intel_expression_tag(553));
assert!(!Expression::is_intel_expression_tag(0));
assert!(!Expression::is_intel_expression_tag(60011));
}
}