@@ -8,6 +8,13 @@ use std::{
8
8
9
9
use coset:: CborSerializable ;
10
10
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
+
11
18
/// Collection of Content Errors.
12
19
pub struct ContentErrors ( Vec < String > ) ;
13
20
@@ -18,20 +25,22 @@ pub struct ContentErrors(Vec<String>);
18
25
pub struct CatalystSignedDocument {
19
26
/// Catalyst Signed Document metadata, raw doc, with content errors.
20
27
inner : Arc < InnerCatalystSignedDocument > ,
21
- /// Content Errors found when parsing the Document
22
- content_errors : Vec < String > ,
23
28
}
24
29
25
30
impl Display for CatalystSignedDocument {
26
31
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 [ " ) ?;
30
35
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:#}" ) ?;
32
42
}
33
- writeln ! ( f, "Content Errors: {:#?}" , self . content_errors) ?;
34
- write ! ( f, "COSE Sign: {:?}" , self . inner. cose_sign)
43
+ writeln ! ( f, "]" )
35
44
}
36
45
}
37
46
@@ -46,6 +55,8 @@ struct InnerCatalystSignedDocument {
46
55
signatures : Vec < coset:: CoseSignature > ,
47
56
/// Raw COSE Sign bytes
48
57
cose_sign : coset:: CoseSign ,
58
+ /// Content Errors found when parsing the Document
59
+ content_errors : Vec < String > ,
49
60
}
50
61
51
62
/// Document Metadata.
@@ -67,6 +78,20 @@ pub struct Metadata {
67
78
pub section : Option < String > ,
68
79
}
69
80
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
+
70
95
impl Default for Metadata {
71
96
fn default ( ) -> Self {
72
97
Self {
@@ -106,12 +131,11 @@ pub enum DocumentRef {
106
131
impl TryFrom < Vec < u8 > > for CatalystSignedDocument {
107
132
type Error = anyhow:: Error ;
108
133
109
- #[ allow( clippy:: todo) ]
110
134
fn try_from ( cose_bytes : Vec < u8 > ) -> Result < Self , Self :: Error > {
111
135
let cose = coset:: CoseSign :: from_slice ( & cose_bytes)
112
136
. map_err ( |e| anyhow:: anyhow!( "Invalid COSE Sign document: {e}" ) ) ?;
113
137
114
- let ( metadata, content_errors) = metadata_from_cose_protected_header ( & cose) ? ;
138
+ let ( metadata, content_errors) = metadata_from_cose_protected_header ( & cose) ;
115
139
let payload = match & cose. payload {
116
140
Some ( payload) => {
117
141
let mut buf = Vec :: new ( ) ;
@@ -130,10 +154,10 @@ impl TryFrom<Vec<u8>> for CatalystSignedDocument {
130
154
payload,
131
155
signatures,
132
156
cose_sign : cose,
157
+ content_errors : content_errors. 0 ,
133
158
} ;
134
159
Ok ( CatalystSignedDocument {
135
160
inner : Arc :: new ( inner) ,
136
- content_errors : content_errors. 0 ,
137
161
} )
138
162
}
139
163
}
@@ -147,7 +171,7 @@ impl CatalystSignedDocument {
147
171
/// Are there any validation errors (as opposed to structural errors.
148
172
#[ must_use]
149
173
pub fn has_error ( & self ) -> bool {
150
- !self . content_errors . is_empty ( )
174
+ !self . inner . content_errors . is_empty ( )
151
175
}
152
176
153
177
/// Return Document Type `UUIDv4`.
@@ -161,14 +185,31 @@ impl CatalystSignedDocument {
161
185
pub fn doc_id ( & self ) -> uuid:: Uuid {
162
186
self . inner . metadata . id
163
187
}
164
- }
165
188
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
+ }
172
213
173
214
/// Generate the COSE protected header used by Catalyst Signed Document.
174
215
fn cose_protected_header ( ) -> coset:: Header {
@@ -212,11 +253,19 @@ fn decode_cbor_document_ref(val: &coset::cbor::Value) -> anyhow::Result<Document
212
253
}
213
254
}
214
255
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
+
215
266
/// Extract `Metadata` from `coset::CoseSign`.
216
267
#[ 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 ) {
220
269
let expected_header = cose_protected_header ( ) ;
221
270
let mut errors = Vec :: new ( ) ;
222
271
@@ -238,15 +287,9 @@ fn metadata_from_cose_protected_header(
238
287
}
239
288
let mut metadata = Metadata :: default ( ) ;
240
289
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) {
250
293
Ok ( doc_type_uuid) => {
251
294
if doc_type_uuid. get_version_num ( ) == 4 {
252
295
metadata. r#type = doc_type_uuid;
@@ -266,15 +309,9 @@ fn metadata_from_cose_protected_header(
266
309
None => errors. push ( "Invalid COSE protected header, missing `type` field" . to_string ( ) ) ,
267
310
} ;
268
311
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) {
278
315
Ok ( doc_id_uuid) => {
279
316
if doc_id_uuid. get_version_num ( ) == 7 {
280
317
metadata. id = doc_id_uuid;
@@ -292,15 +329,9 @@ fn metadata_from_cose_protected_header(
292
329
None => errors. push ( "Invalid COSE protected header, missing `id` field" . to_string ( ) ) ,
293
330
} ;
294
331
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) {
304
335
Ok ( doc_ver_uuid) => {
305
336
let mut is_valid = true ;
306
337
if doc_ver_uuid. get_version_num ( ) != 7 {
@@ -329,53 +360,57 @@ fn metadata_from_cose_protected_header(
329
360
None => errors. push ( "Invalid COSE protected header, missing `ver` field" . to_string ( ) ) ,
330
361
}
331
362
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
+ }
341
374
}
342
375
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
+ }
353
387
}
354
388
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
+ }
365
400
}
366
401
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
+ }
378
413
}
379
414
380
- Ok ( ( metadata, ContentErrors ( errors) ) )
415
+ ( metadata, ContentErrors ( errors) )
381
416
}
0 commit comments