Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- name: Formatting checks
run: cargo fmt --all -- --check
- name: Clippy checks
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "ear"
description = "EAT Attestation Results implementation"
version = "0.3.0"
version = "0.4.0"
edition = "2021"
repository = "https://github.com/veraison/rust-ear"
readme = "README.md"
Expand All @@ -20,4 +20,4 @@ openssl = "0.10.54"
phf = {version = "0.11.1", features = ["macros", "serde"]}
serde = {version = "1.0", features = ["derive"]}
serde_json = {version = "1.0.93", features = ["raw_value"]}
thiserror = "1.0.40"
thiserror = "2.0.16"
26 changes: 25 additions & 1 deletion src/base64.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

use std::fmt::Display;

use base64::{self, engine::general_purpose, Engine as _};
use serde::{
de::{self, Deserialize, Visitor},
Expand All @@ -17,7 +19,7 @@ pub fn decode_str(v: &str) -> Result<Vec<u8>, Error> {

/// a `Vec<u8>` encoded as base64 in human readable serialization
#[derive(Clone, Debug, PartialEq, PartialOrd, Eq, Ord)]
pub struct Bytes(Vec<u8>);
pub struct Bytes(pub Vec<u8>);

impl Bytes {
pub fn new() -> Self {
Expand Down Expand Up @@ -100,3 +102,25 @@ impl Visitor<'_> for BytesVisitor {
Ok(Bytes::from(v))
}
}

impl Display for Bytes {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0
.iter()
.map(|v| format!("{:02x}", v))
.collect::<Vec<_>>()
.join("")
.fmt(f)
}
}

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

#[test]
fn to_string() {
let bytes = Bytes(vec![222u8, 173u8, 190u8, 239u8]);
assert_eq!(bytes.to_string(), "deadbeef".to_string());
}
}
67 changes: 67 additions & 0 deletions src/raw.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
// - tags are stripped when serializing to JSON
// - byte strings are written as base64-encoded strings to JSON (meaning they deserialize as
// text strings, losing their original type).

use std::fmt::Display;

use serde::de::{self, Deserialize, EnumAccess, MapAccess, SeqAccess, Visitor};
use serde::ser::{Serialize, Serializer};
use serde::ser::{SerializeMap as _, SerializeSeq as _, SerializeTupleVariant as _};
Expand Down Expand Up @@ -251,6 +254,35 @@ impl<'de> Visitor<'de> for RawValueVisitor {
}
}

impl Display for RawValue {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Null => "Null".to_string(),
Self::Integer(v) => v.to_string(),
Self::Float(v) => v.to_string(),
Self::Bool(v) => v.to_string(),
Self::Bytes(b) => b.to_string(),
Self::String(s) => format!(r#""{}""#, s),
Self::Array(vs) => format!(
r#"[{}]"#,
vs.iter()
.map(|v| v.to_string())
.collect::<Vec<_>>()
.join(", ")
),
Self::Map(vs) => format!(
r#"{{{}}}"#,
vs.iter()
.map(|(k, v)| format!("{0}: {1}", k, v))
.collect::<Vec<_>>()
.join(", ")
),
Self::Tagged(t, v) => format!("#6.{0}({1})", t, v),
}
.fmt(f)
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down Expand Up @@ -395,4 +427,39 @@ mod test {
buf
);
}

#[test]
fn to_string() {
assert_eq!(RawValue::Null.to_string(), "Null".to_string());
assert_eq!(RawValue::Integer(234).to_string(), "234".to_string());
assert_eq!(RawValue::Float(1.5).to_string(), "1.5".to_string());
assert_eq!(
RawValue::String("foo".to_string()).to_string(),
r#""foo""#.to_string()
);
assert_eq!(
RawValue::Tagged(7, Box::new(RawValue::Bool(true))).to_string(),
"#6.7(true)".to_string()
);
assert_eq!(
RawValue::Bytes(Bytes(vec![222u8, 173u8, 190u8, 239u8])).to_string(),
"deadbeef".to_string()
);
assert_eq!(
RawValue::Array(vec![
RawValue::String("foo".to_string()),
RawValue::String("bar".to_string())
])
.to_string(),
r#"["foo", "bar"]"#.to_string()
);
assert_eq!(
RawValue::Map(vec![
(RawValue::String("foo".to_string()), RawValue::Integer(1)),
(RawValue::String("bar".to_string()), RawValue::Integer(2)),
])
.to_string(),
r#"{"foo": 1, "bar": 2}"#,
)
}
}
14 changes: 13 additions & 1 deletion src/trust/claim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,7 +340,7 @@ pub static SOURCED_DATA_CLAIM_MAP: &Map<i8, ValueDescription<'static>> = &phf_ma
/// This is a claim regarding the trustworthiness of one aspect of the attested environment, as
/// defined in
/// <https://datatracker.ietf.org/doc/html/draft-ietf-rats-ar4si-04#name-trustworthiness-claims>
#[derive(Debug, Clone, Copy)]
#[derive(Clone, Copy)]
pub struct TrustClaim {
/// Claim value
pub value: Option<i8>,
Expand Down Expand Up @@ -464,6 +464,18 @@ impl TrustClaim {
}
}

impl std::fmt::Debug for TrustClaim {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
r#"TrustClaim{{"{0}({1})": {2}}}"#,
self.tag(),
self.key(),
self.value()
)
}
}

impl PartialEq<TrustClaim> for TrustClaim {
fn eq(&self, other: &TrustClaim) -> bool {
self.value() == other.value()
Expand Down
122 changes: 101 additions & 21 deletions src/trust/tier.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// SPDX-License-Identifier: Apache-2.0

use crate::error::Error;

use serde::{
de::{self, Visitor},
ser::{Serialize, Serializer},
Expand All @@ -25,19 +27,9 @@ impl Serialize for TrustTier {
S: Serializer,
{
if serializer.is_human_readable() {
match self {
TrustTier::None => serializer.serialize_str("none"),
TrustTier::Affirming => serializer.serialize_str("affirming"),
TrustTier::Warning => serializer.serialize_str("warning"),
TrustTier::Contraindicated => serializer.serialize_str("contraindicated"),
}
serializer.serialize_str(self.into())
} else {
match self {
TrustTier::None => serializer.serialize_i8(0),
TrustTier::Affirming => serializer.serialize_i8(2),
TrustTier::Warning => serializer.serialize_i8(32),
TrustTier::Contraindicated => serializer.serialize_i8(96),
}
serializer.serialize_i8(self.into())
}
}
}
Expand All @@ -64,13 +56,9 @@ impl Visitor<'_> for TrustTierVisitor {
where
E: de::Error,
{
match value {
0 => Ok(TrustTier::None),
2 => Ok(TrustTier::Affirming),
32 => Ok(TrustTier::Warning),
96 => Ok(TrustTier::Contraindicated),
_ => Err(E::custom(format!("Unexpected TrustTier value: {value}"))),
}
value
.try_into()
.map_err(|_| E::custom(format!("Unexpected TrustTier value: {value}")))
}

fn visit_i16<E>(self, value: i16) -> Result<Self::Value, E>
Expand Down Expand Up @@ -117,12 +105,80 @@ impl Visitor<'_> for TrustTierVisitor {
where
E: de::Error,
{
match value {
value
.try_into()
.map_err(|_| E::custom(format!("Unexpected TrustTier value: {value}")))
}
}

impl TryFrom<&str> for TrustTier {
type Error = Error;

fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"none" => Ok(TrustTier::None),
"affirming" => Ok(TrustTier::Affirming),
"warning" => Ok(TrustTier::Warning),
"contraindicated" => Ok(TrustTier::Contraindicated),
_ => Err(E::custom(format!("Unexpected TrustTier value: {value}"))),
_ => Err(Error::InvalidName(value.to_string())),
}
}
}

impl TryFrom<i8> for TrustTier {
type Error = Error;

fn try_from(value: i8) -> Result<Self, Self::Error> {
match value {
0i8 => Ok(TrustTier::None),
2i8 => Ok(TrustTier::Affirming),
32i8 => Ok(TrustTier::Warning),
96i8 => Ok(TrustTier::Contraindicated),
_ => Err(Error::InvalidValue(value)),
}
}
}

impl From<TrustTier> for String {
fn from(val: TrustTier) -> String {
match val {
TrustTier::None => "none".to_string(),
TrustTier::Affirming => "affirming".to_string(),
TrustTier::Warning => "warning".to_string(),
TrustTier::Contraindicated => "contraindicated".to_string(),
}
}
}

impl<'a, 'b> From<&'a TrustTier> for &'b str {
fn from(val: &'a TrustTier) -> &'b str {
match val {
TrustTier::None => "none",
TrustTier::Affirming => "affirming",
TrustTier::Warning => "warning",
TrustTier::Contraindicated => "contraindicated",
}
}
}

impl From<TrustTier> for i8 {
fn from(val: TrustTier) -> i8 {
match val {
TrustTier::None => 0i8,
TrustTier::Affirming => 2i8,
TrustTier::Warning => 32i8,
TrustTier::Contraindicated => 96i8,
}
}
}

impl From<&TrustTier> for i8 {
fn from(val: &TrustTier) -> i8 {
match val {
TrustTier::None => 0i8,
TrustTier::Affirming => 2i8,
TrustTier::Warning => 32i8,
TrustTier::Contraindicated => 96i8,
}
}
}
Expand Down Expand Up @@ -158,4 +214,28 @@ mod test {
"Semantic(None, \"Unexpected TrustTier value: -2\")"
);
}

#[test]
fn from() {
let tier: TrustTier = 2i8.try_into().unwrap();
assert_eq!(tier, TrustTier::Affirming);

let res = TryInto::<TrustTier>::try_into(7i8).err().unwrap();
assert_eq!(res.to_string(), "invalid value: 7".to_string());

let tier: TrustTier = "WaRniNg".try_into().unwrap();
assert_eq!(tier, TrustTier::Warning);

let res = TryInto::<TrustTier>::try_into("bad").err().unwrap();
assert_eq!(res.to_string(), "invalid name: bad".to_string());
}

#[test]
fn into() {
let i: i8 = TrustTier::Affirming.into();
assert_eq!(i, 2i8);

let s: String = TrustTier::Warning.into();
assert_eq!(s, "warning".to_string());
}
}