@@ -8,6 +8,13 @@ use std::{
88
99use coset:: CborSerializable ;
1010
11+ /// Catalyst Signed Document Content Encoding Key.
12+ const CONTENT_ENCODING_KEY : & str = "content encoding" ;
13+ /// Catalyst Signed Document Content Encoding Value.
14+ const CONTENT_ENCODING_VALUE : & str = "br" ;
15+ /// CBOR tag for UUID content.
16+ const UUID_CBOR_TAG : u64 = 37 ;
17+
1118/// Collection of Content Errors.
1219pub struct ContentErrors ( Vec < String > ) ;
1320
@@ -18,20 +25,22 @@ pub struct ContentErrors(Vec<String>);
1825pub struct CatalystSignedDocument {
1926 /// Catalyst Signed Document metadata, raw doc, with content errors.
2027 inner : Arc < InnerCatalystSignedDocument > ,
21- /// Content Errors found when parsing the Document
22- content_errors : Vec < String > ,
2328}
2429
2530impl Display for CatalystSignedDocument {
2631 fn fmt ( & self , f : & mut Formatter < ' _ > ) -> Result < ( ) , std:: fmt:: Error > {
27- writeln ! ( f, "Metadata: {:? }" , self . inner. metadata) ?;
28- writeln ! ( f, "JSON Payload: { }" , self . inner. payload) ?;
29- writeln ! ( f, "Signatures: " ) ?;
32+ writeln ! ( f, "{ }" , self . inner. metadata) ?;
33+ writeln ! ( f, "JSON Payload {:# }" , self . inner. payload) ?;
34+ writeln ! ( f, "Signatures [ " ) ?;
3035 for signature in & self . inner . signatures {
31- writeln ! ( f, "\t {}" , hex:: encode( signature. signature. as_slice( ) ) ) ?;
36+ writeln ! ( f, " {:#}" , hex:: encode( signature. signature. as_slice( ) ) ) ?;
37+ }
38+ writeln ! ( f, "]" ) ?;
39+ writeln ! ( f, "Content Errors [" ) ?;
40+ for error in & self . inner . content_errors {
41+ writeln ! ( f, " {error:#}" ) ?;
3242 }
33- writeln ! ( f, "Content Errors: {:#?}" , self . content_errors) ?;
34- write ! ( f, "COSE Sign: {:?}" , self . inner. cose_sign)
43+ writeln ! ( f, "]" )
3544 }
3645}
3746
@@ -46,6 +55,8 @@ struct InnerCatalystSignedDocument {
4655 signatures : Vec < coset:: CoseSignature > ,
4756 /// Raw COSE Sign bytes
4857 cose_sign : coset:: CoseSign ,
58+ /// Content Errors found when parsing the Document
59+ content_errors : Vec < String > ,
4960}
5061
5162/// Document Metadata.
@@ -67,6 +78,20 @@ pub struct Metadata {
6778 pub section : Option < String > ,
6879}
6980
81+ impl Display for Metadata {
82+ fn fmt ( & self , f : & mut Formatter < ' _ > ) -> Result < ( ) , std:: fmt:: Error > {
83+ writeln ! ( f, "Metadata {{" ) ?;
84+ writeln ! ( f, " doc_type: {}," , self . r#type) ?;
85+ writeln ! ( f, " doc_id: {}," , self . id) ?;
86+ writeln ! ( f, " doc_ver: {}," , self . ver) ?;
87+ writeln ! ( f, " doc_ref: {:?}," , self . r#ref) ?;
88+ writeln ! ( f, " doc_template: {:?}," , self . template) ?;
89+ writeln ! ( f, " doc_reply: {:?}," , self . reply) ?;
90+ writeln ! ( f, " doc_section: {:?}" , self . section) ?;
91+ writeln ! ( f, "}}" )
92+ }
93+ }
94+
7095impl Default for Metadata {
7196 fn default ( ) -> Self {
7297 Self {
@@ -106,12 +131,11 @@ pub enum DocumentRef {
106131impl TryFrom < Vec < u8 > > for CatalystSignedDocument {
107132 type Error = anyhow:: Error ;
108133
109- #[ allow( clippy:: todo) ]
110134 fn try_from ( cose_bytes : Vec < u8 > ) -> Result < Self , Self :: Error > {
111135 let cose = coset:: CoseSign :: from_slice ( & cose_bytes)
112136 . map_err ( |e| anyhow:: anyhow!( "Invalid COSE Sign document: {e}" ) ) ?;
113137
114- let ( metadata, content_errors) = metadata_from_cose_protected_header ( & cose) ? ;
138+ let ( metadata, content_errors) = metadata_from_cose_protected_header ( & cose) ;
115139 let payload = match & cose. payload {
116140 Some ( payload) => {
117141 let mut buf = Vec :: new ( ) ;
@@ -130,10 +154,10 @@ impl TryFrom<Vec<u8>> for CatalystSignedDocument {
130154 payload,
131155 signatures,
132156 cose_sign : cose,
157+ content_errors : content_errors. 0 ,
133158 } ;
134159 Ok ( CatalystSignedDocument {
135160 inner : Arc :: new ( inner) ,
136- content_errors : content_errors. 0 ,
137161 } )
138162 }
139163}
@@ -147,7 +171,7 @@ impl CatalystSignedDocument {
147171 /// Are there any validation errors (as opposed to structural errors.
148172 #[ must_use]
149173 pub fn has_error ( & self ) -> bool {
150- !self . content_errors . is_empty ( )
174+ !self . inner . content_errors . is_empty ( )
151175 }
152176
153177 /// Return Document Type `UUIDv4`.
@@ -161,14 +185,31 @@ impl CatalystSignedDocument {
161185 pub fn doc_id ( & self ) -> uuid:: Uuid {
162186 self . inner . metadata . id
163187 }
164- }
165188
166- /// Catalyst Signed Document Content Encoding Key.
167- const CONTENT_ENCODING_KEY : & str = "content encoding" ;
168- /// Catalyst Signed Document Content Encoding Value.
169- const CONTENT_ENCODING_VALUE : & str = "br" ;
170- /// CBOR tag for UUID content.
171- const UUID_CBOR_TAG : u64 = 37 ;
189+ /// Return Document Version `UUIDv7`.
190+ #[ must_use]
191+ pub fn doc_ver ( & self ) -> uuid:: Uuid {
192+ self . inner . metadata . ver
193+ }
194+
195+ /// Return Last Document Reference `Option<DocumentRef>`.
196+ #[ must_use]
197+ pub fn doc_ref ( & self ) -> Option < DocumentRef > {
198+ self . inner . metadata . r#ref
199+ }
200+
201+ /// Return Document Template `Option<DocumentRef>`.
202+ #[ must_use]
203+ pub fn doc_template ( & self ) -> Option < DocumentRef > {
204+ self . inner . metadata . template
205+ }
206+
207+ /// Return Document Reply `Option<DocumentRef>`.
208+ #[ must_use]
209+ pub fn doc_reply ( & self ) -> Option < DocumentRef > {
210+ self . inner . metadata . reply
211+ }
212+ }
172213
173214/// Generate the COSE protected header used by Catalyst Signed Document.
174215fn cose_protected_header ( ) -> coset:: Header {
@@ -212,11 +253,19 @@ fn decode_cbor_document_ref(val: &coset::cbor::Value) -> anyhow::Result<Document
212253 }
213254}
214255
256+ /// Find a value for a given key in the protected header.
257+ fn cose_protected_header_find ( cose : & coset:: CoseSign , rest_key : & str ) -> Option < ciborium:: Value > {
258+ cose. protected
259+ . header
260+ . rest
261+ . iter ( )
262+ . find ( |( key, _) | key == & coset:: Label :: Text ( rest_key. to_string ( ) ) )
263+ . map ( |( _, value) | value. clone ( ) )
264+ }
265+
215266/// Extract `Metadata` from `coset::CoseSign`.
216267#[ allow( clippy:: too_many_lines) ]
217- fn metadata_from_cose_protected_header (
218- cose : & coset:: CoseSign ,
219- ) -> anyhow:: Result < ( Metadata , ContentErrors ) > {
268+ fn metadata_from_cose_protected_header ( cose : & coset:: CoseSign ) -> ( Metadata , ContentErrors ) {
220269 let expected_header = cose_protected_header ( ) ;
221270 let mut errors = Vec :: new ( ) ;
222271
@@ -238,15 +287,9 @@ fn metadata_from_cose_protected_header(
238287 }
239288 let mut metadata = Metadata :: default ( ) ;
240289
241- match cose
242- . protected
243- . header
244- . rest
245- . iter ( )
246- . find ( |( key, _) | key == & coset:: Label :: Text ( "type" . to_string ( ) ) )
247- {
248- Some ( ( _, doc_type) ) => {
249- match decode_cbor_uuid ( doc_type) {
290+ match cose_protected_header_find ( cose, "type" ) {
291+ Some ( doc_type) => {
292+ match decode_cbor_uuid ( & doc_type) {
250293 Ok ( doc_type_uuid) => {
251294 if doc_type_uuid. get_version_num ( ) == 4 {
252295 metadata. r#type = doc_type_uuid;
@@ -266,15 +309,9 @@ fn metadata_from_cose_protected_header(
266309 None => errors. push ( "Invalid COSE protected header, missing `type` field" . to_string ( ) ) ,
267310 } ;
268311
269- match cose
270- . protected
271- . header
272- . rest
273- . iter ( )
274- . find ( |( key, _) | key == & coset:: Label :: Text ( "id" . to_string ( ) ) )
275- {
276- Some ( ( _, doc_id) ) => {
277- match decode_cbor_uuid ( doc_id) {
312+ match cose_protected_header_find ( cose, "id" ) {
313+ Some ( doc_id) => {
314+ match decode_cbor_uuid ( & doc_id) {
278315 Ok ( doc_id_uuid) => {
279316 if doc_id_uuid. get_version_num ( ) == 7 {
280317 metadata. id = doc_id_uuid;
@@ -292,15 +329,9 @@ fn metadata_from_cose_protected_header(
292329 None => errors. push ( "Invalid COSE protected header, missing `id` field" . to_string ( ) ) ,
293330 } ;
294331
295- match cose
296- . protected
297- . header
298- . rest
299- . iter ( )
300- . find ( |( key, _) | key == & coset:: Label :: Text ( "ver" . to_string ( ) ) )
301- {
302- Some ( ( _, doc_ver) ) => {
303- match decode_cbor_uuid ( doc_ver) {
332+ match cose_protected_header_find ( cose, "ver" ) {
333+ Some ( doc_ver) => {
334+ match decode_cbor_uuid ( & doc_ver) {
304335 Ok ( doc_ver_uuid) => {
305336 let mut is_valid = true ;
306337 if doc_ver_uuid. get_version_num ( ) != 7 {
@@ -329,53 +360,57 @@ fn metadata_from_cose_protected_header(
329360 None => errors. push ( "Invalid COSE protected header, missing `ver` field" . to_string ( ) ) ,
330361 }
331362
332- if let Some ( ( _, value) ) = cose
333- . protected
334- . header
335- . rest
336- . iter ( )
337- . find ( |( key, _) | key == & coset:: Label :: Text ( "ref" . to_string ( ) ) )
338- {
339- decode_cbor_document_ref ( value)
340- . map_err ( |e| anyhow:: anyhow!( "Invalid COSE protected header `ref` field, err: {e}" ) ) ?;
363+ if let Some ( cbor_doc_ref) = cose_protected_header_find ( cose, "ref" ) {
364+ match decode_cbor_document_ref ( & cbor_doc_ref) {
365+ Ok ( doc_ref) => {
366+ metadata. r#ref = Some ( doc_ref) ;
367+ } ,
368+ Err ( e) => {
369+ errors. push ( format ! (
370+ "Invalid COSE protected header `ref` field, err: {e}"
371+ ) ) ;
372+ } ,
373+ }
341374 }
342375
343- if let Some ( ( _, value) ) = cose
344- . protected
345- . header
346- . rest
347- . iter ( )
348- . find ( |( key, _) | key == & coset:: Label :: Text ( "template" . to_string ( ) ) )
349- {
350- decode_cbor_document_ref ( value) . map_err ( |e| {
351- anyhow:: anyhow!( "Invalid COSE protected header `template` field, err: {e}" )
352- } ) ?;
376+ if let Some ( cbor_doc_template) = cose_protected_header_find ( cose, "template" ) {
377+ match decode_cbor_document_ref ( & cbor_doc_template) {
378+ Ok ( doc_template) => {
379+ metadata. template = Some ( doc_template) ;
380+ } ,
381+ Err ( e) => {
382+ errors. push ( format ! (
383+ "Invalid COSE protected header `template` field, err: {e}"
384+ ) ) ;
385+ } ,
386+ }
353387 }
354388
355- if let Some ( ( _, value) ) = cose
356- . protected
357- . header
358- . rest
359- . iter ( )
360- . find ( |( key, _) | key == & coset:: Label :: Text ( "reply" . to_string ( ) ) )
361- {
362- decode_cbor_document_ref ( value) . map_err ( |e| {
363- anyhow:: anyhow!( "Invalid COSE protected header `reply` field, err: {e}" )
364- } ) ?;
389+ if let Some ( cbor_doc_reply) = cose_protected_header_find ( cose, "reply" ) {
390+ match decode_cbor_document_ref ( & cbor_doc_reply) {
391+ Ok ( doc_reply) => {
392+ metadata. reply = Some ( doc_reply) ;
393+ } ,
394+ Err ( e) => {
395+ errors. push ( format ! (
396+ "Invalid COSE protected header `reply` field, err: {e}"
397+ ) ) ;
398+ } ,
399+ }
365400 }
366401
367- if let Some ( ( _ , value ) ) = cose
368- . protected
369- . header
370- . rest
371- . iter ( )
372- . find ( | ( key , _ ) | key == & coset :: Label :: Text ( "section" . to_string ( ) ) )
373- {
374- anyhow :: ensure! (
375- value . is_text ( ) ,
376- "Invalid COSE protected header, missing `section` field"
377- ) ;
402+ if let Some ( cbor_doc_section ) = cose_protected_header_find ( cose, "section" ) {
403+ match cbor_doc_section . into_text ( ) {
404+ Ok ( doc_section ) => {
405+ metadata . section = Some ( doc_section ) ;
406+ } ,
407+ Err ( e ) => {
408+ errors . push ( format ! (
409+ "Invalid COSE protected header `section` field, err: {e:?}"
410+ ) ) ;
411+ } ,
412+ }
378413 }
379414
380- Ok ( ( metadata, ContentErrors ( errors) ) )
415+ ( metadata, ContentErrors ( errors) )
381416}
0 commit comments