Skip to content

Commit 4ee0dc5

Browse files
authored
refactor(rust/signed-doc): Cleanup Builder, make raw_bytes field non-optional (#369)
* wip * wip * wip * wip * wip * cleanup * wip * wip * fix clippy
1 parent dd0a29e commit 4ee0dc5

File tree

14 files changed

+177
-143
lines changed

14 files changed

+177
-143
lines changed

rust/signed_doc/bins/mk_signed_doc.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ impl Cli {
6767
let payload = serde_json::to_vec(&json_doc)?;
6868
// Start with no signatures.
6969
let signed_doc = Builder::new()
70-
.with_decoded_content(payload)
7170
.with_json_metadata(metadata)?
71+
.with_decoded_content(payload)?
7272
.build();
7373
println!(
7474
"report {}",

rust/signed_doc/src/builder.rs

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,19 @@ use catalyst_types::{catalyst_id::CatalystId, problem_report::ProblemReport};
33

44
use crate::{
55
signature::{tbs_data, Signature},
6-
CatalystSignedDocument, Content, InnerCatalystSignedDocument, Metadata, Signatures,
7-
PROBLEM_REPORT_CTX,
6+
CatalystSignedDocument, Content, Metadata, Signatures,
87
};
98

109
/// Catalyst Signed Document Builder.
1110
#[derive(Debug)]
12-
pub struct Builder(InnerCatalystSignedDocument);
11+
pub struct Builder {
12+
/// metadata
13+
metadata: Metadata,
14+
/// content
15+
content: Content,
16+
/// signatures
17+
signatures: Signatures,
18+
}
1319

1420
impl Default for Builder {
1521
fn default() -> Self {
@@ -21,14 +27,11 @@ impl Builder {
2127
/// Start building a signed document
2228
#[must_use]
2329
pub fn new() -> Self {
24-
let report = ProblemReport::new(PROBLEM_REPORT_CTX);
25-
Self(InnerCatalystSignedDocument {
26-
report,
30+
Self {
2731
metadata: Metadata::default(),
2832
content: Content::default(),
2933
signatures: Signatures::default(),
30-
raw_bytes: None,
31-
})
34+
}
3235
}
3336

3437
/// Set document metadata in JSON format
@@ -38,15 +41,21 @@ impl Builder {
3841
/// - Fails if it is invalid metadata fields JSON object.
3942
pub fn with_json_metadata(mut self, json: serde_json::Value) -> anyhow::Result<Self> {
4043
let metadata = serde_json::from_value(json)?;
41-
self.0.metadata = Metadata::from_metadata_fields(metadata, &self.0.report);
44+
self.metadata = Metadata::from_metadata_fields(metadata, &ProblemReport::new(""));
4245
Ok(self)
4346
}
4447

4548
/// Set decoded (original) document content bytes
46-
#[must_use]
47-
pub fn with_decoded_content(mut self, content: Vec<u8>) -> Self {
48-
self.0.content = Content::from_decoded(content);
49-
self
49+
///
50+
/// # Errors
51+
/// - Compression failure
52+
pub fn with_decoded_content(mut self, decoded: Vec<u8>) -> anyhow::Result<Self> {
53+
if let Some(encoding) = self.metadata.content_encoding() {
54+
self.content = encoding.encode(&decoded)?.into();
55+
} else {
56+
self.content = decoded.into();
57+
}
58+
Ok(self)
5059
}
5160

5261
/// Add a signature to the document
@@ -62,29 +71,49 @@ impl Builder {
6271
if kid.is_id() {
6372
anyhow::bail!("Provided kid should be in a uri format, kid: {kid}");
6473
}
65-
let data_to_sign = tbs_data(&kid, &self.0.metadata, &self.0.content)?;
74+
let data_to_sign = tbs_data(&kid, &self.metadata, &self.content)?;
6675
let sign_bytes = sign_fn(data_to_sign);
67-
self.0.signatures.push(Signature::new(kid, sign_bytes));
76+
self.signatures.push(Signature::new(kid, sign_bytes));
6877

6978
Ok(self)
7079
}
7180

7281
/// Build a signed document with the collected error report.
7382
/// Could provide an invalid document.
83+
///
84+
/// # Panics
85+
/// Should not panic
7486
#[must_use]
87+
#[allow(
88+
clippy::unwrap_used,
89+
reason = "At this point all the data MUST be correctly encodable, and the final prepared bytes MUST be correctly decodable as a CatalystSignedDocument object."
90+
)]
7591
pub fn build(self) -> CatalystSignedDocument {
76-
self.0.into()
92+
let mut e = minicbor::Encoder::new(Vec::new());
93+
// COSE_Sign tag
94+
// <!https://datatracker.ietf.org/doc/html/rfc8152#page-9>
95+
e.tag(minicbor::data::Tag::new(98)).unwrap();
96+
e.array(4).unwrap();
97+
// protected headers (metadata fields)
98+
e.bytes(minicbor::to_vec(&self.metadata).unwrap().as_slice())
99+
.unwrap();
100+
// empty unprotected headers
101+
e.map(0).unwrap();
102+
// content
103+
e.encode(&self.content).unwrap();
104+
// signatures
105+
e.encode(self.signatures).unwrap();
106+
107+
CatalystSignedDocument::try_from(e.into_writer().as_slice()).unwrap()
77108
}
78109
}
79110

80111
impl From<&CatalystSignedDocument> for Builder {
81112
fn from(value: &CatalystSignedDocument) -> Self {
82-
Self(InnerCatalystSignedDocument {
113+
Self {
83114
metadata: value.inner.metadata.clone(),
84115
content: value.inner.content.clone(),
85116
signatures: value.inner.signatures.clone(),
86-
report: value.inner.report.clone(),
87-
raw_bytes: None,
88-
})
117+
}
89118
}
90119
}

rust/signed_doc/src/content.rs

Lines changed: 23 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,78 +1,38 @@
11
//! Catalyst Signed Document Content Payload
22
3-
use anyhow::Context;
4-
use catalyst_types::problem_report::ProblemReport;
5-
6-
use crate::metadata::ContentEncoding;
7-
8-
/// Decompressed Document Content type bytes.
3+
/// Document Content bytes (COSE payload).
94
#[derive(Debug, Clone, PartialEq, Default)]
10-
pub struct Content {
11-
/// Original Decompressed Document's data bytes
12-
data: Option<Vec<u8>>,
13-
}
5+
pub struct Content(Vec<u8>);
146

157
impl Content {
16-
/// Creates a new `Content` value, from the encoded data.
17-
/// verifies a Document's content, that it is correctly encoded and it corresponds and
18-
/// parsed to the specified type
19-
pub(crate) fn from_encoded(
20-
mut data: Vec<u8>, content_encoding: Option<ContentEncoding>, report: &ProblemReport,
21-
) -> Self {
22-
if let Some(content_encoding) = content_encoding {
23-
if let Ok(decoded_data) = content_encoding.decode(&data) {
24-
data = decoded_data;
25-
} else {
26-
report.invalid_value(
27-
"payload",
28-
&hex::encode(&data),
29-
&format!("Invalid Document content, should {content_encoding} encodable"),
30-
"Invalid Document content type.",
31-
);
32-
return Self::default();
33-
}
34-
}
35-
Self::from_decoded(data)
8+
/// Return content bytes.
9+
#[must_use]
10+
pub fn bytes(&self) -> &[u8] {
11+
self.0.as_slice()
3612
}
3713

38-
/// Creates a new `Content` value, from the decoded (original) data.
39-
pub(crate) fn from_decoded(data: Vec<u8>) -> Self {
40-
Self { data: Some(data) }
14+
/// Return content byte size.
15+
#[must_use]
16+
pub fn size(&self) -> usize {
17+
self.0.len()
4118
}
19+
}
4220

43-
/// Return an decoded (original) content bytes.
44-
///
45-
/// # Errors
46-
/// - Missing Document content
47-
pub fn decoded_bytes(&self) -> anyhow::Result<&[u8]> {
48-
self.data
49-
.as_deref()
50-
.ok_or(anyhow::anyhow!("Missing Document content"))
21+
impl From<Vec<u8>> for Content {
22+
fn from(value: Vec<u8>) -> Self {
23+
Self(value)
5124
}
25+
}
5226

53-
/// Return an encoded content bytes,
54-
/// by the provided `content_encoding` provided field.
55-
///
56-
/// # Errors
57-
/// - Missing Document content
58-
/// - Failed to encode content.
59-
pub(crate) fn encoded_bytes(
60-
&self, content_encoding: Option<ContentEncoding>,
61-
) -> anyhow::Result<Vec<u8>> {
62-
let content = self.decoded_bytes()?;
63-
if let Some(content_encoding) = content_encoding {
64-
content_encoding
65-
.encode(content)
66-
.context(format!("Failed to encode {content_encoding} content"))
27+
impl minicbor::Encode<()> for Content {
28+
fn encode<W: minicbor::encode::Write>(
29+
&self, e: &mut minicbor::Encoder<W>, _ctx: &mut (),
30+
) -> Result<(), minicbor::encode::Error<W::Error>> {
31+
if self.0.is_empty() {
32+
e.null()?;
6733
} else {
68-
Ok(content.to_vec())
34+
e.bytes(self.0.as_slice())?;
6935
}
70-
}
71-
72-
/// Return content byte size.
73-
/// If content is empty returns `0`.
74-
#[must_use]
75-
pub fn size(&self) -> usize {
76-
self.data.as_ref().map(Vec::len).unwrap_or_default()
36+
Ok(())
7737
}
7838
}

rust/signed_doc/src/lib.rs

Lines changed: 27 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ struct InnerCatalystSignedDocument {
4646
/// raw CBOR bytes of the `CatalystSignedDocument` object.
4747
/// It is important to keep them to have a consistency what comes from the decoding
4848
/// process, so we would return the same data again
49-
raw_bytes: Option<Vec<u8>>,
49+
raw_bytes: Vec<u8>,
5050
}
5151

5252
/// Keep all the contents private.
@@ -110,12 +110,30 @@ impl CatalystSignedDocument {
110110
self.inner.metadata.doc_ver()
111111
}
112112

113-
/// Return document `Content`.
113+
/// Return document content object.
114114
#[must_use]
115-
pub fn doc_content(&self) -> &Content {
115+
pub(crate) fn content(&self) -> &Content {
116116
&self.inner.content
117117
}
118118

119+
/// Return document decoded (original/non compressed) content bytes.
120+
///
121+
/// # Errors
122+
/// - Decompression failure
123+
pub fn decoded_content(&self) -> anyhow::Result<Vec<u8>> {
124+
if let Some(encoding) = self.doc_content_encoding() {
125+
encoding.decode(self.encoded_content())
126+
} else {
127+
Ok(self.encoded_content().to_vec())
128+
}
129+
}
130+
131+
/// Return document encoded (compressed) content bytes.
132+
#[must_use]
133+
pub fn encoded_content(&self) -> &[u8] {
134+
self.content().bytes()
135+
}
136+
119137
/// Return document `ContentType`.
120138
///
121139
/// # Errors
@@ -213,7 +231,7 @@ impl Decode<'_, ()> for CatalystSignedDocument {
213231
let signatures = Signatures::from_cose_sig_list(&cose_sign.signatures, &report);
214232

215233
let content = if let Some(payload) = cose_sign.payload {
216-
Content::from_encoded(payload, metadata.content_encoding(), &report)
234+
payload.into()
217235
} else {
218236
report.missing_field("COSE Sign Payload", "Missing document content (payload)");
219237
Content::default()
@@ -224,7 +242,7 @@ impl Decode<'_, ()> for CatalystSignedDocument {
224242
content,
225243
signatures,
226244
report,
227-
raw_bytes: Some(cose_bytes.to_vec()),
245+
raw_bytes: cose_bytes.to_vec(),
228246
}
229247
.into())
230248
}
@@ -234,33 +252,10 @@ impl<C> Encode<C> for CatalystSignedDocument {
234252
fn encode<W: minicbor::encode::Write>(
235253
&self, e: &mut encode::Encoder<W>, _ctx: &mut C,
236254
) -> Result<(), encode::Error<W::Error>> {
237-
if let Some(raw_bytes) = &self.inner.raw_bytes {
238-
e.writer_mut()
239-
.write_all(raw_bytes)
240-
.map_err(minicbor::encode::Error::write)?;
241-
} else {
242-
// COSE_Sign tag
243-
// <!https://datatracker.ietf.org/doc/html/rfc8152#page-9>
244-
e.tag(minicbor::data::Tag::new(98))?;
245-
e.array(4)?;
246-
// protected headers (metadata fields)
247-
e.bytes(
248-
minicbor::to_vec(self.doc_meta())
249-
.map_err(minicbor::encode::Error::message)?
250-
.as_slice(),
251-
)?;
252-
// empty unprotected headers
253-
e.map(0)?;
254-
// content
255-
let content = self
256-
.doc_content()
257-
.encoded_bytes(self.doc_content_encoding())
258-
.map_err(minicbor::encode::Error::message)?;
259-
e.bytes(content.as_slice())?;
260-
// signatures
261-
e.encode(self.signatures())?;
262-
}
263-
255+
let raw_bytes = &self.inner.raw_bytes;
256+
e.writer_mut()
257+
.write_all(raw_bytes)
258+
.map_err(minicbor::encode::Error::write)?;
264259
Ok(())
265260
}
266261
}

rust/signed_doc/src/signature/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ pub(crate) fn tbs_data(
116116
<minicbor::bytes::ByteVec>::from(minicbor::to_vec(metadata)?),
117117
<minicbor::bytes::ByteVec>::from(protected_header_bytes(kid)?),
118118
minicbor::bytes::ByteArray::from([]),
119-
<minicbor::bytes::ByteVec>::from(content.encoded_bytes(metadata.content_encoding())?),
119+
content,
120120
))?)
121121
}
122122

rust/signed_doc/src/validator/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ where Provider: VerifyingKeyProvider {
321321
return Ok(false);
322322
};
323323

324-
let Ok(tbs_data) = tbs_data(kid, doc.doc_meta(), doc.doc_content()) else {
324+
let Ok(tbs_data) = tbs_data(kid, doc.doc_meta(), doc.content()) else {
325325
doc.report().other(
326326
"Cannot build a COSE to be signed data",
327327
"During creating COSE to be signed data",

rust/signed_doc/src/validator/rules/content_encoding.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ impl ContentEncodingRule {
2424
);
2525
return Ok(false);
2626
}
27+
if content_encoding.decode(doc.encoded_content()).is_err() {
28+
doc.report().invalid_value(
29+
"payload",
30+
&hex::encode(doc.encoded_content()),
31+
&format!(
32+
"Document content (payload) must decodable by the set content encoding type: {content_encoding}"
33+
),
34+
"Invalid Document content value",
35+
);
36+
return Ok(false);
37+
}
2738
} else if !self.optional {
2839
doc.report().missing_field(
2940
"content-encoding",
@@ -54,9 +65,20 @@ mod tests {
5465
serde_json::json!({"content-encoding": content_encoding.to_string() }),
5566
)
5667
.unwrap()
68+
.with_decoded_content(vec![1, 2, 3])
69+
.unwrap()
5770
.build();
5871
assert!(rule.check(&doc).await.unwrap());
5972

73+
// empty content (empty bytes) could not be brotli decoded
74+
let doc = Builder::new()
75+
.with_json_metadata(
76+
serde_json::json!({"content-encoding": content_encoding.to_string() }),
77+
)
78+
.unwrap()
79+
.build();
80+
assert!(!rule.check(&doc).await.unwrap());
81+
6082
let doc = Builder::new()
6183
.with_json_metadata(serde_json::json!({}))
6284
.unwrap()

0 commit comments

Comments
 (0)