use alloc::string::String;
use alloc::vec::Vec;
use serde::{Deserialize, Serialize};
use crate::error::CompositionError;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum SubjectType {
Wallet,
Did,
EnclaveMeasurement,
ArtefactDigest,
X509Dn,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum SubjectId {
Text(String),
Bytes(Vec<u8>),
}
impl Serialize for SubjectId {
fn serialize<S: serde::Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
match self {
Self::Text(t) => s.serialize_str(t),
Self::Bytes(b) => s.serialize_bytes(b),
}
}
}
impl<'de> Deserialize<'de> for SubjectId {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
struct V;
impl<'de> serde::de::Visitor<'de> for V {
type Value = SubjectId;
fn expecting(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("a Subject id (CBOR tstr/bstr or JSON string / number array)")
}
fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<Self::Value, E> {
Ok(SubjectId::Text(v.into()))
}
fn visit_string<E: serde::de::Error>(self, v: alloc::string::String) -> Result<Self::Value, E> {
Ok(SubjectId::Text(v))
}
fn visit_bytes<E: serde::de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
Ok(SubjectId::Bytes(v.to_vec()))
}
fn visit_byte_buf<E: serde::de::Error>(self, v: alloc::vec::Vec<u8>) -> Result<Self::Value, E> {
Ok(SubjectId::Bytes(v))
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: serde::de::SeqAccess<'de>,
{
let mut buf: alloc::vec::Vec<u8> = match seq.size_hint() {
Some(n) => alloc::vec::Vec::with_capacity(n),
None => alloc::vec::Vec::new(),
};
while let Some(b) = seq.next_element::<u8>()? {
buf.push(b);
}
Ok(SubjectId::Bytes(buf))
}
}
d.deserialize_any(V)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Subject {
#[serde(rename = "type")]
pub subject_type: SubjectType,
pub id: SubjectId,
#[serde(skip_serializing_if = "Option::is_none", default, with = "opt_bytes")]
pub binding: Option<Vec<u8>>,
}
mod opt_bytes {
use alloc::vec::Vec;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
pub fn serialize<S: Serializer>(v: &Option<Vec<u8>>, s: S) -> Result<S::Ok, S::Error> {
match v {
Some(b) => serde_bytes::Bytes::new(b).serialize(s),
None => s.serialize_none(),
}
}
pub fn deserialize<'de, D: Deserializer<'de>>(d: D) -> Result<Option<Vec<u8>>, D::Error> {
let opt: Option<serde_bytes::ByteBuf> = Option::deserialize(d)?;
Ok(opt.map(serde_bytes::ByteBuf::into_vec))
}
}
impl Subject {
pub fn new(
subject_type: SubjectType,
id: SubjectId,
binding: Option<Vec<u8>>,
) -> Result<Self, CompositionError> {
let s = Self {
subject_type,
id,
binding,
};
s.validate_shape()?;
Ok(s)
}
pub fn wallet(key: Vec<u8>) -> Self {
Self { subject_type: SubjectType::Wallet, id: SubjectId::Bytes(key), binding: None }
}
pub fn subject_type(&self) -> &SubjectType { &self.subject_type }
pub fn subject_type_str(&self) -> &str {
match self.subject_type {
SubjectType::Wallet => "wallet",
SubjectType::Did => "did",
SubjectType::EnclaveMeasurement => "enclave-measurement",
SubjectType::ArtefactDigest => "artefact-digest",
SubjectType::X509Dn => "x509-dn",
}
}
pub fn id_bytes(&self) -> Option<&[u8]> {
if let SubjectId::Bytes(b) = &self.id { Some(b) } else { None }
}
pub fn id_utf8(&self) -> Option<&str> {
if let SubjectId::Text(s) = &self.id { Some(s) } else { None }
}
pub fn binding_bytes(&self) -> Option<&[u8]> { self.binding.as_deref() }
pub fn validate_shape(&self) -> Result<(), CompositionError> {
match (&self.subject_type, &self.id) {
(SubjectType::Did, SubjectId::Text(s)) if s.starts_with("did:") => Ok(()),
(SubjectType::Did, _) => Err(CompositionError::Invariant(
"I-1: did subject requires a textual id starting with \"did:\"",
)),
(SubjectType::Wallet, SubjectId::Bytes(b)) if b.len() == 32 || b.len() == 20 => {
Ok(())
}
(SubjectType::Wallet, _) => Err(CompositionError::Invariant(
"I-1: wallet subject requires bytes(20) or bytes(32)",
)),
(SubjectType::EnclaveMeasurement, SubjectId::Bytes(b)) if b.len() == 48 => Ok(()),
(SubjectType::EnclaveMeasurement, _) => Err(CompositionError::Invariant(
"I-1: enclave-measurement subject requires bytes(48)",
)),
(SubjectType::ArtefactDigest, SubjectId::Bytes(b)) if !b.is_empty() => Ok(()),
(SubjectType::ArtefactDigest, _) => Err(CompositionError::Invariant(
"I-1: artefact-digest subject requires non-empty bytes",
)),
(SubjectType::X509Dn, SubjectId::Bytes(b)) if !b.is_empty() => Ok(()),
(SubjectType::X509Dn, _) => Err(CompositionError::Invariant(
"I-1: x509-dn subject requires non-empty DER bytes",
)),
}
}
}