Skip to content

Commit

Permalink
update document content
Browse files Browse the repository at this point in the history
  • Loading branch information
Mr-Leshiy committed Jan 10, 2025
1 parent 2b9282f commit 4455ed2
Show file tree
Hide file tree
Showing 7 changed files with 94 additions and 185 deletions.
50 changes: 50 additions & 0 deletions rust/signed_doc/src/content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
//! Catalyst Signed Document Content Payload
use crate::metadata::{ContentEncoding, ContentType};

/// Decompressed Document Content type bytes.
#[derive(Debug, Clone, PartialEq)]
pub struct Content(Vec<u8>, ContentType);

impl Content {
/// Creates a new `Content` value,
/// verifies a Document's content, that it is correctly encoded and it corresponds and
/// parsed to the specified type
pub fn new(
mut content: Vec<u8>, content_type: ContentType, encoding: Option<ContentEncoding>,
) -> anyhow::Result<Self> {
if let Some(content_encoding) = encoding {
content = content_encoding
.decode(content.as_slice())
.map_err(|e| anyhow::anyhow!("Failed to decode {encoding:?} content: {e}"))?;
}

match content_type {
ContentType::Json => {
serde_json::from_slice::<serde_json::Value>(content.as_slice())?;
},
ContentType::Cbor => {
// TODO impelement a CBOR parsing validation
},
}

Ok(Self(content, content_type))
}

/// Return `true` if Document's content type is Json
#[must_use]
pub fn is_json(&self) -> bool {
matches!(self.1, ContentType::Json)
}

/// Return `true` if Document's content type is Json
#[must_use]
pub fn is_cbor(&self) -> bool {
matches!(self.1, ContentType::Cbor)
}

/// Return content bytes
pub fn bytes(&self) -> Vec<u8> {
self.0.clone()
}
}
112 changes: 41 additions & 71 deletions rust/signed_doc/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,26 @@ use std::{
};

use anyhow::anyhow;
use content::Content;
use coset::{CborSerializable, CoseSignature};

mod content;
mod error;
mod metadata;
mod payload;
mod signature;

pub use metadata::{DocumentRef, Metadata, UuidV7};
use payload::JsonContent;
pub use signature::KidUri;
use signature::Signatures;

/// Inner type that holds the Catalyst Signed Document with parsing errors.
#[derive(Default)]
struct InnerCatalystSignedDocument {
/// Document Metadata
metadata: Metadata,
/// Document Payload viewed as JSON Content
payload: JsonContent,
/// Document Content
content: Content,
/// Signatures
signatures: Signatures,
/// Raw COSE Sign data
cose_sign: coset::CoseSign,
}

/// Keep all the contents private.
Expand All @@ -43,15 +40,9 @@ pub struct CatalystSignedDocument {
impl Display for CatalystSignedDocument {
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
writeln!(f, "{}", self.inner.metadata)?;
writeln!(f, "{:#?}\n", self.inner.payload)?;
writeln!(f, "Signature Information [")?;
for signature in &self.inner.cose_sign.signatures {
writeln!(
f,
" {} 0x{:#}",
String::from_utf8_lossy(&signature.protected.header.key_id),
hex::encode(signature.signature.as_slice())
)?;
for kid in &self.inner.signatures.kids() {
writeln!(f, " {kid}")?;
}
writeln!(f, "]\n")
}
Expand All @@ -67,44 +58,47 @@ impl TryFrom<&[u8]> for CatalystSignedDocument {

let metadata = Metadata::try_from(&cose_sign.protected)?;

let mut content_errors = Vec::new();
let mut payload = JsonContent::default();

if let Some(bytes) = &cose_sign.payload {
match JsonContent::try_from((bytes.as_ref(), metadata.content_encoding())) {
Ok(c) => payload = c,
Err(e) => {
content_errors.push(anyhow!("Invalid Payload: {e}"));
},
}
} else {
content_errors.push(anyhow!("COSE payload is empty"));
};
let mut errors = Vec::new();

let mut signatures = Signatures::default();
match Signatures::try_from(&cose_sign.signatures) {
Ok(s) => signatures = s,
Err(errors) => {
for e in errors.errors() {
content_errors.push(anyhow!("{e}"));
Err(sign_errors) => {
for e in sign_errors.errors() {
errors.push(anyhow!("{e}"));
}
},
}

let inner = InnerCatalystSignedDocument {
metadata,
payload,
signatures,
cose_sign,
};

if !content_errors.is_empty() {
return Err(error::Error(content_errors));
if let Some(payload) = cose_sign.payload {
match Content::new(
payload,
metadata.content_type(),
metadata.content_encoding(),
) {
Ok(content) => {
if !errors.is_empty() {
return Err(error::Error(errors));
}

Ok(CatalystSignedDocument {
inner: InnerCatalystSignedDocument {
metadata,
content,
signatures,
}
.into(),
})
},
Err(e) => {
errors.push(anyhow::anyhow!("Invalid Document Content: {e}"));
Err(error::Error(errors))
},
}
} else {
errors.push(anyhow!("Document content is missing"));
Err(error::Error(errors))
}

Ok(CatalystSignedDocument {
inner: Arc::new(inner),
})
}
}

Expand All @@ -129,34 +123,10 @@ impl CatalystSignedDocument {
self.inner.metadata.doc_ver()
}

/// Return Last Document Reference `Option<DocumentRef>`.
#[must_use]
pub fn doc_ref(&self) -> Option<DocumentRef> {
self.inner.metadata.doc_ref()
}

/// Return Document Template `Option<DocumentRef>`.
#[must_use]
pub fn doc_template(&self) -> Option<DocumentRef> {
self.inner.metadata.doc_template()
}

/// Return Document Reply `Option<DocumentRef>`.
#[must_use]
pub fn doc_reply(&self) -> Option<DocumentRef> {
self.inner.metadata.doc_reply()
}

/// Return Document Reply `Option<DocumentRef>`.
#[must_use]
pub fn doc_section(&self) -> Option<String> {
self.inner.metadata.doc_section()
}

/// Return Raw COSE SIGN bytes.
/// Return document `Content`.
#[must_use]
pub fn cose_sign_bytes(&self) -> Vec<u8> {
self.inner.cose_sign.clone().to_vec().unwrap_or_default()
pub fn document_content(&self) -> &Content {
&self.inner.content
}

/// Return a list of signature KIDs.
Expand Down
5 changes: 2 additions & 3 deletions rust/signed_doc/src/metadata/content_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,11 @@ impl TryFrom<&coset::cbor::Value> for ContentEncoding {

impl ContentEncoding {
/// Decompress a Brotli payload
pub fn decode(self, payload: &Vec<u8>) -> anyhow::Result<Vec<u8>> {
pub fn decode(self, mut payload: &[u8]) -> anyhow::Result<Vec<u8>> {
match self {
Self::Brotli => {
let mut buf = Vec::new();
let mut bytes = payload.as_slice();
brotli::BrotliDecompress(&mut bytes, &mut buf)?;
brotli::BrotliDecompress(&mut payload, &mut buf)?;
Ok(buf)
},
}
Expand Down
2 changes: 1 addition & 1 deletion rust/signed_doc/src/metadata/content_type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use coset::iana::CoapContentFormat;
use serde::{de, Deserialize, Deserializer};

/// Payload Content Type.
#[derive(Copy, Clone, Debug)]
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ContentType {
/// 'application/cbor'
Cbor,
Expand Down
40 changes: 0 additions & 40 deletions rust/signed_doc/src/metadata/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,9 @@ pub struct Metadata {
/// Additional Metadata Fields.
#[serde(flatten)]
extra: AdditionalFields,
/// Metadata Content Errors
#[serde(skip)]
content_errors: Vec<String>,
}

impl Metadata {
/// Returns true if metadata has no validation errors.
#[must_use]
pub fn is_valid(&self) -> bool {
self.content_errors.is_empty()
}

/// Return Document Type `UUIDv4`.
#[must_use]
pub fn doc_type(&self) -> uuid::Uuid {
Expand Down Expand Up @@ -84,36 +75,6 @@ impl Metadata {
pub fn content_encoding(&self) -> Option<ContentEncoding> {
self.content_encoding
}

/// Return Last Document Reference `Option<DocumentRef>`.
#[must_use]
pub fn doc_ref(&self) -> Option<DocumentRef> {
self.extra.doc_ref
}

/// Return Document Template `Option<DocumentRef>`.
#[must_use]
pub fn doc_template(&self) -> Option<DocumentRef> {
self.extra.template
}

/// Return Document Reply `Option<DocumentRef>`.
#[must_use]
pub fn doc_reply(&self) -> Option<DocumentRef> {
self.extra.reply
}

/// Return Document Section `Option<String>`.
#[must_use]
pub fn doc_section(&self) -> Option<String> {
self.extra.section.clone()
}

/// List of Content Errors.
#[must_use]
pub fn content_errors(&self) -> &Vec<String> {
&self.content_errors
}
}

impl Display for Metadata {
Expand All @@ -138,7 +99,6 @@ impl Default for Metadata {
content_type: ContentType::default(),
content_encoding: None,
extra: AdditionalFields::default(),
content_errors: Vec::new(),
}
}
}
Expand Down
38 changes: 0 additions & 38 deletions rust/signed_doc/src/payload/json.rs

This file was deleted.

32 changes: 0 additions & 32 deletions rust/signed_doc/src/payload/mod.rs

This file was deleted.

0 comments on commit 4455ed2

Please sign in to comment.