Skip to main content

bitwarden_random/
ffi.rs

1//! Cross-platform (WASM / UniFFI) bindings for random-number generation.
2
3use bitwarden_error::bitwarden_error;
4use rand::{Rng, RngExt};
5use thiserror::Error;
6
7use crate::rng;
8
9/// Error returned by [`SdkRandomNumberClient::gen_bytes`].
10#[bitwarden_error(flat)]
11#[derive(Debug, Error)]
12pub enum GenBytesError {
13    /// More than 1 KiB of randomness was requested.
14    #[error("gen_bytes() is limited to 1KiB; requested {requested} bytes")]
15    TooManyBytes {
16        /// The number of bytes that was requested.
17        requested: u32,
18    },
19}
20
21/// Error returned by [`SdkRandomNumberClient::gen_range`].
22#[bitwarden_error(flat)]
23#[derive(Debug, Error)]
24pub enum GenRangeError {
25    /// `min` was greater than `max`, so the range is empty.
26    #[error("Invalid range: min ({min}) must not exceed max ({max})")]
27    InvalidRange {
28        /// The requested lower bound.
29        min: u32,
30        /// The requested upper bound.
31        max: u32,
32    },
33}
34
35/// Client exposing random-number generation to cross-platform bindings.
36#[derive(Default)]
37#[cfg_attr(feature = "uniffi", derive(uniffi::Object))]
38#[bitwarden_ffi::wasm_object]
39pub struct SdkRandomNumberClient;
40
41#[bitwarden_ffi::wasm_export]
42#[cfg_attr(feature = "uniffi", uniffi::export)]
43impl SdkRandomNumberClient {
44    /// Construct a new client.
45    #[cfg_attr(feature = "wasm", wasm_bindgen(constructor))]
46    #[cfg_attr(feature = "uniffi", uniffi::constructor)]
47    pub fn new() -> Self {
48        // UniFFI does not support associated (static) functions, so the client is exposed as an
49        // object with a constructor and `&self` methods. The WASM binding mirrors that
50        // shape (constructor + instance methods) so a single impl block serves both.
51        SdkRandomNumberClient
52    }
53
54    /// Generate `len` cryptographically-secure random bytes.
55    ///
56    /// Returns [`GenBytesError::TooManyBytes`] if `len` exceeds 1 KiB. If you want over 1KiB of
57    /// randomness, please reconsider your design.
58    pub fn gen_bytes(&self, len: u32) -> Result<Vec<u8>, GenBytesError> {
59        if len > 1024 {
60            return Err(GenBytesError::TooManyBytes { requested: len });
61        }
62
63        let mut buf = vec![0u8; len as usize];
64        rng().fill_bytes(&mut buf);
65        Ok(buf)
66    }
67
68    /// Generate a random v4 UUID, sampled from a CRNG
69    ///
70    /// This has 122 random bits of entropy.
71    pub fn gen_uuid(&self) -> String {
72        let mut bytes = [0u8; 16];
73        rng().fill_bytes(&mut bytes);
74        uuid::Builder::from_random_bytes(bytes)
75            .with_variant(uuid::Variant::RFC4122)
76            .with_version(uuid::Version::Random)
77            .into_uuid()
78            .to_string()
79    }
80
81    /// Generate a cryptographically-secure random number in the range `[min, max]` (inclusive).
82    ///
83    /// Returns [`GenRangeError::InvalidRange`] if `min > max`.
84    pub fn gen_range(&self, min: u32, max: u32) -> Result<u32, GenRangeError> {
85        if min > max {
86            return Err(GenRangeError::InvalidRange { min, max });
87        }
88        Ok(rng().random_range(min..=max))
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use uuid::Uuid;
95
96    use super::*;
97
98    #[test]
99    fn gen_bytes_returns_requested_length() {
100        let client = SdkRandomNumberClient::new();
101        assert_eq!(client.gen_bytes(0).unwrap().len(), 0);
102        assert_eq!(client.gen_bytes(1).unwrap().len(), 1);
103        assert_eq!(client.gen_bytes(32).unwrap().len(), 32);
104        // 1 KiB is the documented maximum and must succeed.
105        assert_eq!(client.gen_bytes(1024).unwrap().len(), 1024);
106    }
107
108    #[test]
109    fn gen_bytes_is_random() {
110        let client = SdkRandomNumberClient::new();
111        // Two independent draws differ with overwhelming probability.
112        assert_ne!(client.gen_bytes(32).unwrap(), client.gen_bytes(32).unwrap());
113    }
114
115    #[test]
116    fn gen_bytes_errors_above_1_kib() {
117        let err = SdkRandomNumberClient::new().gen_bytes(1025).unwrap_err();
118        assert!(matches!(
119            err,
120            GenBytesError::TooManyBytes { requested: 1025 }
121        ));
122    }
123
124    #[test]
125    fn gen_uuid_is_a_valid_random_v4_uuid() {
126        let client = SdkRandomNumberClient::new();
127        let uuid = Uuid::parse_str(&client.gen_uuid()).expect("a valid UUID string");
128        assert_eq!(uuid.get_version(), Some(uuid::Version::Random));
129        assert_eq!(uuid.get_variant(), uuid::Variant::RFC4122);
130    }
131
132    #[test]
133    fn gen_uuid_is_distinct() {
134        let client = SdkRandomNumberClient::new();
135        assert_ne!(client.gen_uuid(), client.gen_uuid());
136    }
137
138    #[test]
139    fn gen_range_stays_within_inclusive_bounds() {
140        let client = SdkRandomNumberClient::new();
141        for _ in 0..1000 {
142            let n = client.gen_range(10, 20).unwrap();
143            assert!((10..=20).contains(&n));
144        }
145    }
146
147    #[test]
148    fn gen_range_with_single_value_is_that_value() {
149        let client = SdkRandomNumberClient::new();
150        assert_eq!(client.gen_range(7, 7).unwrap(), 7);
151    }
152
153    #[test]
154    fn gen_range_errors_when_min_exceeds_max() {
155        let err = SdkRandomNumberClient::new().gen_range(20, 10).unwrap_err();
156        assert!(matches!(
157            err,
158            GenRangeError::InvalidRange { min: 20, max: 10 }
159        ));
160    }
161}