@@ -4,13 +4,14 @@ use catalyst_signed_doc::*;
44use catalyst_types:: catalyst_id:: role_index:: RoleId ;
55use common:: create_dummy_key_pair;
66use minicbor:: { data:: Tag , Encoder } ;
7+ use rand:: Rng ;
78
89mod common;
910
1011type PostCheck = dyn Fn ( & CatalystSignedDocument ) -> anyhow:: Result < ( ) > ;
1112
1213struct TestCase {
13- name : & ' static str ,
14+ name : String ,
1415 bytes_gen : Box < dyn Fn ( ) -> anyhow:: Result < Encoder < Vec < u8 > > > > ,
1516 // If the provided bytes can be even decoded without error (valid COSE or not).
1617 // If set to `false` all further checks will not even happen.
@@ -27,8 +28,7 @@ fn signed_doc_with_valid_alias_case(alias: &'static str) -> TestCase {
2728 let doc_ref_cloned = doc_ref. clone ( ) ;
2829
2930 TestCase {
30- name :
31- "Provided category_id, brand_id, campaign_id field should be processed as parameters." ,
31+ name : format ! ( "Provided '{alias}' field should be processed as parameters." ) ,
3232 bytes_gen : Box :: new ( {
3333 move || {
3434 let mut e = Encoder :: new ( Vec :: new ( ) ) ;
@@ -78,14 +78,201 @@ fn signed_doc_with_valid_alias_case(alias: &'static str) -> TestCase {
7878 }
7979}
8080
81+ fn signed_doc_with_missing_header_field_case ( field : & ' static str ) -> TestCase {
82+ let uuid_v7 = UuidV7 :: new ( ) ;
83+ let uuid_v4 = UuidV4 :: new ( ) ;
84+ let doc_ref = DocumentRef :: new ( UuidV7 :: new ( ) , UuidV7 :: new ( ) , DocLocator :: default ( ) ) ;
85+
86+ TestCase {
87+ name : format ! ( "Catalyst Signed Doc with missing '{field}' header." ) ,
88+ bytes_gen : Box :: new ( {
89+ move || {
90+ let mut e = Encoder :: new ( Vec :: new ( ) ) ;
91+ e. tag ( Tag :: new ( 98 ) ) ?;
92+ e. array ( 4 ) ?;
93+
94+ // protected headers (metadata fields)
95+ e. bytes ( {
96+ let mut p_headers = Encoder :: new ( Vec :: new ( ) ) ;
97+ p_headers. map ( 4 ) ?;
98+ if field != "content-type" {
99+ p_headers. u8 ( 3 ) ?. encode ( ContentType :: Json ) ?;
100+ }
101+ if field != "type" {
102+ p_headers
103+ . str ( "type" ) ?
104+ . encode_with ( uuid_v4, & mut catalyst_types:: uuid:: CborContext :: Tagged ) ?;
105+ }
106+ if field != "id" {
107+ p_headers
108+ . str ( "id" ) ?
109+ . encode_with ( uuid_v7, & mut catalyst_types:: uuid:: CborContext :: Tagged ) ?;
110+ }
111+ if field != "ver" {
112+ p_headers
113+ . str ( "ver" ) ?
114+ . encode_with ( uuid_v7, & mut catalyst_types:: uuid:: CborContext :: Tagged ) ?;
115+ }
116+
117+ p_headers
118+ . str ( "parameters" ) ?
119+ . encode_with ( doc_ref. clone ( ) , & mut ( ) ) ?;
120+
121+ p_headers. into_writer ( ) . as_slice ( )
122+ } ) ?;
123+
124+ // empty unprotected headers
125+ e. map ( 0 ) ?;
126+ // content
127+ e. bytes ( serde_json:: to_vec ( & serde_json:: Value :: Null ) ?. as_slice ( ) ) ?;
128+ // zero signatures
129+ e. array ( 0 ) ?;
130+
131+ Ok ( e)
132+ }
133+ } ) ,
134+ can_decode : true ,
135+ valid_doc : false ,
136+ post_checks : Some ( Box :: new ( {
137+ move |doc| {
138+ if field == "content-type" {
139+ anyhow:: ensure!( doc. doc_meta( ) . content_type( ) . is_err( ) ) ;
140+ }
141+ if field == "type" {
142+ anyhow:: ensure!( doc. doc_meta( ) . doc_type( ) . is_err( ) ) ;
143+ }
144+ if field == "id" {
145+ anyhow:: ensure!( doc. doc_meta( ) . doc_id( ) . is_err( ) ) ;
146+ }
147+ if field == "ver" {
148+ anyhow:: ensure!( doc. doc_meta( ) . doc_ver( ) . is_err( ) ) ;
149+ }
150+
151+ Ok ( ( ) )
152+ }
153+ } ) ) ,
154+ }
155+ }
156+
157+ fn signed_doc_with_random_header_field_case ( field : & ' static str ) -> TestCase {
158+ let uuid_v7 = UuidV7 :: new ( ) ;
159+ let uuid_v4 = UuidV4 :: new ( ) ;
160+ let doc_ref = DocumentRef :: new ( UuidV7 :: new ( ) , UuidV7 :: new ( ) , DocLocator :: default ( ) ) ;
161+
162+ TestCase {
163+ name : format ! ( "Catalyst Signed Doc with random bytes in '{field}' header field." ) ,
164+ bytes_gen : Box :: new ( {
165+ move || {
166+ let mut e = Encoder :: new ( Vec :: new ( ) ) ;
167+ e. tag ( Tag :: new ( 98 ) ) ?;
168+ e. array ( 4 ) ?;
169+
170+ // protected headers (metadata fields)
171+ e. bytes ( {
172+ let mut rng = rand:: thread_rng ( ) ;
173+ let mut rand_buf = [ 0u8 ; 128 ] ;
174+ rng. try_fill ( & mut rand_buf) ?;
175+
176+ let is_required_header = [ "type" , "id" , "ver" , "parameters" ]
177+ . iter ( )
178+ . any ( |v| v == & field) ;
179+
180+ let mut p_headers = Encoder :: new ( Vec :: new ( ) ) ;
181+ p_headers. map ( if is_required_header { 5 } else { 6 } ) ?;
182+ if field == "content-type" {
183+ p_headers. u8 ( 3 ) ?. encode_with ( rand_buf, & mut ( ) ) ?;
184+ } else {
185+ p_headers. u8 ( 3 ) ?. encode ( ContentType :: Json ) ?;
186+ }
187+ if field == "type" {
188+ p_headers. str ( "type" ) ?. encode_with ( rand_buf, & mut ( ) ) ?;
189+ } else {
190+ p_headers
191+ . str ( "type" ) ?
192+ . encode_with ( uuid_v4, & mut catalyst_types:: uuid:: CborContext :: Tagged ) ?;
193+ }
194+ if field == "id" {
195+ p_headers. str ( "id" ) ?. encode_with ( rand_buf, & mut ( ) ) ?;
196+ } else {
197+ p_headers
198+ . str ( "id" ) ?
199+ . encode_with ( uuid_v7, & mut catalyst_types:: uuid:: CborContext :: Tagged ) ?;
200+ }
201+ if field == "ver" {
202+ p_headers. str ( "ver" ) ?. encode_with ( rand_buf, & mut ( ) ) ?;
203+ } else {
204+ p_headers
205+ . str ( "ver" ) ?
206+ . encode_with ( uuid_v7, & mut catalyst_types:: uuid:: CborContext :: Tagged ) ?;
207+ }
208+ if field == "parameters" {
209+ p_headers
210+ . str ( "parameters" ) ?
211+ . encode_with ( rand_buf, & mut ( ) ) ?;
212+ } else {
213+ p_headers
214+ . str ( "parameters" ) ?
215+ . encode_with ( doc_ref. clone ( ) , & mut ( ) ) ?;
216+ }
217+
218+ if !is_required_header {
219+ p_headers. str ( field) ?. encode_with ( rand_buf, & mut ( ) ) ?;
220+ }
221+
222+ p_headers. into_writer ( ) . as_slice ( )
223+ } ) ?;
224+
225+ // empty unprotected headers
226+ e. map ( 0 ) ?;
227+ // content
228+ e. bytes ( serde_json:: to_vec ( & serde_json:: Value :: Null ) ?. as_slice ( ) ) ?;
229+ // zero signatures
230+ e. array ( 0 ) ?;
231+
232+ Ok ( e)
233+ }
234+ } ) ,
235+ can_decode : true ,
236+ valid_doc : false ,
237+ post_checks : Some ( Box :: new ( {
238+ move |doc| {
239+ anyhow:: ensure!( doc. doc_meta( ) . content_encoding( ) . is_none( ) ) ;
240+ anyhow:: ensure!( doc. doc_meta( ) . doc_ref( ) . is_none( ) ) ;
241+ anyhow:: ensure!( doc. doc_meta( ) . template( ) . is_none( ) ) ;
242+ anyhow:: ensure!( doc. doc_meta( ) . reply( ) . is_none( ) ) ;
243+ anyhow:: ensure!( doc. doc_meta( ) . section( ) . is_none( ) ) ;
244+ anyhow:: ensure!( doc. doc_meta( ) . collabs( ) . is_empty( ) ) ;
245+
246+ if field == "content-type" {
247+ anyhow:: ensure!( doc. doc_meta( ) . content_type( ) . is_err( ) ) ;
248+ }
249+ if field == "type" {
250+ anyhow:: ensure!( doc. doc_meta( ) . doc_type( ) . is_err( ) ) ;
251+ }
252+ if field == "id" {
253+ anyhow:: ensure!( doc. doc_meta( ) . doc_id( ) . is_err( ) ) ;
254+ }
255+ if field == "ver" {
256+ anyhow:: ensure!( doc. doc_meta( ) . doc_ver( ) . is_err( ) ) ;
257+ }
258+ if field == "parameters" {
259+ anyhow:: ensure!( doc. doc_meta( ) . parameters( ) . is_none( ) ) ;
260+ }
261+
262+ Ok ( ( ) )
263+ }
264+ } ) ) ,
265+ }
266+ }
267+
81268// `parameters` value along with its aliases are not allowed to be presented
82269fn signed_doc_with_parameters_and_aliases_case ( aliases : & ' static [ & ' static str ] ) -> TestCase {
83270 let uuid_v7 = UuidV7 :: new ( ) ;
84271 let uuid_v4 = UuidV4 :: new ( ) ;
85272 let doc_ref = DocumentRef :: new ( UuidV7 :: new ( ) , UuidV7 :: new ( ) , DocLocator :: default ( ) ) ;
86273
87274 TestCase {
88- name : "Multiple definitions of campaign_id, brand_id, category_id and parameters at once." ,
275+ name : format ! ( "Multiple definitions of '{}' at once." , aliases . join ( ", " ) ) ,
89276 bytes_gen : Box :: new ( {
90277 move || {
91278 let mut e = Encoder :: new ( Vec :: new ( ) ) ;
@@ -134,7 +321,7 @@ fn signed_doc_with_parameters_and_aliases_case(aliases: &'static [&'static str])
134321
135322fn decoding_empty_bytes_case ( ) -> TestCase {
136323 TestCase {
137- name : "Decoding empty bytes" ,
324+ name : "Decoding empty bytes" . to_string ( ) ,
138325 bytes_gen : Box :: new ( || Ok ( Encoder :: new ( Vec :: new ( ) ) ) ) ,
139326 can_decode : false ,
140327 valid_doc : false ,
@@ -147,7 +334,7 @@ fn signed_doc_with_all_fields_case() -> TestCase {
147334 let uuid_v4 = UuidV4 :: new ( ) ;
148335
149336 TestCase {
150- name : "Catalyst Signed Doc with minimally defined metadata fields, signed (one signature), CBOR tagged." ,
337+ name : "Catalyst Signed Doc with minimally defined metadata fields, signed (one signature), CBOR tagged." . to_string ( ) ,
151338 bytes_gen : Box :: new ( {
152339 move || {
153340 let ( _, _, kid) = create_dummy_key_pair ( RoleId :: Role0 ) ?;
@@ -203,7 +390,8 @@ fn minimally_valid_tagged_signed_doc() -> TestCase {
203390 let uuid_v7 = UuidV7 :: new ( ) ;
204391 let uuid_v4 = UuidV4 :: new ( ) ;
205392 TestCase {
206- name : "Catalyst Signed Doc with minimally defined metadata fields, unsigned, CBOR tagged." ,
393+ name : "Catalyst Signed Doc with minimally defined metadata fields, unsigned, CBOR tagged."
394+ . to_string ( ) ,
207395 bytes_gen : Box :: new ( {
208396 move || {
209397 let mut e = Encoder :: new ( Vec :: new ( ) ) ;
@@ -260,7 +448,8 @@ fn minimally_valid_untagged_signed_doc() -> TestCase {
260448 let uuid_v7 = UuidV7 :: new ( ) ;
261449 let uuid_v4 = UuidV4 :: new ( ) ;
262450 TestCase {
263- name : "Catalyst Signed Doc with minimally defined metadata fields, unsigned, CBOR tagged." ,
451+ name : "Catalyst Signed Doc with minimally defined metadata fields, unsigned, CBOR tagged."
452+ . to_string ( ) ,
264453 bytes_gen : Box :: new ( {
265454 move || {
266455 let mut e = Encoder :: new ( Vec :: new ( ) ) ;
@@ -319,6 +508,21 @@ fn catalyst_signed_doc_decoding_test() {
319508 signed_doc_with_valid_alias_case ( "category_id" ) ,
320509 signed_doc_with_valid_alias_case ( "brand_id" ) ,
321510 signed_doc_with_valid_alias_case ( "campaign_id" ) ,
511+ signed_doc_with_missing_header_field_case ( "content-type" ) ,
512+ signed_doc_with_missing_header_field_case ( "type" ) ,
513+ signed_doc_with_missing_header_field_case ( "id" ) ,
514+ signed_doc_with_missing_header_field_case ( "ver" ) ,
515+ signed_doc_with_random_header_field_case ( "content-type" ) ,
516+ signed_doc_with_random_header_field_case ( "type" ) ,
517+ signed_doc_with_random_header_field_case ( "id" ) ,
518+ signed_doc_with_random_header_field_case ( "ver" ) ,
519+ signed_doc_with_random_header_field_case ( "ref" ) ,
520+ signed_doc_with_random_header_field_case ( "template" ) ,
521+ signed_doc_with_random_header_field_case ( "reply" ) ,
522+ signed_doc_with_random_header_field_case ( "section" ) ,
523+ signed_doc_with_random_header_field_case ( "collabs" ) ,
524+ signed_doc_with_random_header_field_case ( "parameters" ) ,
525+ signed_doc_with_random_header_field_case ( "content-encoding" ) ,
322526 signed_doc_with_parameters_and_aliases_case ( & [ "parameters" , "category_id" ] ) ,
323527 signed_doc_with_parameters_and_aliases_case ( & [ "parameters" , "brand_id" ] ) ,
324528 signed_doc_with_parameters_and_aliases_case ( & [ "parameters" , "campaign_id" ] ) ,
0 commit comments