Skip to main content

bitbox_api/
communication.rs

1// SPDX-License-Identifier: Apache-2.0
2
3use super::u2fframing::{self, U2FFraming};
4use crate::runtime::Runtime;
5use crate::util::Threading;
6use async_trait::async_trait;
7use thiserror::Error;
8
9pub const FIRMWARE_CMD: u8 = 0x80 + 0x40 + 0x01;
10
11#[derive(Error, Debug)]
12pub enum Error {
13    #[error("unknown error")]
14    Unknown,
15    #[error("write error")]
16    Write,
17    #[error("read error")]
18    Read,
19    #[error("u2f framing decoding error")]
20    U2fDecode,
21    #[error("error querying device info")]
22    Info,
23    #[error("firmware version {0} required")]
24    Version(&'static str),
25}
26
27#[cfg_attr(feature = "multithreaded", async_trait)]
28#[cfg_attr(not(feature="multithreaded"), async_trait(?Send))]
29pub trait ReadWrite: Threading {
30    fn write(&self, msg: &[u8]) -> Result<usize, Error>;
31    async fn read(&self) -> Result<Vec<u8>, Error>;
32
33    async fn query(&self, msg: &[u8]) -> Result<Vec<u8>, Error> {
34        self.write(msg)?;
35        self.read().await
36    }
37}
38
39pub struct U2fHidCommunication {
40    read_write: Box<dyn ReadWrite>,
41    u2fhid: u2fframing::U2fHid,
42}
43
44impl crate::util::Threading for U2fHidCommunication {}
45
46impl U2fHidCommunication {
47    pub fn from(read_write: Box<dyn ReadWrite>, cmd: u8) -> Self {
48        U2fHidCommunication {
49            read_write,
50            u2fhid: u2fframing::U2fHid::new(cmd),
51        }
52    }
53}
54
55#[cfg_attr(feature = "multithreaded", async_trait)]
56#[cfg_attr(not(feature="multithreaded"),async_trait(?Send))]
57impl ReadWrite for U2fHidCommunication {
58    fn write(&self, msg: &[u8]) -> Result<usize, Error> {
59        let mut buf = [0u8; u2fframing::MAX_LEN];
60        let size = self.u2fhid.encode(msg, &mut buf).unwrap();
61        for chunk in buf[..size].chunks(64) {
62            self.read_write.write(chunk)?;
63        }
64        Ok(size)
65    }
66
67    async fn read(&self) -> Result<Vec<u8>, Error> {
68        let mut readbuf = self.read_write.read().await?;
69        loop {
70            match self.u2fhid.decode(&readbuf).or(Err(Error::U2fDecode))? {
71                Some(d) => {
72                    return Ok(d);
73                }
74                None => {
75                    let more = self.read_write.read().await?;
76                    readbuf.extend_from_slice(&more);
77                }
78            }
79        }
80    }
81}
82
83#[cfg(feature = "wasm")]
84pub struct U2fWsCommunication {
85    read_write: Box<dyn ReadWrite>,
86    u2fhid: u2fframing::U2fWs,
87}
88
89#[cfg(feature = "wasm")]
90impl Threading for U2fWsCommunication {}
91
92#[cfg(feature = "wasm")]
93impl U2fWsCommunication {
94    pub fn from(read_write: Box<dyn ReadWrite>, cmd: u8) -> Self {
95        U2fWsCommunication {
96            read_write,
97            u2fhid: u2fframing::U2fWs::new(cmd),
98        }
99    }
100}
101
102#[cfg(feature = "wasm")]
103#[async_trait(?Send)]
104impl ReadWrite for U2fWsCommunication {
105    fn write(&self, msg: &[u8]) -> Result<usize, Error> {
106        let mut buf = [0u8; u2fframing::MAX_LEN];
107        let size = self.u2fhid.encode(msg, &mut buf).unwrap();
108        self.read_write.write(&buf[..size])
109    }
110
111    async fn read(&self) -> Result<Vec<u8>, Error> {
112        let mut readbuf = self.read_write.read().await?;
113        loop {
114            match self.u2fhid.decode(&readbuf).or(Err(Error::U2fDecode))? {
115                Some(d) => {
116                    return Ok(d);
117                }
118                None => {
119                    let more = self.read_write.read().await?;
120                    readbuf.extend_from_slice(&more);
121                }
122            }
123        }
124    }
125}
126
127// sinve v7.0.0, requets and responses are framed with hww* codes.
128// hwwReq* are HWW-level framing opcodes of requests.
129// New request.
130const HWW_REQ_NEW: u8 = 0x00;
131// Poll an outstanding request for completion.
132const HWW_REQ_RETRY: u8 = 0x01;
133// Cancel any outstanding request.
134// const HWW_REQ_CANCEL: u8 = 0x02;
135// INFO api call (used to be OP_INFO api call), graduated to the toplevel framing so it works
136// the same way for all firmware versions.
137const HWW_INFO: u8 = b'i';
138
139// hwwRsp* are HWW-level framing pocodes of responses.
140
141// Request finished, payload is valid.
142const HWW_RSP_ACK: u8 = 0x00;
143// Request is outstanding, retry later with hwwOpRetry.
144const HWW_RSP_NOTREADY: u8 = 0x01;
145// Device is busy, request was dropped. Client should retry the exact same msg.
146const HWW_RSP_BUSY: u8 = 0x02;
147// Bad request.
148const HWW_RSP_NACK: u8 = 0x03;
149
150#[derive(PartialEq, Debug, Copy, Clone)]
151pub enum Product {
152    Unknown,
153    BitBox02Multi,
154    BitBox02BtcOnly,
155    BitBox02NovaMulti,
156    BitBox02NovaBtcOnly,
157}
158
159#[derive(Debug)]
160pub struct Info {
161    pub version: semver::Version,
162    pub product: Product,
163    #[allow(dead_code)]
164    pub unlocked: bool,
165    // Is None before firmware version 9.20.0.
166    #[allow(dead_code)]
167    pub initialized: Option<bool>,
168}
169
170pub struct HwwCommunication<R: Runtime> {
171    communication: Box<dyn ReadWrite>,
172    pub info: Info,
173    marker: std::marker::PhantomData<R>,
174}
175
176async fn get_info(communication: &dyn ReadWrite) -> Result<Info, Error> {
177    let response = communication.query(&[HWW_INFO]).await?;
178    let (version_str_len, response) = (
179        *response.first().ok_or(Error::Info)? as usize,
180        response.get(1..).ok_or(Error::Info)?,
181    );
182    let (version_bytes, response) = (
183        response.get(..version_str_len).ok_or(Error::Info)?,
184        response.get(version_str_len..).ok_or(Error::Info)?,
185    );
186    let version_str = std::str::from_utf8(version_bytes)
187        .or(Err(Error::Info))?
188        .strip_prefix('v')
189        .ok_or(Error::Info)?;
190
191    let version = semver::Version::parse(version_str).or(Err(Error::Info))?;
192    const PLATFORM_BITBOX02: u8 = 0x00;
193    const PLATFORM_BITBOX02_NOVA: u8 = 0x02;
194    const BITBOX02_EDITION_MULTI: u8 = 0x00;
195    const BITBOX02_EDITION_BTCONLY: u8 = 0x01;
196    let platform_byte = *response.first().ok_or(Error::Info)?;
197    let edition_byte = *response.get(1).ok_or(Error::Info)?;
198    let unlocked_byte = *response.get(2).ok_or(Error::Info)?;
199    let initialized_byte = response.get(3);
200    Ok(Info {
201        version,
202        product: match (platform_byte, edition_byte) {
203            (PLATFORM_BITBOX02, BITBOX02_EDITION_MULTI) => Product::BitBox02Multi,
204            (PLATFORM_BITBOX02, BITBOX02_EDITION_BTCONLY) => Product::BitBox02BtcOnly,
205            (PLATFORM_BITBOX02_NOVA, BITBOX02_EDITION_MULTI) => Product::BitBox02NovaMulti,
206            (PLATFORM_BITBOX02_NOVA, BITBOX02_EDITION_BTCONLY) => Product::BitBox02NovaBtcOnly,
207            _ => Product::Unknown,
208        },
209        unlocked: match unlocked_byte {
210            0x00 => false,
211            0x01 => true,
212            _ => return Err(Error::Info),
213        },
214        initialized: match initialized_byte {
215            None => None,
216            Some(0x00) => Some(false),
217            Some(0x01) => Some(true),
218            _ => return Err(Error::Info),
219        },
220    })
221}
222
223impl<R: Runtime> HwwCommunication<R> {
224    pub async fn from(communication: Box<dyn ReadWrite>) -> Result<Self, Error> {
225        let info = get_info(communication.as_ref()).await?;
226        // communication message framing since 7.0.0
227        if !semver::VersionReq::parse(">=7.0.0")
228            .or(Err(Error::Unknown))?
229            .matches(&info.version)
230        {
231            return Err(Error::Version(">=7.0.0"));
232        }
233
234        Ok(HwwCommunication {
235            communication,
236            info,
237            marker: std::marker::PhantomData,
238        })
239    }
240
241    #[cfg(all(test, not(feature = "multithreaded")))]
242    pub(crate) fn from_test_parts(communication: Box<dyn ReadWrite>, info: Info) -> Self {
243        HwwCommunication {
244            communication,
245            info,
246            marker: std::marker::PhantomData,
247        }
248    }
249
250    pub async fn query(&self, msg: &[u8]) -> Result<Vec<u8>, Error> {
251        let mut framed_msg = Vec::from([HWW_REQ_NEW]);
252        framed_msg.extend_from_slice(msg);
253
254        let mut response = loop {
255            let response = self.communication.query(&framed_msg).await?;
256            if let Some(&HWW_RSP_BUSY) = response.first() {
257                R::sleep(std::time::Duration::from_millis(1000)).await;
258                continue;
259            }
260            break response;
261        };
262        loop {
263            match response.first() {
264                Some(&HWW_RSP_ACK) => {
265                    return Ok(response.split_off(1));
266                }
267                Some(&HWW_RSP_BUSY) => {
268                    return Err(Error::Info);
269                }
270                Some(&HWW_RSP_NACK) => {
271                    return Err(Error::Info);
272                }
273                Some(&HWW_RSP_NOTREADY) => {
274                    R::sleep(std::time::Duration::from_millis(200)).await;
275                    response = self.communication.query(&[HWW_REQ_RETRY]).await?;
276                }
277                _ => return Err(Error::Info),
278            }
279        }
280    }
281}