foundation_urtypes/
value.rs

1// SPDX-FileCopyrightText: © 2023 Foundation Devices, Inc. <hello@foundationdevices.com>
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4//! An Uniform Resource value.
5//!
6//! The [`Value`] type aggregates every known UR type (by this crate) into a
7//! single enumeration variant containing those.
8//!
9//! This can be used to parse a Uniform Resource by checking on the UR type
10//! and then calling the corresponding decoder.
11//!
12//! # Example
13//!
14//! Parsing a UR:
15//!
16//! ```rust
17//! // As a UR: ur:bytes/gdaebycpeofygoiyktlonlpkrksfutwyzmwmfyeozs
18//! use foundation_urtypes::value::Value;
19//! const PAYLOAD: &[u8] = &[
20//!     0x50, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
21//!     0xEE, 0xFF,
22//! ];
23//! const UR_TYPE: &str = "bytes";
24//!
25//! let value = Value::from_ur(UR_TYPE, PAYLOAD).unwrap();
26//! println!("{:?}", value);
27//! ```
28
29use core::fmt::{Display, Formatter};
30
31use minicbor::{bytes::ByteSlice, encode::Write, Encode, Encoder};
32
33use crate::registry::{HDKeyRef, PassportRequest, PassportResponse};
34
35#[derive(Debug, PartialEq)]
36pub enum Value<'a> {
37    /// bytes.
38    Bytes(&'a [u8]),
39    /// crypto-hdkey.
40    HDKey(HDKeyRef<'a>),
41    /// crypto-psbt.
42    Psbt(&'a [u8]),
43    /// crypto-request for Passport.
44    PassportRequest(PassportRequest),
45    /// crypto-response for Passport.
46    PassportResponse(PassportResponse<'a>),
47}
48
49impl<'a> Value<'a> {
50    /// Construct a new [`Value`] from the type and the CBOR payload.
51    pub fn from_ur(ur_type: &str, payload: &'a [u8]) -> Result<Self, Error> {
52        let value = match ur_type {
53            "bytes" => Self::Bytes(minicbor::decode::<&ByteSlice>(payload)?),
54            "hdkey" | "crypto-hdkey" => Self::HDKey(minicbor::decode(payload)?),
55            "psbt" | "crypto-psbt" => Self::Psbt(minicbor::decode::<&ByteSlice>(payload)?),
56            // TODO: Remove crypto-request and crypto-response, these have
57            // been removed from the UR registry standard (BCR-2020-006).
58            "x-passport-request" | "crypto-request" => {
59                Self::PassportRequest(minicbor::decode(payload)?)
60            }
61            "x-passport-response" | "crypto-response" => {
62                Self::PassportResponse(minicbor::decode(payload)?)
63            }
64            _ => return Err(Error::UnsupportedResource),
65        };
66
67        Ok(value)
68    }
69
70    /// Return the type of this value as a string.
71    ///
72    /// # Notes
73    ///
74    /// This will return the _deprecated_ types as some implementers of UR
75    /// still don't support the newer ones.
76    ///
77    /// When changing this to use the newer types also change
78    /// [`Value::from_ur`].
79    pub fn ur_type(&self) -> &'static str {
80        match self {
81            Value::Bytes(_) => "bytes",
82            Value::HDKey(_) => "hdkey",
83            Value::Psbt(_) => "crypto-psbt",
84            Value::PassportRequest(_) => "crypto-request",
85            Value::PassportResponse(_) => "crypto-response",
86        }
87    }
88}
89
90impl<'a, C> Encode<C> for Value<'a> {
91    fn encode<W: Write>(
92        &self,
93        e: &mut Encoder<W>,
94        ctx: &mut C,
95    ) -> Result<(), minicbor::encode::Error<W::Error>> {
96        match self {
97            Value::Bytes(v) => minicbor::bytes::encode(v, e, ctx),
98            Value::HDKey(v) => v.encode(e, ctx),
99            Value::Psbt(v) => minicbor::bytes::encode(v, e, ctx),
100            Value::PassportRequest(v) => v.encode(e, ctx),
101            Value::PassportResponse(v) => v.encode(e, ctx),
102        }
103    }
104}
105
106/// Errors that can occur when parsing a value.
107#[derive(Debug)]
108pub enum Error {
109    /// Unsupported Uniform Resource type.
110    UnsupportedResource,
111    /// Failed to decode CBOR payload.
112    InvalidCbor(minicbor::decode::Error),
113}
114
115impl Display for Error {
116    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
117        match self {
118            Self::UnsupportedResource => write!(f, "unsupported Uniform Resource type"),
119            Self::InvalidCbor(_) => write!(f, "failed to decode CBOR payload"),
120        }
121    }
122}
123
124#[cfg(feature = "std")]
125impl std::error::Error for Error {
126    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
127        match self {
128            Self::InvalidCbor(e) => Some(e),
129            _ => None,
130        }
131    }
132}
133
134impl From<minicbor::decode::Error> for Error {
135    fn from(error: minicbor::decode::Error) -> Self {
136        Self::InvalidCbor(error)
137    }
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn test_byte_string_bytes() {
146        const BYTES_PAYLOAD: &[u8] = &[
147            0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD,
148            0xEE, 0xFF,
149        ];
150        const CBOR_PAYLOAD: &[u8] = &[
151            0x50, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC,
152            0xDD, 0xEE, 0xFF,
153        ];
154
155        let value = Value::from_ur("bytes", CBOR_PAYLOAD).unwrap();
156        assert_eq!(value, Value::Bytes(BYTES_PAYLOAD));
157
158        let cbor = minicbor::to_vec(&value).unwrap();
159        assert_eq!(cbor, CBOR_PAYLOAD);
160    }
161}