1use std::str::FromStr;
2
3use serde::{Deserialize, Serialize};
4use serde_cbor_2::Value;
5use std::fmt;
6use uuid::Uuid;
7use webauthn_rs_proto::AuthenticatorTransport;
8
9use self::CBORCommand;
10use super::*;
11
12#[derive(Serialize, Debug, Clone)]
18pub struct GetInfoRequest {}
19
20impl CBORCommand for GetInfoRequest {
21 const CMD: u8 = 0x04;
22 const HAS_PAYLOAD: bool = false;
23 type Response = GetInfoResponse;
24}
25
26#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
30#[serde(try_from = "BTreeMap<u32, Value>", into = "BTreeMap<u32, Value>")]
31pub struct GetInfoResponse {
32 pub versions: BTreeSet<String>,
34 pub extensions: Option<Vec<String>>,
36 pub aaguid: Option<Uuid>,
38 pub options: Option<BTreeMap<String, bool>>,
40 pub max_msg_size: Option<u32>,
42 pub pin_protocols: Option<Vec<u32>>,
44 pub max_cred_count_in_list: Option<u32>,
45 pub max_cred_id_len: Option<u32>,
46 pub transports: Option<Vec<String>>,
51 pub algorithms: Option<Value>,
53 pub max_serialized_large_blob_array: Option<usize>,
54 pub force_pin_change: bool,
55 pub min_pin_length: Option<usize>,
60 pub firmware_version: Option<i128>,
61 pub max_cred_blob_length: Option<usize>,
62 pub max_rpids_for_set_min_pin_length: Option<u32>,
63 pub preferred_platform_uv_attempts: Option<u32>,
64 pub uv_modality: Option<u32>,
65 pub certifications: Option<BTreeMap<String, u8>>,
66
67 pub remaining_discoverable_credentials: Option<u32>,
90
91 pub vendor_prototype_config_commands: Option<BTreeSet<u64>>,
92}
93
94impl fmt::Display for GetInfoResponse {
95 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
96 write!(f, "versions: ")?;
97 if self.versions.is_empty() {
98 writeln!(f, "N/A")?;
99 } else {
100 for v in self.versions.iter() {
101 write!(f, "{v} ")?;
102 }
103 writeln!(f)?;
104 }
105
106 write!(f, "extensions: ")?;
107 for e in self.extensions.iter().flatten() {
108 write!(f, "{e} ")?;
109 }
110 writeln!(f)?;
111
112 match self.aaguid {
113 Some(aaguid) => writeln!(f, "aaguid: {aaguid}")?,
114 None => writeln!(f, "aaguid: INVALID")?,
115 }
116
117 write!(f, "options: ")?;
118 for (o, b) in self.options.iter().flatten() {
119 write!(f, "{o}:{b} ")?;
120 }
121 writeln!(f)?;
122
123 if let Some(v) = self.max_msg_size {
124 writeln!(f, "max message size: {v}")?;
125 }
126
127 write!(f, "PIN protocols: ")?;
128 if !self
129 .pin_protocols
130 .as_ref()
131 .map(|v| !v.is_empty())
132 .unwrap_or_default()
133 {
134 writeln!(f, "N/A")?;
135 } else {
136 for e in self.pin_protocols.iter().flatten() {
137 write!(f, "{e} ")?;
138 }
139 writeln!(f)?;
140 }
141
142 if let Some(v) = self.max_cred_count_in_list {
143 writeln!(f, "max cred count in list: {v}")?;
144 }
145
146 if let Some(v) = self.max_cred_id_len {
147 writeln!(f, "max cred ID length: {v}")?;
148 }
149
150 write!(f, "transports: ")?;
151 if !self
152 .transports
153 .as_ref()
154 .map(|v| !v.is_empty())
155 .unwrap_or_default()
156 {
157 writeln!(f, "N/A")?;
158 } else {
159 for v in self.transports.iter().flatten() {
160 write!(f, "{v} ")?;
161 }
162 writeln!(f)?;
163 }
164
165 if let Some(v) = &self.algorithms {
166 writeln!(f, "algorithms: {v:?}")?;
167 }
168
169 if let Some(v) = self.max_serialized_large_blob_array {
170 writeln!(f, "max serialized large blob array: {v}")?;
171 }
172
173 writeln!(f, "force PIN change: {:?}", self.force_pin_change)?;
174
175 if let Some(v) = self.min_pin_length {
176 writeln!(f, "minimum PIN length: {v}")?;
177 }
178
179 if let Some(v) = self.firmware_version {
180 writeln!(f, "firmware version: 0x{v:X}")?;
181 }
182
183 if let Some(v) = self.max_cred_blob_length {
184 writeln!(f, "max cred blob length: {v}")?;
185 }
186
187 if let Some(v) = self.max_rpids_for_set_min_pin_length {
188 writeln!(f, "max RPIDs for set minimum PIN length: {v}")?;
189 }
190
191 if let Some(v) = self.preferred_platform_uv_attempts {
192 writeln!(f, "preferred platform UV attempts: {v}")?;
193 }
194
195 if let Some(v) = self.uv_modality {
196 writeln!(f, "UV modality: 0x{v:X}")?;
197 }
198
199 if let Some(v) = &self.certifications {
200 writeln!(f, "certifications: {v:?}")?;
201 }
202
203 if let Some(v) = self.remaining_discoverable_credentials {
204 writeln!(f, "remaining discoverable credentials: {v}")?;
205 }
206
207 if let Some(v) = &self.vendor_prototype_config_commands {
208 writeln!(f, "vendor prototype config commands: {v:x?}")?;
209 }
210
211 Ok(())
212 }
213}
214
215impl GetInfoResponse {
216 pub fn get_min_pin_length(&self) -> usize {
220 self.min_pin_length.unwrap_or(4)
221 }
222
223 pub fn get_transports(&self) -> Option<Vec<AuthenticatorTransport>> {
226 self.transports.as_ref().map(|transports| {
227 transports
228 .iter()
229 .filter_map(|transport| FromStr::from_str(transport).ok())
230 .collect()
231 })
232 }
233
234 pub fn get_option(&self, option: &str) -> Option<bool> {
236 self.options
237 .as_ref()
238 .and_then(|o| o.get(option))
239 .map(|v| v.to_owned())
240 }
241
242 pub fn ctap21_biometrics(&self) -> Option<bool> {
254 self.get_option("bioEnroll")
255 }
256
257 pub fn ctap21pre_biometrics(&self) -> Option<bool> {
269 self.get_option("userVerificationMgmtPreview")
270 }
271
272 pub fn user_verification_configured(&self) -> bool {
275 self.get_option("uv").unwrap_or_default()
276 }
277
278 pub fn supports_config(&self) -> bool {
281 self.get_option("authnrCfg").unwrap_or_default()
282 }
283
284 pub fn supports_enterprise_attestation(&self) -> bool {
287 self.get_option("ep").is_some()
288 }
289
290 pub fn make_cred_uv_not_required(&self) -> bool {
293 self.get_option("makeCredUvNotRqd").unwrap_or_default()
294 }
295
296 pub fn ctap21_credential_management(&self) -> bool {
299 self.get_option("credMgmt").unwrap_or_default()
300 }
301
302 pub fn ctap21pre_credential_management(&self) -> bool {
305 self.get_option("credentialMgmtPreview").unwrap_or_default()
306 }
307}
308
309impl TryFrom<BTreeMap<u32, Value>> for GetInfoResponse {
310 type Error = &'static str;
311
312 fn try_from(mut raw: BTreeMap<u32, Value>) -> Result<Self, Self::Error> {
313 trace!("raw = {:?}", raw);
314 let versions = raw
315 .remove(&0x01)
316 .and_then(|v| value_to_set_string(v, "0x01"))
317 .ok_or("0x01")?;
318
319 let extensions = raw
320 .remove(&0x02)
321 .and_then(|v| value_to_vec_string(v, "0x02"));
322
323 let aaguid = raw
324 .remove(&0x03)
325 .and_then(|v| match v {
326 Value::Bytes(x) => Some(x),
327 _ => {
328 error!("Invalid type for 0x03: {:?}", v);
329 None
330 }
331 })
332 .and_then(|v| Uuid::from_slice(&v).ok());
333
334 let options = raw.remove(&0x04).and_then(|v| {
335 if let Value::Map(v) = v {
336 let mut x = BTreeMap::new();
337 for (ka, va) in v.into_iter() {
338 match (ka, va) {
339 (Value::Text(s), Value::Bool(b)) => {
340 x.insert(s, b);
341 }
342 _ => error!("Invalid value inside 0x04"),
343 }
344 }
345 Some(x)
346 } else {
347 error!("Invalid type for 0x04: {:?}", v);
348 None
349 }
350 });
351
352 let max_msg_size = raw.remove(&0x05).and_then(|v| value_to_u32(&v, "0x05"));
353
354 let pin_protocols = raw.remove(&0x06).and_then(|v| value_to_vec_u32(v, "0x06"));
355
356 let max_cred_count_in_list = raw.remove(&0x07).and_then(|v| value_to_u32(&v, "0x07"));
357
358 let max_cred_id_len = raw.remove(&0x08).and_then(|v| value_to_u32(&v, "0x08"));
359
360 let transports = raw
361 .remove(&0x09)
362 .and_then(|v| value_to_vec_string(v, "0x09"));
363
364 let algorithms = raw.remove(&0x0A);
365
366 let max_serialized_large_blob_array =
367 raw.remove(&0x0b).and_then(|v| value_to_usize(v, "0x0b"));
368
369 let force_pin_change = raw
370 .remove(&0x0c)
371 .and_then(|v| value_to_bool(&v, "0x0c"))
372 .unwrap_or_default();
373
374 let min_pin_length = raw.remove(&0x0d).and_then(|v| value_to_usize(v, "0x0d"));
375
376 let firmware_version = raw.remove(&0x0e).and_then(|v| value_to_i128(v, "0x0e"));
377
378 let max_cred_blob_length = raw.remove(&0x0f).and_then(|v| value_to_usize(v, "0x0f"));
379
380 let max_rpids_for_set_min_pin_length =
381 raw.remove(&0x10).and_then(|v| value_to_u32(&v, "0x10"));
382
383 let preferred_platform_uv_attempts =
384 raw.remove(&0x11).and_then(|v| value_to_u32(&v, "0x11"));
385
386 let uv_modality = raw.remove(&0x12).and_then(|v| value_to_u32(&v, "0x12"));
387
388 let certifications = raw
389 .remove(&0x13)
390 .and_then(|v| value_to_map(v, "0x13"))
391 .map(|v| {
392 let mut x = BTreeMap::new();
393 for (ka, va) in v.into_iter() {
394 if let (Value::Text(s), Value::Integer(i)) = (ka, va) {
395 if let Ok(i) = u8::try_from(i) {
396 x.insert(s, i);
397 continue;
398 }
399 }
400 error!("Invalid value inside 0x13");
401 }
402 x
403 });
404
405 let remaining_discoverable_credentials =
406 raw.remove(&0x14).and_then(|v| value_to_u32(&v, "0x14"));
407
408 let vendor_prototype_config_commands =
409 raw.remove(&0x15).and_then(|v| value_to_set_u64(v, "0x15"));
410
411 Ok(GetInfoResponse {
412 versions,
413 extensions,
414 aaguid,
415 options,
416 max_msg_size,
417 pin_protocols,
418 max_cred_count_in_list,
419 max_cred_id_len,
420 transports,
421 algorithms,
422 max_serialized_large_blob_array,
423 force_pin_change,
424 min_pin_length,
425 firmware_version,
426 max_cred_blob_length,
427 max_rpids_for_set_min_pin_length,
428 preferred_platform_uv_attempts,
429 uv_modality,
430 certifications,
431 remaining_discoverable_credentials,
432 vendor_prototype_config_commands,
433 })
434 }
435}
436
437impl From<GetInfoResponse> for BTreeMap<u32, Value> {
438 fn from(value: GetInfoResponse) -> Self {
439 let GetInfoResponse {
440 versions,
441 extensions,
442 aaguid,
443 options,
444 max_msg_size,
445 pin_protocols,
446 max_cred_count_in_list,
447 max_cred_id_len,
448 transports,
449 algorithms,
450 max_serialized_large_blob_array,
451 force_pin_change,
452 min_pin_length,
453 firmware_version,
454 max_cred_blob_length,
455 max_rpids_for_set_min_pin_length,
456 preferred_platform_uv_attempts,
457 uv_modality,
458 certifications,
459 remaining_discoverable_credentials,
460 vendor_prototype_config_commands,
461 } = value;
462
463 let mut o = BTreeMap::from([(
464 0x01,
465 Value::Array(versions.into_iter().map(Value::Text).collect()),
466 )]);
467
468 if let Some(extensions) = extensions {
469 o.insert(
470 0x02,
471 Value::Array(extensions.into_iter().map(Value::Text).collect()),
472 );
473 }
474
475 if let Some(aaguid) = aaguid {
476 o.insert(0x03, Value::Bytes(aaguid.as_bytes().to_vec()));
477 }
478
479 if let Some(options) = options {
480 o.insert(
481 0x04,
482 Value::Map(
483 options
484 .into_iter()
485 .map(|(k, v)| (Value::Text(k), Value::Bool(v)))
486 .collect(),
487 ),
488 );
489 }
490
491 if let Some(max_msg_size) = max_msg_size {
492 o.insert(0x05, Value::Integer(max_msg_size.into()));
493 }
494
495 if let Some(pin_protocols) = pin_protocols {
496 o.insert(
497 0x06,
498 Value::Array(
499 pin_protocols
500 .into_iter()
501 .map(|v| Value::Integer(v.into()))
502 .collect(),
503 ),
504 );
505 }
506
507 if let Some(max_cred_count_in_list) = max_cred_count_in_list {
508 o.insert(0x07, Value::Integer(max_cred_count_in_list.into()));
509 }
510
511 if let Some(max_cred_id_len) = max_cred_id_len {
512 o.insert(0x08, Value::Integer(max_cred_id_len.into()));
513 }
514
515 if let Some(transports) = transports {
516 o.insert(
517 0x09,
518 Value::Array(transports.into_iter().map(Value::Text).collect()),
519 );
520 }
521
522 if let Some(algorithms) = algorithms {
523 o.insert(0x0a, algorithms);
524 }
525
526 if let Some(v) = max_serialized_large_blob_array {
527 o.insert(0x0b, Value::Integer((v as u32).into()));
528 }
529
530 if force_pin_change {
531 o.insert(0x0c, Value::Bool(true));
532 }
533
534 if let Some(min_pin_length) = min_pin_length {
535 o.insert(0x0d, Value::Integer((min_pin_length as u64).into()));
536 }
537
538 if let Some(v) = firmware_version {
539 o.insert(0x0e, Value::Integer(v));
540 }
541
542 if let Some(v) = max_cred_blob_length {
543 o.insert(0x0f, Value::Integer((v as u64).into()));
544 }
545
546 if let Some(v) = max_rpids_for_set_min_pin_length {
547 o.insert(0x10, Value::Integer(v.into()));
548 }
549
550 if let Some(v) = preferred_platform_uv_attempts {
551 o.insert(0x11, Value::Integer(v.into()));
552 }
553
554 if let Some(v) = uv_modality {
555 o.insert(0x12, Value::Integer(v.into()));
556 }
557
558 if let Some(v) = certifications {
559 o.insert(
560 0x13,
561 Value::Map(
562 v.into_iter()
563 .map(|(k, v)| (Value::Text(k), Value::Integer(v.into())))
564 .collect(),
565 ),
566 );
567 }
568
569 if let Some(v) = remaining_discoverable_credentials {
570 o.insert(0x14, Value::Integer(v.into()));
571 }
572
573 if let Some(v) = vendor_prototype_config_commands {
574 o.insert(
575 0x15,
576 Value::Array(v.into_iter().map(|v| Value::Integer(v.into())).collect()),
577 );
578 }
579
580 o
581 }
582}
583
584crate::deserialize_cbor!(GetInfoResponse);
585
586#[cfg(test)]
587mod tests {
588 use super::*;
589 use crate::transport::iso7816::ISO7816LengthForm;
590 use uuid::uuid;
591
592 #[test]
593 fn get_info_response_nfc_usb() {
594 let _ = tracing_subscriber::fmt().try_init();
595
596 let raw_apdu: Vec<u8> = vec![
597 170, 1, 131, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 108, 70,
598 73, 68, 79, 95, 50, 95, 49, 95, 80, 82, 69, 2, 130, 107, 99, 114, 101, 100, 80, 114,
599 111, 116, 101, 99, 116, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, 116, 3, 80,
600 47, 192, 87, 159, 129, 19, 71, 234, 177, 22, 187, 90, 141, 185, 32, 42, 4, 165, 98,
601 114, 107, 245, 98, 117, 112, 245, 100, 112, 108, 97, 116, 244, 105, 99, 108, 105, 101,
602 110, 116, 80, 105, 110, 245, 117, 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 77,
603 103, 109, 116, 80, 114, 101, 118, 105, 101, 119, 245, 5, 25, 4, 176, 6, 129, 1, 7, 8,
604 8, 24, 128, 9, 130, 99, 110, 102, 99, 99, 117, 115, 98, 10, 130, 162, 99, 97, 108, 103,
605 38, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 162,
606 99, 97, 108, 103, 39, 100, 116, 121, 112, 101, 106, 112, 117, 98, 108, 105, 99, 45,
607 107, 101, 121,
608 ];
609
610 let a = <GetInfoResponse as CBORResponse>::try_from(raw_apdu.as_slice())
611 .expect("Falied to decode apdu");
612
613 assert!(a.versions.len() == 3);
617 assert!(a.versions.contains("U2F_V2"));
618 assert!(a.versions.contains("FIDO_2_0"));
619 assert!(a.versions.contains("FIDO_2_1_PRE"));
620
621 assert!(a.extensions == Some(vec!["credProtect".to_string(), "hmac-secret".to_string()]));
622 assert_eq!(
623 a.aaguid,
624 Some(uuid!("2fc0579f-8113-47ea-b116-bb5a8db9202a"))
625 );
626
627 let m = a.options.as_ref().unwrap();
628 assert!(m.len() == 5);
629 assert!(m.get("clientPin") == Some(&true));
630 assert!(m.get("credentialMgmtPreview") == Some(&true));
631 assert!(m.get("plat") == Some(&false));
632 assert!(m.get("rk") == Some(&true));
633 assert!(m.get("up") == Some(&true));
634
635 assert!(a.max_msg_size == Some(1200));
636 assert!(a.max_cred_count_in_list == Some(8));
637 assert!(a.max_cred_id_len == Some(128));
638
639 assert!(a.transports == Some(vec!["nfc".to_string(), "usb".to_string()]));
640 }
641
642 #[test]
643 fn token2_nfc() {
644 let _ = tracing_subscriber::fmt().try_init();
645
646 let raw_apdu = vec![
647 178, 1, 132, 102, 85, 50, 70, 95, 86, 50, 104, 70, 73, 68, 79, 95, 50, 95, 48, 104, 70,
648 73, 68, 79, 95, 50, 95, 49, 108, 70, 73, 68, 79, 95, 50, 95, 49, 95, 80, 82, 69, 2,
649 133, 104, 99, 114, 101, 100, 66, 108, 111, 98, 107, 99, 114, 101, 100, 80, 114, 111,
650 116, 101, 99, 116, 107, 104, 109, 97, 99, 45, 115, 101, 99, 114, 101, 116, 108, 108,
651 97, 114, 103, 101, 66, 108, 111, 98, 75, 101, 121, 108, 109, 105, 110, 80, 105, 110,
652 76, 101, 110, 103, 116, 104, 3, 80, 171, 50, 240, 198, 34, 57, 175, 187, 196, 112, 210,
653 239, 78, 37, 77, 183, 4, 172, 98, 114, 107, 245, 98, 117, 112, 245, 100, 112, 108, 97,
654 116, 244, 104, 97, 108, 119, 97, 121, 115, 85, 118, 244, 104, 99, 114, 101, 100, 77,
655 103, 109, 116, 245, 105, 97, 117, 116, 104, 110, 114, 67, 102, 103, 245, 105, 99, 108,
656 105, 101, 110, 116, 80, 105, 110, 245, 106, 108, 97, 114, 103, 101, 66, 108, 111, 98,
657 115, 245, 110, 112, 105, 110, 85, 118, 65, 117, 116, 104, 84, 111, 107, 101, 110, 245,
658 111, 115, 101, 116, 77, 105, 110, 80, 73, 78, 76, 101, 110, 103, 116, 104, 245, 112,
659 109, 97, 107, 101, 67, 114, 101, 100, 85, 118, 78, 111, 116, 82, 113, 100, 245, 117,
660 99, 114, 101, 100, 101, 110, 116, 105, 97, 108, 77, 103, 109, 116, 80, 114, 101, 118,
661 105, 101, 119, 245, 5, 25, 6, 0, 6, 130, 2, 1, 7, 8, 8, 24, 96, 9, 130, 99, 117, 115,
662 98, 99, 110, 102, 99, 10, 129, 162, 99, 97, 108, 103, 38, 100, 116, 121, 112, 101, 106,
663 112, 117, 98, 108, 105, 99, 45, 107, 101, 121, 11, 25, 8, 0, 12, 244, 13, 4, 14, 25, 1,
664 0, 15, 24, 32, 16, 6, 19, 161, 100, 70, 73, 68, 79, 1, 20, 24, 50,
665 ];
666 let a = <GetInfoResponse as CBORResponse>::try_from(raw_apdu.as_slice())
667 .expect("Falied to decode apdu");
668
669 assert_eq!(a.versions.len(), 4);
671 assert!(a.versions.contains("U2F_V2"));
672 assert!(a.versions.contains("FIDO_2_0"));
673 assert!(a.versions.contains("FIDO_2_1_PRE"));
674 assert!(a.versions.contains("FIDO_2_1"));
675
676 assert_eq!(
677 a.extensions,
678 Some(vec![
679 "credBlob".to_string(),
680 "credProtect".to_string(),
681 "hmac-secret".to_string(),
682 "largeBlobKey".to_string(),
683 "minPinLength".to_string()
684 ])
685 );
686
687 assert_eq!(
688 a.aaguid,
689 Some(uuid!("ab32f0c6-2239-afbb-c470-d2ef4e254db7"))
690 );
691
692 assert_eq!(a.get_option("alwaysUv"), Some(false));
693 assert_eq!(a.get_option("authnrCfg"), Some(true));
694 assert!(a.supports_config());
695 assert_eq!(a.get_option("clientPin"), Some(true));
696 assert_eq!(a.get_option("credMgmt"), Some(true));
697 assert_eq!(a.get_option("credentialMgmtPreview"), Some(true));
698 assert_eq!(a.get_option("largeBlobs"), Some(true));
699 assert_eq!(a.get_option("makeCredUvNotRqd"), Some(true));
700 assert!(a.make_cred_uv_not_required());
701 assert_eq!(a.get_option("pinUvAuthToken"), Some(true));
702 assert_eq!(a.get_option("plat"), Some(false));
703 assert_eq!(a.get_option("rk"), Some(true));
704 assert_eq!(a.get_option("setMinPINLength"), Some(true));
705 assert_eq!(a.get_option("up"), Some(true));
706
707 assert!(a.ctap21_biometrics().is_none());
708 assert!(a.ctap21pre_biometrics().is_none());
709 assert!(!a.supports_enterprise_attestation());
710 assert!(!a.user_verification_configured());
711
712 assert_eq!(a.max_msg_size, Some(1536));
713 assert_eq!(a.pin_protocols, Some(vec![2, 1]));
714 assert_eq!(a.max_cred_count_in_list, Some(8));
715 assert_eq!(a.max_cred_id_len, Some(96));
716 assert_eq!(
717 a.transports,
718 Some(vec!["usb".to_string(), "nfc".to_string()])
719 );
720 assert_eq!(
721 a.get_transports(),
722 Some(vec![
723 AuthenticatorTransport::Usb,
724 AuthenticatorTransport::Nfc
725 ])
726 );
727
728 assert_eq!(a.max_serialized_large_blob_array, Some(2048));
729 assert!(!a.force_pin_change);
730 assert_eq!(a.min_pin_length, Some(4));
731 assert_eq!(a.get_min_pin_length(), 4);
732 assert_eq!(a.firmware_version, Some(0x100));
733 assert_eq!(a.max_cred_blob_length, Some(32));
734 assert_eq!(a.max_rpids_for_set_min_pin_length, Some(6));
735 assert!(a.preferred_platform_uv_attempts.is_none());
736 assert!(a.uv_modality.is_none());
737 assert_eq!(
738 a.certifications,
739 Some(BTreeMap::from([("FIDO".to_string(), 1)]))
740 );
741 assert_eq!(a.remaining_discoverable_credentials, Some(50));
742 assert!(a.vendor_prototype_config_commands.is_none());
743 }
744
745 #[test]
746 fn get_info_request() {
747 let req = GetInfoRequest {};
748 let short = vec![0x80, 0x10, 0, 0, 1, 0x4, 0];
749 let ext = vec![0x80, 0x10, 0, 0, 0, 0, 1, 0x4, 0, 0];
750
751 let a = to_short_apdus(&req.cbor().unwrap());
752 assert_eq!(1, a.len());
753 assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::ShortOnly).unwrap());
754 assert_eq!(short, a[0].to_bytes(&ISO7816LengthForm::Extended).unwrap());
755
756 assert_eq!(
757 ext,
758 to_extended_apdu(req.cbor().unwrap())
759 .to_bytes(&ISO7816LengthForm::Extended)
760 .unwrap()
761 );
762 assert_eq!(
763 ext,
764 to_extended_apdu(req.cbor().unwrap())
765 .to_bytes(&ISO7816LengthForm::ExtendedOnly)
766 .unwrap()
767 );
768 assert!(to_extended_apdu(req.cbor().unwrap())
769 .to_bytes(&ISO7816LengthForm::ShortOnly)
770 .is_err());
771 }
772}