1- //! CTAP 2.1 `authenticatorLargeBlobs` command (`0x0C`). Wire-level model only;
1+ //! CTAP 2.2 `authenticatorLargeBlobs` command (`0x0C`). Wire-level model only;
22//! see [`crate::ops::webauthn::large_blob`] for the high-level read pipeline.
33
44use serde_bytes:: ByteBuf ;
@@ -42,6 +42,48 @@ impl Ctap2LargeBlobsRequest {
4242 pin_uv_auth_protocol : None ,
4343 }
4444 }
45+
46+ /// First chunk of a chunked write. CTAP 2.2 §6.10.2 requires `length` only when `offset == 0`.
47+ /// Pass `None` for `pin_uv_auth` on unprotected authenticators (no clientPin, no built-in UV).
48+ pub fn new_set_first (
49+ chunk : Vec < u8 > ,
50+ total_length : u32 ,
51+ pin_uv_auth : Option < ( Vec < u8 > , u32 ) > ,
52+ ) -> Self {
53+ let ( pin_uv_auth_param, pin_uv_auth_protocol) = match pin_uv_auth {
54+ Some ( ( p, v) ) => ( Some ( ByteBuf :: from ( p) ) , Some ( v) ) ,
55+ None => ( None , None ) ,
56+ } ;
57+ Self {
58+ get : None ,
59+ set : Some ( ByteBuf :: from ( chunk) ) ,
60+ offset : 0 ,
61+ length : Some ( total_length) ,
62+ pin_uv_auth_param,
63+ pin_uv_auth_protocol,
64+ }
65+ }
66+
67+ /// Continuation chunk. CTAP 2.2 §6.10.2 forbids `length` when `offset != 0`.
68+ /// Pass `None` for `pin_uv_auth` on unprotected authenticators.
69+ pub fn new_set_continuation (
70+ chunk : Vec < u8 > ,
71+ offset : u32 ,
72+ pin_uv_auth : Option < ( Vec < u8 > , u32 ) > ,
73+ ) -> Self {
74+ let ( pin_uv_auth_param, pin_uv_auth_protocol) = match pin_uv_auth {
75+ Some ( ( p, v) ) => ( Some ( ByteBuf :: from ( p) ) , Some ( v) ) ,
76+ None => ( None , None ) ,
77+ } ;
78+ Self {
79+ get : None ,
80+ set : Some ( ByteBuf :: from ( chunk) ) ,
81+ offset,
82+ length : None ,
83+ pin_uv_auth_param,
84+ pin_uv_auth_protocol,
85+ }
86+ }
4587}
4688
4789#[ cfg_attr( test, derive( SerializeIndexed ) ) ]
@@ -68,4 +110,70 @@ mod tests {
68110 } ;
69111 assert_eq ! ( map. len( ) , 2 ) ;
70112 }
113+
114+ #[ test]
115+ fn set_first_encodes_length_and_offset_zero ( ) {
116+ let req =
117+ Ctap2LargeBlobsRequest :: new_set_first ( vec ! [ 0x01 , 0x02 ] , 17 , Some ( ( vec ! [ 0xAA ; 16 ] , 2 ) ) ) ;
118+ let bytes = cbor:: to_vec ( & req) . expect ( "serialize" ) ;
119+ let value: cbor:: Value = cbor:: from_slice ( & bytes) . expect ( "deserialize" ) ;
120+ let cbor:: Value :: Map ( map) = value else {
121+ panic ! ( "expected map" ) ;
122+ } ;
123+ let pairs: std:: collections:: BTreeMap < _ , _ > = map
124+ . into_iter ( )
125+ . filter_map ( |( k, v) | match k {
126+ cbor:: Value :: Integer ( i) => Some ( ( i, v) ) ,
127+ _ => None ,
128+ } )
129+ . collect ( ) ;
130+ assert ! ( matches!( pairs. get( & 0x02 ) , Some ( cbor:: Value :: Bytes ( _) ) ) ) ;
131+ assert_eq ! ( pairs. get( & 0x03 ) , Some ( & cbor:: Value :: Integer ( 0 ) ) ) ;
132+ assert_eq ! ( pairs. get( & 0x04 ) , Some ( & cbor:: Value :: Integer ( 17 ) ) ) ;
133+ assert ! ( matches!( pairs. get( & 0x05 ) , Some ( cbor:: Value :: Bytes ( _) ) ) ) ;
134+ assert_eq ! ( pairs. get( & 0x06 ) , Some ( & cbor:: Value :: Integer ( 2 ) ) ) ;
135+ assert ! ( !pairs. contains_key( & 0x01 ) , "get must not be present" ) ;
136+ }
137+
138+ #[ test]
139+ fn set_continuation_omits_length ( ) {
140+ let req =
141+ Ctap2LargeBlobsRequest :: new_set_continuation ( vec ! [ 0xFF ] , 64 , Some ( ( vec ! [ 0xBB ; 16 ] , 2 ) ) ) ;
142+ let bytes = cbor:: to_vec ( & req) . expect ( "serialize" ) ;
143+ let value: cbor:: Value = cbor:: from_slice ( & bytes) . expect ( "deserialize" ) ;
144+ let cbor:: Value :: Map ( map) = value else {
145+ panic ! ( "expected map" ) ;
146+ } ;
147+ let pairs: std:: collections:: BTreeMap < _ , _ > = map
148+ . into_iter ( )
149+ . filter_map ( |( k, v) | match k {
150+ cbor:: Value :: Integer ( i) => Some ( ( i, v) ) ,
151+ _ => None ,
152+ } )
153+ . collect ( ) ;
154+ assert_eq ! ( pairs. get( & 0x03 ) , Some ( & cbor:: Value :: Integer ( 64 ) ) ) ;
155+ assert ! ( !pairs. contains_key( & 0x04 ) , "length must be absent" ) ;
156+ assert ! ( matches!( pairs. get( & 0x02 ) , Some ( cbor:: Value :: Bytes ( _) ) ) ) ;
157+ }
158+
159+ #[ test]
160+ fn set_first_unauthenticated_omits_auth_params ( ) {
161+ // CTAP 2.2 §6.10.2: unprotected authenticators skip the pinUvAuth verification block,
162+ // so the platform omits both pinUvAuthParam and pinUvAuthProtocol.
163+ let req = Ctap2LargeBlobsRequest :: new_set_first ( vec ! [ 0x01 , 0x02 ] , 17 , None ) ;
164+ let bytes = cbor:: to_vec ( & req) . expect ( "serialize" ) ;
165+ let value: cbor:: Value = cbor:: from_slice ( & bytes) . expect ( "deserialize" ) ;
166+ let cbor:: Value :: Map ( map) = value else {
167+ panic ! ( "expected map" ) ;
168+ } ;
169+ let pairs: std:: collections:: BTreeMap < _ , _ > = map
170+ . into_iter ( )
171+ . filter_map ( |( k, v) | match k {
172+ cbor:: Value :: Integer ( i) => Some ( ( i, v) ) ,
173+ _ => None ,
174+ } )
175+ . collect ( ) ;
176+ assert ! ( !pairs. contains_key( & 0x05 ) ) ;
177+ assert ! ( !pairs. contains_key( & 0x06 ) ) ;
178+ }
71179}
0 commit comments