ctaphid_types/
response.rs

1// Copyright (C) 2021-2022 Robin Krahl <robin.krahl@ireas.org>
2// SPDX-License-Identifier: Apache-2.0 or MIT
3
4use core::convert::{TryFrom, TryInto};
5
6use crate::{
7    channel::Channel,
8    error::{ParseError, SerializationError},
9    util::{Parser, Serializer},
10};
11
12/// The response of the CTAPHID INIT command.
13///
14/// See [§ 11.2.9.1.3 of the CTAP specification][spec].
15///
16/// [spec]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#usb-hid-init
17#[derive(Clone, Copy, Debug, Eq, Ord, PartialEq, PartialOrd)]
18pub struct InitResponse<T: AsRef<[u8]>> {
19    /// The nonce as sent in the init request.
20    pub nonce: [u8; 8],
21    /// The channel allocated for this session.
22    pub channel: Channel,
23    /// The protocol version implemented by the device.
24    pub protocol_version: u8,
25    /// The version of the device.
26    pub device_version: DeviceVersion,
27    /// The capabilities of the device.
28    pub capabilities: Capabilities,
29    /// Additional flags that are not supported by this library.
30    pub rest: T,
31}
32
33impl<T: AsRef<[u8]>> InitResponse<T> {
34    /// Serializes this response to the given buffer.
35    pub fn serialize(&self, buffer: &mut [u8]) -> Result<usize, SerializationError> {
36        let mut serializer = Serializer::new(buffer);
37        serializer.push_slice(&self.nonce)?;
38        serializer.push_slice(&self.channel.to_bytes())?;
39        serializer.push_slice(&[self.protocol_version])?;
40        serializer.push_slice(&self.device_version.to_bytes())?;
41        serializer.push_slice(&[self.capabilities.bits()])?;
42        serializer.push_slice(self.rest.as_ref())?;
43        Ok(serializer.bytes_written())
44    }
45}
46
47impl<'a, T: AsRef<[u8]> + TryFrom<&'a [u8]>> TryFrom<&'a [u8]> for InitResponse<T> {
48    type Error = ParseError;
49
50    fn try_from(data: &'a [u8]) -> Result<Self, Self::Error> {
51        let mut parser = Parser::new(data);
52        let nonce = parser.take_array()?;
53        let channel = parser.take_into()?;
54        let protocol_version = parser.take()?;
55        let device_version = parser.take_into()?;
56        let capabilities = parser.take()?;
57        let rest = parser.into_rest();
58
59        Ok(Self {
60            nonce,
61            channel,
62            protocol_version,
63            device_version,
64            capabilities: Capabilities::from_bits_truncate(capabilities),
65            rest: rest
66                .try_into()
67                .map_err(|_| ParseError::BufferCreationFailed)?,
68        })
69    }
70}
71
72/// The version of a CTAPHID device.
73///
74/// See [§ 11.2.9.1.3 of the CTAP specification][spec].
75///
76/// [spec]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#usb-hid-init
77#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
78pub struct DeviceVersion {
79    /// The major device version.
80    pub major: u8,
81    /// The minor device version.
82    pub minor: u8,
83    /// The build device version.
84    pub build: u8,
85}
86
87impl DeviceVersion {
88    /// Encodes the device version as a byte array.
89    pub fn to_bytes(self) -> [u8; 3] {
90        self.into()
91    }
92}
93
94impl From<DeviceVersion> for [u8; 3] {
95    fn from(version: DeviceVersion) -> Self {
96        [version.major, version.minor, version.build]
97    }
98}
99
100impl From<&[u8; 3]> for DeviceVersion {
101    fn from(data: &[u8; 3]) -> Self {
102        Self {
103            major: data[0],
104            minor: data[1],
105            build: data[2],
106        }
107    }
108}
109
110bitflags::bitflags! {
111    /// The capabilities of a CTAPHID device.
112    ///
113    /// See [§ 11.2.9.1.3 of the CTAP specification][spec].
114    ///
115    /// [spec]: https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#usb-hid-init
116    #[derive(Default)]
117    pub struct Capabilities: u8 {
118        /// Indicates that the device supports the wink command.
119        const WINK = 0x01;
120        /// Indicates that the device supports the CBOR command.
121        const CBOR = 0x04;
122        /// Indicates that the device does not support the MSG command.
123        const NMSG = 0x08;
124    }
125}
126
127impl Capabilities {
128    /// Returns true if the device supports the wink command.
129    pub fn has_wink(&self) -> bool {
130        self.contains(Self::WINK)
131    }
132
133    /// Returns true if the device supports the CBOR command.
134    pub fn has_cbor(&self) -> bool {
135        self.contains(Self::CBOR)
136    }
137
138    /// Returns true if the device supports the MSG command.
139    pub fn has_msg(&self) -> bool {
140        !self.contains(Self::NMSG)
141    }
142}
143
144#[cfg(test)]
145mod tests {
146    use std::convert::TryFrom as _;
147
148    use quickcheck::{Arbitrary, TestResult};
149
150    use super::{Capabilities, DeviceVersion, InitResponse};
151
152    impl Arbitrary for Capabilities {
153        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
154            Self::from_bits_truncate(Arbitrary::arbitrary(g))
155        }
156    }
157
158    impl Arbitrary for DeviceVersion {
159        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
160            Self {
161                major: Arbitrary::arbitrary(g),
162                minor: Arbitrary::arbitrary(g),
163                build: Arbitrary::arbitrary(g),
164            }
165        }
166    }
167
168    impl Arbitrary for InitResponse<Vec<u8>> {
169        fn arbitrary(g: &mut quickcheck::Gen) -> Self {
170            Self {
171                nonce: [
172                    Arbitrary::arbitrary(g),
173                    Arbitrary::arbitrary(g),
174                    Arbitrary::arbitrary(g),
175                    Arbitrary::arbitrary(g),
176                    Arbitrary::arbitrary(g),
177                    Arbitrary::arbitrary(g),
178                    Arbitrary::arbitrary(g),
179                    Arbitrary::arbitrary(g),
180                ],
181                channel: Arbitrary::arbitrary(g),
182                protocol_version: Arbitrary::arbitrary(g),
183                device_version: Arbitrary::arbitrary(g),
184                capabilities: Arbitrary::arbitrary(g),
185                rest: Arbitrary::arbitrary(g),
186            }
187        }
188
189        fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
190            let nonce = self.nonce;
191            let channel = self.channel;
192            let protocol_version = self.protocol_version;
193            let device_version = self.device_version;
194            let capabilities = self.capabilities;
195            Box::new(self.rest.shrink().map(move |rest| Self {
196                nonce,
197                channel,
198                protocol_version,
199                device_version,
200                capabilities,
201                rest,
202            }))
203        }
204    }
205
206    quickcheck::quickcheck! {
207        fn parse_init(data: Vec<u8>) -> bool {
208            let _ = InitResponse::<Vec<u8>>::try_from(data.as_slice());
209            true
210        }
211    }
212
213    quickcheck::quickcheck! {
214        fn serialize_init(init: InitResponse<Vec<u8>>) -> bool {
215            let mut buffer = vec![0; init.rest.len() + 17];
216            let _ = init.serialize(&mut buffer);
217            true
218        }
219    }
220
221    quickcheck::quickcheck! {
222        fn serialize_parse_init(init: InitResponse<Vec<u8>>) -> TestResult {
223            let mut buffer = vec![0; init.rest.len() + 17];
224            let n = init.serialize(&mut buffer).unwrap();
225            let parsed = InitResponse::try_from(&buffer[..n]).unwrap();
226            TestResult::from_bool(init == parsed)
227        }
228    }
229}