common_access_token/token.rs
1//! Token implementation for Common Access Token
2
3use crate::claims::{Claims, RegisteredClaims};
4use crate::constants::tprint_params;
5use crate::error::Error;
6use crate::header::{Algorithm, CborValue, Header, HeaderMap, KeyId};
7use crate::utils::{compute_hmac_sha256, current_timestamp, verify_hmac_sha256};
8use crate::FingerprintType;
9use minicbor::{Decoder, Encoder};
10use std::collections::BTreeMap;
11use std::ffi::OsStr;
12use std::path::Path;
13
14/// Common Access Token structure
15#[derive(Debug, Clone)]
16pub struct Token {
17 /// Token header
18 pub header: Header,
19 /// Token claims
20 pub claims: Claims,
21 /// Token signature
22 pub signature: Vec<u8>,
23 /// Original payload bytes (for verification)
24 original_payload_bytes: Option<Vec<u8>>,
25}
26
27impl Token {
28 /// Create a new token with the given header, claims, and signature
29 pub fn new(header: Header, claims: Claims, signature: Vec<u8>) -> Self {
30 Self {
31 header,
32 claims,
33 signature,
34 original_payload_bytes: None,
35 }
36 }
37
38 /// Encode the token to CBOR bytes
39 pub fn to_bytes(&self) -> Result<Vec<u8>, Error> {
40 let mut buf = Vec::new();
41 let mut enc = Encoder::new(&mut buf);
42
43 // For HMAC algorithms, use COSE_Mac0 format with CWT tag
44 if let Some(Algorithm::HmacSha256) = self.header.algorithm() {
45 // Apply CWT tag (61)
46 enc.tag(minicbor::data::Tag::new(61))?;
47 // Apply COSE_Mac0 tag (17)
48 enc.tag(minicbor::data::Tag::new(17))?;
49 }
50
51 // COSE structure array with 4 items
52 enc.array(4)?;
53
54 // 1. Protected header (encoded as CBOR and then as bstr)
55 let protected_bytes = encode_map(&self.header.protected)?;
56 enc.bytes(&protected_bytes)?;
57
58 // 2. Unprotected header
59 encode_map_direct(&self.header.unprotected, &mut enc)?;
60
61 // 3. Payload (encoded as CBOR and then as bstr)
62 let claims_map = self.claims.to_map();
63 let claims_bytes = encode_map(&claims_map)?;
64 enc.bytes(&claims_bytes)?;
65
66 // 4. Signature/MAC
67 enc.bytes(&self.signature)?;
68
69 Ok(buf)
70 }
71
72 /// Decode a token from CBOR bytes
73 ///
74 /// This function supports both COSE_Sign1 (tag 18) and COSE_Mac0 (tag 17) structures,
75 /// as well as custom tags. It will automatically skip any tags and process the underlying
76 /// CBOR array.
77 pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
78 let mut dec = Decoder::new(bytes);
79
80 // Check if the token starts with a tag (COSE_Sign1 tag = 18, COSE_Mac0 tag = 17, or custom tag = 61)
81 if dec.datatype()? == minicbor::data::Type::Tag {
82 // Skip the tag
83 let _ = dec.tag()?;
84
85 // Check for a second tag
86 if dec.datatype()? == minicbor::data::Type::Tag {
87 let _ = dec.tag()?;
88 }
89 }
90
91 // Expect array with 4 items
92 let array_len = dec.array()?.unwrap_or(0);
93 if array_len != 4 {
94 return Err(Error::InvalidFormat(format!(
95 "Expected array of length 4, got {array_len}"
96 )));
97 }
98
99 // 1. Protected header
100 let protected_bytes = dec.bytes()?;
101 let protected = decode_map(protected_bytes)?;
102
103 // 2. Unprotected header
104 let unprotected = decode_map_direct(&mut dec)?;
105
106 // Create header
107 let header = Header {
108 protected,
109 unprotected,
110 };
111
112 // 3. Payload
113 let claims_bytes = dec.bytes()?;
114 let claims_map = decode_map(claims_bytes)?;
115 let claims = Claims::from_map(&claims_map);
116
117 // 4. Signature
118 let signature = dec.bytes()?.to_vec();
119
120 Ok(Self {
121 header,
122 claims,
123 signature,
124 original_payload_bytes: Some(claims_bytes.to_vec()),
125 })
126 }
127
128 /// Verify the token signature
129 ///
130 /// This function supports both COSE_Sign1 and COSE_Mac0 structures.
131 /// It will first try to verify the signature using the COSE_Sign1 structure,
132 /// and if that fails, it will try the COSE_Mac0 structure.
133 pub fn verify(&self, key: &[u8]) -> Result<(), Error> {
134 let alg = self.header.algorithm().ok_or_else(|| {
135 Error::InvalidFormat("Missing algorithm in protected header".to_string())
136 })?;
137
138 match alg {
139 Algorithm::HmacSha256 => {
140 // Try with COSE_Sign1 structure first
141 let sign1_input = self.sign1_input()?;
142 let sign1_result = verify_hmac_sha256(key, &sign1_input, &self.signature);
143
144 if sign1_result.is_ok() {
145 return Ok(());
146 }
147
148 // If COSE_Sign1 verification fails, try COSE_Mac0 structure
149 let mac0_input = self.mac0_input()?;
150 verify_hmac_sha256(key, &mac0_input, &self.signature)
151 }
152 }
153 }
154
155 /// Verify the token claims
156 pub fn verify_claims(&self, options: &VerificationOptions) -> Result<(), Error> {
157 let now = current_timestamp();
158
159 // Check expiration
160 if options.verify_exp {
161 if let Some(exp) = self.claims.registered.exp {
162 if now >= exp {
163 return Err(Error::Expired);
164 }
165 } else if options.require_exp {
166 return Err(Error::MissingClaim("exp".to_string()));
167 }
168 }
169
170 // Check not before
171 if options.verify_nbf {
172 if let Some(nbf) = self.claims.registered.nbf {
173 if now < nbf {
174 return Err(Error::NotYetValid);
175 }
176 }
177 }
178
179 // Check issuer
180 if let Some(expected_iss) = &options.expected_issuer {
181 if let Some(iss) = &self.claims.registered.iss {
182 if iss != expected_iss {
183 return Err(Error::InvalidIssuer);
184 }
185 } else if options.require_iss {
186 return Err(Error::MissingClaim("iss".to_string()));
187 }
188 }
189
190 // Check audience
191 if let Some(expected_aud) = &options.expected_audience {
192 if let Some(aud) = &self.claims.registered.aud {
193 if aud != expected_aud {
194 return Err(Error::InvalidAudience);
195 }
196 } else if options.require_aud {
197 return Err(Error::MissingClaim("aud".to_string()));
198 }
199 }
200
201 // Check CAT-specific claims
202 if options.verify_catu {
203 self.verify_catu_claim(options)?;
204 }
205
206 if options.verify_catm {
207 self.verify_catm_claim(options)?;
208 }
209
210 if options.verify_catreplay {
211 self.verify_catreplay_claim(options)?;
212 }
213
214 if options.verify_cattprint {
215 self.verify_cattprint_claim(options)?;
216 }
217
218 Ok(())
219 }
220
221 /// Verify the CATU (URI) claim against the provided URI
222 fn verify_catu_claim(&self, options: &VerificationOptions) -> Result<(), Error> {
223 use crate::constants::{cat_keys, uri_components};
224 use url::Url;
225
226 // Get the URI to verify against
227 let uri = match &options.uri {
228 Some(uri) => uri,
229 None => {
230 return Err(Error::InvalidClaimValue(
231 "No URI provided for CATU verification".to_string(),
232 ))
233 }
234 };
235
236 // Parse the URI
237 let parsed_uri = match Url::parse(uri) {
238 Ok(url) => url,
239 Err(_) => {
240 return Err(Error::InvalidClaimValue(format!(
241 "Invalid URI format: {uri}"
242 )))
243 }
244 };
245
246 // Parse the Path from the URI
247 let parsed_path = Path::new(parsed_uri.path());
248
249 // Check if token has CATU claim
250 let catu_claim = match self.claims.custom.get(&cat_keys::CATU) {
251 Some(claim) => claim,
252 None => return Ok(()), // No CATU claim, so nothing to verify
253 };
254
255 // CATU claim should be a map
256 let component_map = match catu_claim {
257 CborValue::Map(map) => map,
258 _ => {
259 return Err(Error::InvalidUriClaim(
260 "CATU claim is not a map".to_string(),
261 ))
262 }
263 };
264
265 // Verify each component in the CATU claim
266 for (component_key, component_value) in component_map {
267 match *component_key {
268 uri_components::SCHEME => {
269 self.verify_uri_component(
270 &parsed_uri.scheme().to_string(),
271 component_value,
272 "scheme",
273 )?;
274 }
275 uri_components::HOST => {
276 self.verify_uri_component(
277 &parsed_uri.host_str().unwrap_or("").to_string(),
278 component_value,
279 "host",
280 )?;
281 }
282 uri_components::PORT => {
283 let port = parsed_uri.port().map(|p| p.to_string()).unwrap_or_default();
284 self.verify_uri_component(&port, component_value, "port")?;
285 }
286 uri_components::PATH => {
287 self.verify_uri_component(
288 &parsed_uri.path().to_string(),
289 component_value,
290 "path",
291 )?;
292 }
293 uri_components::QUERY => {
294 let query = parsed_uri.query().unwrap_or("").to_string();
295 self.verify_uri_component(&query, component_value, "query")?;
296 }
297 uri_components::PARENT_PATH => {
298 // Extract parent directory path from URI path.
299 // For URI "https://example.com/a/b/file.txt", this extracts "/a/b".
300 // For root-level files, this returns an empty string.
301 // Non-UTF8 paths are converted to empty strings.
302 let parent_path = parsed_path.parent().unwrap_or(Path::new("")).to_str().unwrap_or("").to_string();
303 self.verify_uri_component(&parent_path, component_value, "parent_path")?;
304 }
305 uri_components::FILENAME => {
306 // Extract complete filename (with extension) from URI path.
307 // For URI "https://example.com/path/video.mp4", this extracts "video.mp4".
308 // For paths without a filename, this returns an empty string.
309 // Non-UTF8 filenames are converted to empty strings.
310 let filename = parsed_path.file_name().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_string();
311 self.verify_uri_component(&filename, component_value, "filename")?;
312 }
313 uri_components::STEM => {
314 // Extract filename without extension from URI path.
315 // For URI "https://example.com/path/video.mp4", this extracts "video".
316 // For "archive.tar.gz", this extracts "archive.tar" (only last extension removed).
317 // For files without extension, returns the entire filename.
318 // Non-UTF8 stems are converted to empty strings.
319 let stem = parsed_path.file_stem().unwrap_or(OsStr::new("")).to_str().unwrap_or("").to_string();
320 self.verify_uri_component(&stem, component_value, "stem")?;
321 }
322 uri_components::EXTENSION => {
323 // Extract file extension from path
324 let path = parsed_uri.path();
325 let extension = path.split('.').next_back().unwrap_or("").to_string();
326 if !path.contains('.') || path.ends_with('.') {
327 // No extension or ends with dot
328 self.verify_uri_component(&"".to_string(), component_value, "extension")?;
329 } else {
330 self.verify_uri_component(
331 &format!(".{extension}"),
332 component_value,
333 "extension",
334 )?;
335 }
336 }
337 _ => {
338 // Ignore unsupported components
339 }
340 }
341 }
342
343 Ok(())
344 }
345
346 /// Verify a URI component against match conditions
347 fn verify_uri_component(
348 &self,
349 component: &String,
350 match_conditions: &CborValue,
351 component_name: &str,
352 ) -> Result<(), Error> {
353 use crate::constants::match_types;
354 use hmac_sha256::Hash as Sha256Hash;
355 use hmac_sha512::Hash as Sha512Hash;
356 use regex::Regex;
357
358 // Match conditions should be a map
359 let match_map = match match_conditions {
360 CborValue::Map(map) => map,
361 _ => {
362 return Err(Error::InvalidUriClaim(format!(
363 "Match conditions for {component_name} is not a map"
364 )))
365 }
366 };
367
368 for (match_type, match_value) in match_map {
369 match *match_type {
370 match_types::EXACT => {
371 if let CborValue::Text(text) = match_value {
372 if component != text {
373 return Err(Error::InvalidUriClaim(format!(
374 "URI component {component_name} '{component}' does not exactly match required value '{text}'"
375 )));
376 }
377 }
378 }
379 match_types::PREFIX => {
380 if let CborValue::Text(prefix) = match_value {
381 if !component.starts_with(prefix) {
382 return Err(Error::InvalidUriClaim(format!(
383 "URI component {component_name} '{component}' does not start with required prefix '{prefix}'"
384 )));
385 }
386 }
387 }
388 match_types::SUFFIX => {
389 if let CborValue::Text(suffix) = match_value {
390 if !component.ends_with(suffix) {
391 return Err(Error::InvalidUriClaim(format!(
392 "URI component {component_name} '{component}' does not end with required suffix '{suffix}'"
393 )));
394 }
395 }
396 }
397 match_types::CONTAINS => {
398 if let CborValue::Text(contained) = match_value {
399 if !component.contains(contained) {
400 return Err(Error::InvalidUriClaim(format!(
401 "URI component {component_name} '{component}' does not contain required text '{contained}'"
402 )));
403 }
404 }
405 }
406 match_types::REGEX => {
407 if let CborValue::Array(array) = match_value {
408 if let Some(CborValue::Text(pattern)) = array.first() {
409 match Regex::new(pattern) {
410 Ok(regex) => {
411 if !regex.is_match(component) {
412 return Err(Error::InvalidUriClaim(format!(
413 "URI component {component_name} '{component}' does not match required regex pattern '{pattern}'"
414 )));
415 }
416 }
417 Err(_) => {
418 return Err(Error::InvalidUriClaim(format!(
419 "Invalid regex pattern: {pattern}"
420 )))
421 }
422 }
423 }
424 }
425 }
426 match_types::SHA256 => {
427 if let CborValue::Bytes(expected_hash) = match_value {
428 let hash = Sha256Hash::hash(component.as_bytes());
429
430 if !ct_codecs::verify(&hash, expected_hash.as_slice()) {
431 return Err(Error::InvalidUriClaim(format!(
432 "URI component {component_name} '{component}' SHA-256 hash does not match expected value"
433 )));
434 }
435 }
436 }
437 match_types::SHA512_256 => {
438 if let CborValue::Bytes(expected_hash) = match_value {
439 let hash = Sha512Hash::hash(component.as_bytes());
440 let truncated_hash = &hash[0..32]; // Take first 256 bits (32 bytes)
441
442 if !ct_codecs::verify(truncated_hash, &expected_hash[..]) {
443 return Err(Error::InvalidUriClaim(format!(
444 "URI component {component_name} '{component}' SHA-512/256 hash does not match expected value"
445 )));
446 }
447 }
448 }
449 _ => {
450 // Ignore unsupported match types
451 }
452 }
453 }
454
455 Ok(())
456 }
457
458 /// Verify the CATM (HTTP method) claim against the provided method
459 fn verify_catm_claim(&self, options: &VerificationOptions) -> Result<(), Error> {
460 use crate::constants::cat_keys;
461
462 // Get the HTTP method to verify against
463 let method = match &options.http_method {
464 Some(method) => method,
465 None => {
466 return Err(Error::InvalidClaimValue(
467 "No HTTP method provided for CATM verification".to_string(),
468 ))
469 }
470 };
471
472 // Check if token has CATM claim
473 let catm_claim = match self.claims.custom.get(&cat_keys::CATM) {
474 Some(claim) => claim,
475 None => return Ok(()), // No CATM claim, so nothing to verify
476 };
477
478 // CATM claim should be an array of allowed methods
479 let allowed_methods = match catm_claim {
480 CborValue::Array(methods) => methods,
481 _ => {
482 return Err(Error::InvalidMethodClaim(
483 "CATM claim is not an array".to_string(),
484 ))
485 }
486 };
487
488 // Check if the provided method is in the allowed methods list
489 let method_upper = method.to_uppercase();
490 let method_allowed = allowed_methods.iter().any(|m| {
491 if let CborValue::Text(allowed) = m {
492 allowed.to_uppercase() == method_upper
493 } else {
494 false
495 }
496 });
497
498 if !method_allowed {
499 return Err(Error::InvalidMethodClaim(format!(
500 "HTTP method '{}' is not allowed. Permitted methods: {:?}",
501 method,
502 allowed_methods
503 .iter()
504 .filter_map(|m| if let CborValue::Text(t) = m {
505 Some(t.as_str())
506 } else {
507 None
508 })
509 .collect::<Vec<&str>>()
510 )));
511 }
512
513 Ok(())
514 }
515
516 /// Verify the CATREPLAY claim for token replay protection
517 fn verify_catreplay_claim(&self, options: &VerificationOptions) -> Result<(), Error> {
518 use crate::constants::{cat_keys, replay_values};
519
520 // Check if token has CATREPLAY claim
521 let catreplay_claim = match self.claims.custom.get(&cat_keys::CATREPLAY) {
522 Some(claim) => claim,
523 None => return Ok(()), // No CATREPLAY claim, so nothing to verify
524 };
525
526 // Get the replay protection value
527 let replay_value = match catreplay_claim {
528 CborValue::Integer(value) => *value as i32,
529 _ => {
530 return Err(Error::InvalidClaimValue(
531 "CATREPLAY claim is not an integer".to_string(),
532 ))
533 }
534 };
535
536 match replay_value {
537 replay_values::PERMITTED => {
538 // Replay is permitted, no verification needed
539 Ok(())
540 }
541 replay_values::PROHIBITED => {
542 // Replay is prohibited, check if token has been seen before
543 if options.token_seen_before {
544 Err(Error::ReplayViolation(
545 "Token replay is prohibited".to_string(),
546 ))
547 } else {
548 Ok(())
549 }
550 }
551 replay_values::REUSE_DETECTION => {
552 // Reuse is detected but allowed, no error returned
553 // Implementations should log or notify about reuse
554 Ok(())
555 }
556 _ => Err(Error::InvalidClaimValue(format!(
557 "Invalid CATREPLAY value: {replay_value}"
558 ))),
559 }
560 }
561
562 /// Verify the CATTPRINT (TLS Fingerprint) claim against the provided fingerprint type and value
563 fn verify_cattprint_claim(&self, options: &VerificationOptions) -> Result<(), Error> {
564 use crate::constants::cat_keys;
565
566 // Get the Fingerprint type to verify against
567 let fingerprint_type = match &options.fingerprint_type {
568 Some(fingerprint_type) => fingerprint_type,
569 None => {
570 return Err(Error::InvalidClaimValue(
571 "No Fingerprint Type provided for CATTPRINT verification".to_string(),
572 ))
573 }
574 };
575
576 // Get the Fingerprint value to verify against
577 let fingerprint_value = match &options.fingerprint_value {
578 Some(fingerprint_value) => fingerprint_value,
579 None => {
580 return Err(Error::InvalidClaimValue(
581 "No Fingerprint Value provided for CATTPRINT verification".to_string(),
582 ))
583 }
584 };
585
586 // Check if token has CATTPRINT claim
587 let cattprint_claim = match self.claims.custom.get(&cat_keys::CATTPRINT) {
588 Some(claim) => claim,
589 None => return Ok(()), // No CATTPRINT claim, so nothing to verify
590 };
591
592 // CATTPRINT claim should be a map of 2 values
593 let cattprint_map = match cattprint_claim {
594 CborValue::Map(cattprint_map) => cattprint_map,
595 _ => {
596 return Err(Error::InvalidTLSFingerprintClaim(
597 "CATTPRINT claim is not a map".to_string(),
598 ))
599 }
600 };
601
602 // Check if the provided Fingerprint Type matches
603 let claim_fingerprint_type = cattprint_map.get(&tprint_params::FINGERPRINT_TYPE);
604 if let Some(CborValue::Integer(claim_type)) = claim_fingerprint_type {
605 if *claim_type != (*fingerprint_type as i64) {
606 // Convert claim_type (i64) to FingerprintType for human-readable name
607 let claim_type_name = FingerprintType::from_i64(*claim_type)
608 .map(|ft| ft.as_str())
609 .unwrap_or("<unknown>");
610 return Err(Error::InvalidTLSFingerprintClaim(format!(
611 "TLS Fingerprint Type '{}' does not match required value '{}'",
612 claim_type_name, fingerprint_type.as_str()
613 )));
614 }
615 } else {
616 return Err(Error::InvalidTLSFingerprintClaim(
617 "Missing or invalid Fingerprint Type in CATTPRINT claim".to_string(),
618 ));
619 }
620
621 // Check if the provided Fingerprint Value matches
622 let fingerprint_value_upper = fingerprint_value.to_lowercase();
623 let claim_fingerprint_value = cattprint_map.get(&tprint_params::FINGERPRINT_VALUE);
624 if let Some(CborValue::Text(claim_value)) = claim_fingerprint_value {
625 if claim_value.to_lowercase() != fingerprint_value_upper {
626 return Err(Error::InvalidTLSFingerprintClaim(format!(
627 "TLS Fingerprint Value '{}' does not match required value '{}'",
628 claim_value, fingerprint_value
629 )));
630 }
631 } else {
632 return Err(Error::InvalidTLSFingerprintClaim(
633 "Missing or invalid Fingerprint Value in CATTPRINT claim".to_string(),
634 ));
635 }
636
637 Ok(())
638 }
639
640 // Note: signature_input method removed as we now use mac0_input for HMAC algorithms
641
642 /// Get the encoded payload bytes, using original bytes if available
643 fn get_payload_bytes(&self) -> Result<Vec<u8>, Error> {
644 if let Some(ref original) = self.original_payload_bytes {
645 // Use original bytes for verification
646 Ok(original.clone())
647 } else {
648 // Encode claims for newly created tokens
649 let claims_map = self.claims.to_map();
650 encode_map(&claims_map)
651 }
652 }
653
654 /// Get the COSE_Sign1 signature input
655 fn sign1_input(&self) -> Result<Vec<u8>, Error> {
656 // Sig_structure = [
657 // context : "Signature1",
658 // protected : bstr .cbor header_map,
659 // external_aad : bstr,
660 // payload : bstr .cbor claims
661 // ]
662
663 let mut buf = Vec::new();
664 let mut enc = Encoder::new(&mut buf);
665
666 // Start array with 4 items
667 enc.array(4)?;
668
669 // 1. Context
670 enc.str("Signature1")?;
671
672 // 2. Protected header
673 let protected_bytes = encode_map(&self.header.protected)?;
674 enc.bytes(&protected_bytes)?;
675
676 // 3. External AAD (empty in our case)
677 enc.bytes(&[])?;
678
679 // 4. Payload
680 let claims_bytes = self.get_payload_bytes()?;
681 enc.bytes(&claims_bytes)?;
682
683 Ok(buf)
684 }
685
686 /// Get the COSE_Mac0 signature input
687 fn mac0_input(&self) -> Result<Vec<u8>, Error> {
688 // Mac_structure = [
689 // context : "MAC0",
690 // protected : bstr .cbor header_map,
691 // external_aad : bstr,
692 // payload : bstr .cbor claims
693 // ]
694
695 let mut buf = Vec::new();
696 let mut enc = Encoder::new(&mut buf);
697
698 // Start array with 4 items
699 enc.array(4)?;
700
701 // 1. Context
702 enc.str("MAC0")?;
703
704 // 2. Protected header
705 let protected_bytes = encode_map(&self.header.protected)?;
706 enc.bytes(&protected_bytes)?;
707
708 // 3. External AAD (empty in our case)
709 enc.bytes(&[])?;
710
711 // 4. Payload
712 let claims_bytes = self.get_payload_bytes()?;
713 enc.bytes(&claims_bytes)?;
714
715 Ok(buf)
716 }
717
718 // Convenience methods for common token operations
719
720 /// Check if the token has expired
721 ///
722 /// Returns `true` if the token has an expiration claim and the current time is at or after it.
723 /// Returns `false` if the token has no expiration claim or if it hasn't expired yet.
724 ///
725 /// # Example
726 ///
727 /// ```
728 /// use common_access_token::{TokenBuilder, Algorithm, RegisteredClaims, current_timestamp};
729 ///
730 /// let key = b"my-secret-key";
731 /// let now = current_timestamp();
732 ///
733 /// // Token that expires in 1 hour
734 /// let token = TokenBuilder::new()
735 /// .algorithm(Algorithm::HmacSha256)
736 /// .registered_claims(RegisteredClaims::new().with_expiration(now + 3600))
737 /// .sign(key)
738 /// .unwrap();
739 ///
740 /// assert!(!token.is_expired());
741 /// ```
742 pub fn is_expired(&self) -> bool {
743 if let Some(exp) = self.claims.registered.exp {
744 current_timestamp() >= exp
745 } else {
746 false
747 }
748 }
749
750 /// Get the duration until token expiration
751 ///
752 /// Returns `Some(Duration)` if the token has an expiration claim and hasn't expired yet.
753 /// Returns `None` if the token has no expiration claim or has already expired.
754 ///
755 /// # Example
756 ///
757 /// ```
758 /// use common_access_token::{TokenBuilder, Algorithm, RegisteredClaims, current_timestamp};
759 ///
760 /// let key = b"my-secret-key";
761 /// let now = current_timestamp();
762 ///
763 /// let token = TokenBuilder::new()
764 /// .algorithm(Algorithm::HmacSha256)
765 /// .registered_claims(RegisteredClaims::new().with_expiration(now + 3600))
766 /// .sign(key)
767 /// .unwrap();
768 ///
769 /// if let Some(duration) = token.expires_in() {
770 /// println!("Token expires in {} seconds", duration.as_secs());
771 /// }
772 /// ```
773 pub fn expires_in(&self) -> Option<std::time::Duration> {
774 if let Some(exp) = self.claims.registered.exp {
775 let now = current_timestamp();
776 if now < exp {
777 Some(std::time::Duration::from_secs(exp - now))
778 } else {
779 None
780 }
781 } else {
782 None
783 }
784 }
785
786 /// Check if the token is valid based on the not-before (nbf) claim
787 ///
788 /// Returns `true` if the token has no nbf claim or if the current time is at or after it.
789 /// Returns `false` if the token has an nbf claim and the current time is before it.
790 ///
791 /// # Example
792 ///
793 /// ```
794 /// use common_access_token::{TokenBuilder, Algorithm, RegisteredClaims, current_timestamp};
795 ///
796 /// let key = b"my-secret-key";
797 /// let now = current_timestamp();
798 ///
799 /// let token = TokenBuilder::new()
800 /// .algorithm(Algorithm::HmacSha256)
801 /// .registered_claims(RegisteredClaims::new().with_not_before(now))
802 /// .sign(key)
803 /// .unwrap();
804 ///
805 /// assert!(token.is_valid_yet());
806 /// ```
807 pub fn is_valid_yet(&self) -> bool {
808 if let Some(nbf) = self.claims.registered.nbf {
809 current_timestamp() >= nbf
810 } else {
811 true
812 }
813 }
814
815 /// Get the issuer claim value
816 ///
817 /// # Example
818 ///
819 /// ```
820 /// use common_access_token::{TokenBuilder, Algorithm, RegisteredClaims};
821 ///
822 /// let key = b"my-secret-key";
823 /// let token = TokenBuilder::new()
824 /// .algorithm(Algorithm::HmacSha256)
825 /// .registered_claims(RegisteredClaims::new().with_issuer("example-issuer"))
826 /// .sign(key)
827 /// .unwrap();
828 ///
829 /// assert_eq!(token.issuer(), Some("example-issuer"));
830 /// ```
831 pub fn issuer(&self) -> Option<&str> {
832 self.claims.registered.iss.as_deref()
833 }
834
835 /// Get the subject claim value
836 ///
837 /// # Example
838 ///
839 /// ```
840 /// use common_access_token::{TokenBuilder, Algorithm, RegisteredClaims};
841 ///
842 /// let key = b"my-secret-key";
843 /// let token = TokenBuilder::new()
844 /// .algorithm(Algorithm::HmacSha256)
845 /// .registered_claims(RegisteredClaims::new().with_subject("user-123"))
846 /// .sign(key)
847 /// .unwrap();
848 ///
849 /// assert_eq!(token.subject(), Some("user-123"));
850 /// ```
851 pub fn subject(&self) -> Option<&str> {
852 self.claims.registered.sub.as_deref()
853 }
854
855 /// Get the audience claim value
856 ///
857 /// # Example
858 ///
859 /// ```
860 /// use common_access_token::{TokenBuilder, Algorithm, RegisteredClaims};
861 ///
862 /// let key = b"my-secret-key";
863 /// let token = TokenBuilder::new()
864 /// .algorithm(Algorithm::HmacSha256)
865 /// .registered_claims(RegisteredClaims::new().with_audience("api-service"))
866 /// .sign(key)
867 /// .unwrap();
868 ///
869 /// assert_eq!(token.audience(), Some("api-service"));
870 /// ```
871 pub fn audience(&self) -> Option<&str> {
872 self.claims.registered.aud.as_deref()
873 }
874
875 /// Get the expiration timestamp
876 pub fn expiration(&self) -> Option<u64> {
877 self.claims.registered.exp
878 }
879
880 /// Get the not-before timestamp
881 pub fn not_before(&self) -> Option<u64> {
882 self.claims.registered.nbf
883 }
884
885 /// Get the issued-at timestamp
886 pub fn issued_at(&self) -> Option<u64> {
887 self.claims.registered.iat
888 }
889
890 /// Get a custom claim as a string
891 ///
892 /// Returns `Some(&str)` if the claim exists and is a text value, `None` otherwise.
893 ///
894 /// # Example
895 ///
896 /// ```
897 /// use common_access_token::{TokenBuilder, Algorithm};
898 ///
899 /// let key = b"my-secret-key";
900 /// let token = TokenBuilder::new()
901 /// .algorithm(Algorithm::HmacSha256)
902 /// .custom_string(100, "custom-value")
903 /// .sign(key)
904 /// .unwrap();
905 ///
906 /// assert_eq!(token.get_custom_string(100), Some("custom-value"));
907 /// assert_eq!(token.get_custom_string(999), None);
908 /// ```
909 pub fn get_custom_string(&self, key: i32) -> Option<&str> {
910 match self.claims.custom.get(&key) {
911 Some(CborValue::Text(s)) => Some(s.as_str()),
912 _ => None,
913 }
914 }
915
916 /// Get a custom claim as an integer
917 ///
918 /// Returns `Some(i64)` if the claim exists and is an integer value, `None` otherwise.
919 ///
920 /// # Example
921 ///
922 /// ```
923 /// use common_access_token::{TokenBuilder, Algorithm};
924 ///
925 /// let key = b"my-secret-key";
926 /// let token = TokenBuilder::new()
927 /// .algorithm(Algorithm::HmacSha256)
928 /// .custom_int(100, 42)
929 /// .sign(key)
930 /// .unwrap();
931 ///
932 /// assert_eq!(token.get_custom_int(100), Some(42));
933 /// assert_eq!(token.get_custom_int(999), None);
934 /// ```
935 pub fn get_custom_int(&self, key: i32) -> Option<i64> {
936 match self.claims.custom.get(&key) {
937 Some(CborValue::Integer(i)) => Some(*i),
938 _ => None,
939 }
940 }
941
942 /// Get a custom claim as binary data
943 ///
944 /// Returns `Some(&[u8])` if the claim exists and is a bytes value, `None` otherwise.
945 ///
946 /// # Example
947 ///
948 /// ```
949 /// use common_access_token::{TokenBuilder, Algorithm};
950 ///
951 /// let key = b"my-secret-key";
952 /// let data = vec![1, 2, 3, 4];
953 /// let token = TokenBuilder::new()
954 /// .algorithm(Algorithm::HmacSha256)
955 /// .custom_binary(100, data.clone())
956 /// .sign(key)
957 /// .unwrap();
958 ///
959 /// assert_eq!(token.get_custom_binary(100), Some(data.as_slice()));
960 /// assert_eq!(token.get_custom_binary(999), None);
961 /// ```
962 pub fn get_custom_binary(&self, key: i32) -> Option<&[u8]> {
963 match self.claims.custom.get(&key) {
964 Some(CborValue::Bytes(b)) => Some(b.as_slice()),
965 _ => None,
966 }
967 }
968
969 /// Get a reference to a custom claim value
970 ///
971 /// Returns `Some(&CborValue)` if the claim exists, `None` otherwise.
972 ///
973 /// # Example
974 ///
975 /// ```
976 /// use common_access_token::{TokenBuilder, Algorithm, CborValue};
977 ///
978 /// let key = b"my-secret-key";
979 /// let token = TokenBuilder::new()
980 /// .algorithm(Algorithm::HmacSha256)
981 /// .custom_string(100, "value")
982 /// .sign(key)
983 /// .unwrap();
984 ///
985 /// if let Some(CborValue::Text(s)) = token.get_custom_claim(100) {
986 /// assert_eq!(s, "value");
987 /// }
988 /// ```
989 pub fn get_custom_claim(&self, key: i32) -> Option<&CborValue> {
990 self.claims.custom.get(&key)
991 }
992
993 /// Check if a custom claim exists
994 ///
995 /// # Example
996 ///
997 /// ```
998 /// use common_access_token::{TokenBuilder, Algorithm};
999 ///
1000 /// let key = b"my-secret-key";
1001 /// let token = TokenBuilder::new()
1002 /// .algorithm(Algorithm::HmacSha256)
1003 /// .custom_string(100, "value")
1004 /// .sign(key)
1005 /// .unwrap();
1006 ///
1007 /// assert!(token.has_custom_claim(100));
1008 /// assert!(!token.has_custom_claim(999));
1009 /// ```
1010 pub fn has_custom_claim(&self, key: i32) -> bool {
1011 self.claims.custom.contains_key(&key)
1012 }
1013}
1014
1015/// Options for token verification
1016#[derive(Debug, Clone, Default)]
1017pub struct VerificationOptions {
1018 /// Verify expiration claim
1019 pub verify_exp: bool,
1020 /// Require expiration claim
1021 pub require_exp: bool,
1022 /// Verify not before claim
1023 pub verify_nbf: bool,
1024 /// Expected issuer
1025 pub expected_issuer: Option<String>,
1026 /// Require issuer claim
1027 pub require_iss: bool,
1028 /// Expected audience
1029 pub expected_audience: Option<String>,
1030 /// Require audience claim
1031 pub require_aud: bool,
1032 /// Verify CAT-specific URI claim (CATU) against provided URI
1033 pub verify_catu: bool,
1034 /// URI to verify against CATU claim
1035 pub uri: Option<String>,
1036 /// Verify CAT-specific HTTP methods claim (CATM) against provided method
1037 pub verify_catm: bool,
1038 /// HTTP method to verify against CATM claim
1039 pub http_method: Option<String>,
1040 /// Verify CAT-specific replay protection (CATREPLAY)
1041 pub verify_catreplay: bool,
1042 /// Whether the token has been seen before (for replay protection)
1043 pub token_seen_before: bool,
1044 /// Verify CAT-specific TLS Fingerprint claim (CATTPRINT) against provided Fingerprint Type and Value
1045 pub verify_cattprint: bool,
1046 /// Fingerprint Type to verify against CATTPRINT claim
1047 pub fingerprint_type: Option<FingerprintType>,
1048 /// Fingerprint Value to verify against CATTPRINT claim
1049 pub fingerprint_value: Option<String>,
1050}
1051
1052impl VerificationOptions {
1053 /// Create new default verification options
1054 pub fn new() -> Self {
1055 Self {
1056 verify_exp: true,
1057 require_exp: false,
1058 verify_nbf: true,
1059 expected_issuer: None,
1060 require_iss: false,
1061 expected_audience: None,
1062 require_aud: false,
1063 verify_catu: false,
1064 uri: None,
1065 verify_catm: false,
1066 http_method: None,
1067 verify_catreplay: false,
1068 token_seen_before: false,
1069 verify_cattprint: false,
1070 fingerprint_type: None,
1071 fingerprint_value: None,
1072 }
1073 }
1074
1075 /// Set whether to verify expiration
1076 pub fn verify_exp(mut self, verify: bool) -> Self {
1077 self.verify_exp = verify;
1078 self
1079 }
1080
1081 /// Set whether to require expiration
1082 pub fn require_exp(mut self, require: bool) -> Self {
1083 self.require_exp = require;
1084 self
1085 }
1086
1087 /// Set whether to verify not before
1088 pub fn verify_nbf(mut self, verify: bool) -> Self {
1089 self.verify_nbf = verify;
1090 self
1091 }
1092
1093 /// Set expected issuer
1094 pub fn expected_issuer<S: Into<String>>(mut self, issuer: S) -> Self {
1095 self.expected_issuer = Some(issuer.into());
1096 self
1097 }
1098
1099 /// Set whether to require issuer
1100 pub fn require_iss(mut self, require: bool) -> Self {
1101 self.require_iss = require;
1102 self
1103 }
1104
1105 /// Set expected audience
1106 pub fn expected_audience<S: Into<String>>(mut self, audience: S) -> Self {
1107 self.expected_audience = Some(audience.into());
1108 self
1109 }
1110
1111 /// Set whether to require audience
1112 pub fn require_aud(mut self, require: bool) -> Self {
1113 self.require_aud = require;
1114 self
1115 }
1116
1117 /// Set whether to verify CAT-specific URI claim (CATU)
1118 pub fn verify_catu(mut self, verify: bool) -> Self {
1119 self.verify_catu = verify;
1120 self
1121 }
1122
1123 /// Set URI to verify against CATU claim
1124 pub fn uri<S: Into<String>>(mut self, uri: S) -> Self {
1125 self.uri = Some(uri.into());
1126 self
1127 }
1128
1129 /// Set whether to verify CAT-specific HTTP methods claim (CATM)
1130 pub fn verify_catm(mut self, verify: bool) -> Self {
1131 self.verify_catm = verify;
1132 self
1133 }
1134
1135 /// Set HTTP method to verify against CATM claim
1136 pub fn http_method<S: Into<String>>(mut self, method: S) -> Self {
1137 self.http_method = Some(method.into());
1138 self
1139 }
1140
1141 /// Set whether to verify CAT-specific replay protection (CATREPLAY)
1142 pub fn verify_catreplay(mut self, verify: bool) -> Self {
1143 self.verify_catreplay = verify;
1144 self
1145 }
1146
1147 /// Set whether the token has been seen before (for replay protection)
1148 pub fn token_seen_before(mut self, seen: bool) -> Self {
1149 self.token_seen_before = seen;
1150 self
1151 }
1152
1153 /// Set whether to verify CAT-specific TLS Fingerprint claim (CATTPRINT)
1154 pub fn verify_cattprint(mut self, verify: bool) -> Self {
1155 self.verify_cattprint = verify;
1156 self
1157 }
1158
1159 /// Set fingerprint type to verify for the CATTPRINT claim
1160 pub fn fingerprint_type(mut self, fingerprint_type: FingerprintType) -> Self {
1161 self.fingerprint_type = Some(fingerprint_type);
1162 self
1163 }
1164
1165 /// Set fingerprint value to verify for the CATTPRINT claim
1166 pub fn fingerprint_value<S: Into<String>>(mut self, fingerprint_value: S) -> Self {
1167 self.fingerprint_value = Some(fingerprint_value.into());
1168 self
1169 }
1170}
1171
1172/// Builder for creating tokens
1173#[derive(Debug, Clone, Default)]
1174pub struct TokenBuilder {
1175 header: Header,
1176 claims: Claims,
1177}
1178
1179impl TokenBuilder {
1180 /// Create a new token builder
1181 pub fn new() -> Self {
1182 Self::default()
1183 }
1184
1185 /// Set the algorithm
1186 pub fn algorithm(mut self, alg: Algorithm) -> Self {
1187 self.header = self.header.with_algorithm(alg);
1188 self
1189 }
1190
1191 /// Set the key identifier in the protected header
1192 pub fn protected_key_id(mut self, kid: KeyId) -> Self {
1193 self.header = self.header.with_protected_key_id(kid);
1194 self
1195 }
1196
1197 /// Set the key identifier in the unprotected header
1198 pub fn unprotected_key_id(mut self, kid: KeyId) -> Self {
1199 self.header = self.header.with_unprotected_key_id(kid);
1200 self
1201 }
1202
1203 /// Set the registered claims
1204 pub fn registered_claims(mut self, claims: RegisteredClaims) -> Self {
1205 self.claims = self.claims.with_registered_claims(claims);
1206 self
1207 }
1208
1209 /// Add a custom claim with a string value
1210 pub fn custom_string<S: Into<String>>(mut self, key: i32, value: S) -> Self {
1211 self.claims = self.claims.with_custom_string(key, value);
1212 self
1213 }
1214
1215 /// Add a custom claim with a binary value
1216 pub fn custom_binary<B: Into<Vec<u8>>>(mut self, key: i32, value: B) -> Self {
1217 self.claims = self.claims.with_custom_binary(key, value);
1218 self
1219 }
1220
1221 /// Add a custom claim with an integer value
1222 pub fn custom_int(mut self, key: i32, value: i64) -> Self {
1223 self.claims = self.claims.with_custom_int(key, value);
1224 self
1225 }
1226
1227 /// Add a custom claim with a nested map value
1228 pub fn custom_map(mut self, key: i32, value: BTreeMap<i32, CborValue>) -> Self {
1229 self.claims = self.claims.with_custom_map(key, value);
1230 self
1231 }
1232
1233 /// Add a custom claim with a CborValue directly
1234 pub fn custom_cbor(mut self, key: i32, value: CborValue) -> Self {
1235 self.claims.custom.insert(key, value);
1236 self
1237 }
1238
1239 /// Add a custom claim with an array value
1240 pub fn custom_array(mut self, key: i32, value: Vec<CborValue>) -> Self {
1241 self.claims.custom.insert(key, CborValue::Array(value));
1242 self
1243 }
1244
1245 /// Set expiration time relative to now (in seconds)
1246 ///
1247 /// This is a convenience method that sets the expiration claim to the current time plus the specified number of seconds.
1248 ///
1249 /// # Example
1250 ///
1251 /// ```
1252 /// use common_access_token::{TokenBuilder, Algorithm, current_timestamp};
1253 ///
1254 /// let key = b"my-secret-key";
1255 ///
1256 /// // Token expires in 1 hour
1257 /// let token = TokenBuilder::new()
1258 /// .algorithm(Algorithm::HmacSha256)
1259 /// .expires_in_secs(3600)
1260 /// .sign(key)
1261 /// .unwrap();
1262 ///
1263 /// assert!(!token.is_expired());
1264 /// ```
1265 pub fn expires_in_secs(mut self, seconds: u64) -> Self {
1266 let exp = current_timestamp() + seconds;
1267 self.claims.registered.exp = Some(exp);
1268 self
1269 }
1270
1271 /// Set expiration time relative to now using a Duration
1272 ///
1273 /// This is a convenience method that sets the expiration claim to the current time plus the specified duration.
1274 ///
1275 /// # Example
1276 ///
1277 /// ```
1278 /// use common_access_token::{TokenBuilder, Algorithm};
1279 /// use std::time::Duration;
1280 ///
1281 /// let key = b"my-secret-key";
1282 ///
1283 /// // Token expires in 1 hour
1284 /// let token = TokenBuilder::new()
1285 /// .algorithm(Algorithm::HmacSha256)
1286 /// .expires_in(Duration::from_secs(3600))
1287 /// .sign(key)
1288 /// .unwrap();
1289 ///
1290 /// assert!(!token.is_expired());
1291 /// ```
1292 pub fn expires_in(self, duration: std::time::Duration) -> Self {
1293 self.expires_in_secs(duration.as_secs())
1294 }
1295
1296 /// Set token lifetime with issued-at and expiration claims
1297 ///
1298 /// This convenience method sets both the `iat` (issued at) and `exp` (expiration) claims.
1299 /// The issued-at is set to the current time, and expiration is set to current time plus the specified seconds.
1300 ///
1301 /// # Example
1302 ///
1303 /// ```
1304 /// use common_access_token::{TokenBuilder, Algorithm};
1305 ///
1306 /// let key = b"my-secret-key";
1307 ///
1308 /// // Token valid for 1 hour
1309 /// let token = TokenBuilder::new()
1310 /// .algorithm(Algorithm::HmacSha256)
1311 /// .valid_for_secs(3600)
1312 /// .sign(key)
1313 /// .unwrap();
1314 ///
1315 /// assert!(token.issued_at().is_some());
1316 /// assert!(token.expiration().is_some());
1317 /// ```
1318 pub fn valid_for_secs(mut self, seconds: u64) -> Self {
1319 let now = current_timestamp();
1320 self.claims.registered.iat = Some(now);
1321 self.claims.registered.exp = Some(now + seconds);
1322 self
1323 }
1324
1325 /// Set token lifetime with issued-at and expiration claims using a Duration
1326 ///
1327 /// This convenience method sets both the `iat` (issued at) and `exp` (expiration) claims.
1328 /// The issued-at is set to the current time, and expiration is set to current time plus the specified duration.
1329 ///
1330 /// # Example
1331 ///
1332 /// ```
1333 /// use common_access_token::{TokenBuilder, Algorithm};
1334 /// use std::time::Duration;
1335 ///
1336 /// let key = b"my-secret-key";
1337 ///
1338 /// // Token valid for 1 hour
1339 /// let token = TokenBuilder::new()
1340 /// .algorithm(Algorithm::HmacSha256)
1341 /// .valid_for(Duration::from_secs(3600))
1342 /// .sign(key)
1343 /// .unwrap();
1344 ///
1345 /// assert!(token.issued_at().is_some());
1346 /// assert!(token.expiration().is_some());
1347 /// ```
1348 pub fn valid_for(self, duration: std::time::Duration) -> Self {
1349 self.valid_for_secs(duration.as_secs())
1350 }
1351
1352 /// Build and sign the token
1353 pub fn sign(self, key: &[u8]) -> Result<Token, Error> {
1354 // Ensure we have an algorithm
1355 let alg = self.header.algorithm().ok_or_else(|| {
1356 Error::InvalidFormat("Missing algorithm in protected header".to_string())
1357 })?;
1358
1359 // Create token without signature
1360 let token = Token {
1361 header: self.header,
1362 claims: self.claims,
1363 signature: Vec::new(),
1364 original_payload_bytes: None,
1365 };
1366
1367 // Compute signature input based on algorithm
1368 // HMAC algorithms use COSE_Mac0 structure, others use COSE_Sign1
1369 let (_signature_input, signature) = match alg {
1370 Algorithm::HmacSha256 => {
1371 let mac_input = token.mac0_input()?;
1372 let mac = compute_hmac_sha256(key, &mac_input);
1373 (mac_input, mac)
1374 }
1375 };
1376
1377 // Create final token with signature
1378 Ok(Token {
1379 header: token.header,
1380 claims: token.claims,
1381 signature,
1382 original_payload_bytes: None,
1383 })
1384 }
1385}
1386
1387// Helper functions for CBOR encoding/decoding
1388
1389fn encode_map(map: &HeaderMap) -> Result<Vec<u8>, Error> {
1390 let mut buf = Vec::new();
1391 let mut enc = Encoder::new(&mut buf);
1392
1393 encode_map_direct(map, &mut enc)?;
1394
1395 Ok(buf)
1396}
1397
1398/// Encode a CBOR value directly to the encoder
1399fn encode_cbor_value(value: &CborValue, enc: &mut Encoder<&mut Vec<u8>>) -> Result<(), Error> {
1400 match value {
1401 CborValue::Integer(i) => {
1402 enc.i64(*i)?;
1403 }
1404 CborValue::Bytes(b) => {
1405 enc.bytes(b)?;
1406 }
1407 CborValue::Text(s) => {
1408 enc.str(s)?;
1409 }
1410 CborValue::Map(nested_map) => {
1411 // Create a nested encoder for the map
1412 encode_map_direct(nested_map, enc)?;
1413 }
1414 CborValue::Array(arr) => {
1415 // Create a nested encoder for the array
1416 enc.array(arr.len() as u64)?;
1417 for item in arr {
1418 encode_cbor_value(item, enc)?;
1419 }
1420 }
1421 CborValue::Null => {
1422 enc.null()?;
1423 }
1424 }
1425 Ok(())
1426}
1427
1428fn encode_map_direct(map: &HeaderMap, enc: &mut Encoder<&mut Vec<u8>>) -> Result<(), Error> {
1429 enc.map(map.len() as u64)?;
1430
1431 for (key, value) in map {
1432 enc.i32(*key)?;
1433 encode_cbor_value(value, enc)?;
1434 }
1435
1436 Ok(())
1437}
1438
1439fn decode_map(bytes: &[u8]) -> Result<HeaderMap, Error> {
1440 let mut dec = Decoder::new(bytes);
1441 decode_map_direct(&mut dec)
1442}
1443
1444/// Decode a CBOR array
1445fn decode_array(dec: &mut Decoder<'_>) -> Result<Vec<CborValue>, Error> {
1446 let array_len = dec.array()?.unwrap_or(0);
1447 let mut array = Vec::with_capacity(array_len as usize);
1448
1449 for _ in 0..array_len {
1450 // Try to decode based on the datatype
1451 let datatype = dec.datatype()?;
1452
1453 // Handle each type separately
1454 let value = if datatype == minicbor::data::Type::Int {
1455 // Integer value
1456 let i = dec.i64()?;
1457 CborValue::Integer(i)
1458 } else if datatype == minicbor::data::Type::U8
1459 || datatype == minicbor::data::Type::U16
1460 || datatype == minicbor::data::Type::U32
1461 || datatype == minicbor::data::Type::U64
1462 {
1463 // Unsigned integer value
1464 let i = dec.u64()? as i64;
1465 CborValue::Integer(i)
1466 } else if datatype == minicbor::data::Type::Bytes {
1467 // Byte string
1468 let b = dec.bytes()?;
1469 CborValue::Bytes(b.to_vec())
1470 } else if datatype == minicbor::data::Type::String {
1471 // Text string
1472 let s = dec.str()?;
1473 CborValue::Text(s.to_string())
1474 } else if datatype == minicbor::data::Type::Map {
1475 // Nested map
1476 let nested_map = decode_map_direct(dec)?;
1477 CborValue::Map(nested_map)
1478 } else if datatype == minicbor::data::Type::Array {
1479 // Nested array
1480 let nested_array = decode_array(dec)?;
1481 CborValue::Array(nested_array)
1482 } else if datatype == minicbor::data::Type::Null {
1483 // Null value
1484 dec.null()?;
1485 CborValue::Null
1486 } else {
1487 // Unsupported type
1488 return Err(Error::InvalidFormat(format!(
1489 "Unsupported CBOR type in array: {datatype:?}"
1490 )));
1491 };
1492
1493 array.push(value);
1494 }
1495
1496 Ok(array)
1497}
1498
1499fn decode_map_direct(dec: &mut Decoder<'_>) -> Result<HeaderMap, Error> {
1500 let map_len = dec.map()?.unwrap_or(0);
1501 let mut map = HeaderMap::new();
1502
1503 for _ in 0..map_len {
1504 let key = dec.i32()?;
1505
1506 // Try to decode based on the datatype
1507 let datatype = dec.datatype()?;
1508
1509 // Handle each type separately
1510 let value = if datatype == minicbor::data::Type::Int {
1511 // Integer value
1512 let i = dec.i64()?;
1513 CborValue::Integer(i)
1514 } else if datatype == minicbor::data::Type::U8
1515 || datatype == minicbor::data::Type::U16
1516 || datatype == minicbor::data::Type::U32
1517 || datatype == minicbor::data::Type::U64
1518 {
1519 // Unsigned integer value
1520 let i = dec.u64()? as i64;
1521 CborValue::Integer(i)
1522 } else if datatype == minicbor::data::Type::Bytes {
1523 // Byte string
1524 let b = dec.bytes()?;
1525 CborValue::Bytes(b.to_vec())
1526 } else if datatype == minicbor::data::Type::String {
1527 // Text string
1528 let s = dec.str()?;
1529 CborValue::Text(s.to_string())
1530 } else if datatype == minicbor::data::Type::Map {
1531 // Nested map
1532 let nested_map = decode_map_direct(dec)?;
1533 CborValue::Map(nested_map)
1534 } else if datatype == minicbor::data::Type::Array {
1535 // Array
1536 let array = decode_array(dec)?;
1537 CborValue::Array(array)
1538 } else if datatype == minicbor::data::Type::Null {
1539 // Null value
1540 dec.null()?;
1541 CborValue::Null
1542 } else {
1543 // Unsupported type
1544 return Err(Error::InvalidFormat(format!(
1545 "Unsupported CBOR type: {datatype:?}"
1546 )));
1547 };
1548
1549 map.insert(key, value);
1550 }
1551
1552 Ok(map)
1553}