Skip to main content

bitwarden_crypto/stream/
large_memory_buffer.rs

1//! A module for allocating large remote buffers not in WASM memory. WASM generally does not support
2//! resizing the heap after it has been allocated, so we need to allocate a buffer that can be
3//! released again.
4//!
5//! The buffer is allocated once to an exact size at construction and is never resized, since
6//! resizing a `Uint8Array` on WASM is slow (it requires allocating a new array and copying).
7//!
8//! This is used for streaming encryption in the legacy format.
9
10pub struct Buffer {
11    // The Uint8Array lives in JS memory, and wasm merely keeps a reference to it in the
12    // wasm-bindgen-glue-code-heap `https://wasm-bindgen.github.io/wasm-bindgen/contributing/design/js-objects-in-rust.html#long-lived-js-objects`
13    // We cannot convert the full arary to slice / Vec because that would require allocating and
14    // copying the data to WASM memory. Instead, we create slice access operations.
15    //
16    // It is freed automatically by the host JS engine's garbage collector, as soon as the rust
17    // side drops the reference. The wasm-bindgen-glue-code-heap releases the JS-side reference
18    // as soon as the rust side reference is
19    //
20    // Please note, while chrome / firefox do release the memory from the JS environment, on the OS
21    // side they may keep the memory allocated for a while regardless.
22    #[cfg(target_arch = "wasm32")]
23    inner: js_sys::Uint8Array,
24
25    // On non-wasm targets, deallocating memory is supported so we can just use a Vec<u8> that
26    // lives on the heap and is managed by regular Rust ownership rules.
27    #[cfg(not(target_arch = "wasm32"))]
28    inner: Vec<u8>,
29
30    // Total allocated capacity in bytes. Fixed at construction; the buffer never grows.
31    capacity: usize,
32
33    // Write cursor: the number of bytes appended so far.
34    position: usize,
35}
36
37#[derive(Debug)]
38pub(crate) struct InvalidIndexError;
39
40impl Buffer {
41    pub fn new(capacity: usize) -> Self {
42        Self {
43            #[cfg(target_arch = "wasm32")]
44            inner: js_sys::Uint8Array::new_with_length(capacity as u32).into(),
45
46            #[cfg(not(target_arch = "wasm32"))]
47            inner: vec![0; capacity],
48
49            capacity,
50            position: 0,
51        }
52    }
53
54    pub fn index(&self, index: std::ops::Range<usize>) -> Result<Vec<u8>, InvalidIndexError> {
55        if index.end > self.capacity || index.start > index.end {
56            return Err(InvalidIndexError);
57        }
58
59        #[cfg(target_arch = "wasm32")]
60        {
61            Ok(self
62                .inner
63                .slice(index.start as u32, index.end as u32)
64                .to_vec())
65        }
66
67        #[cfg(not(target_arch = "wasm32"))]
68        {
69            Ok(self.inner[index].to_vec())
70        }
71    }
72
73    pub fn append(&mut self, data: &[u8]) -> Result<(), InvalidIndexError> {
74        if self.position + data.len() > self.capacity {
75            return Err(InvalidIndexError);
76        }
77
78        #[cfg(target_arch = "wasm32")]
79        {
80            self.inner
81                .subarray(self.position as u32, (self.position + data.len()) as u32)
82                .copy_from(data);
83        }
84
85        #[cfg(not(target_arch = "wasm32"))]
86        {
87            self.inner[self.position..self.position + data.len()].copy_from_slice(data);
88        }
89
90        self.position += data.len();
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98
99    #[test]
100    fn test_buffer() {
101        let mut buffer = Buffer::new(5);
102        buffer
103            .append(&[1, 2, 3, 4, 5])
104            .expect("range must be valid");
105        assert_eq!(&buffer.index(0..5).unwrap(), &[1, 2, 3, 4, 5]);
106    }
107
108    #[test]
109    fn test_append_advances_write_cursor() {
110        let mut buffer = Buffer::new(9);
111        buffer
112            .append(&[1, 2, 3, 4, 5])
113            .expect("range must be valid");
114        buffer.append(&[6, 7, 8, 9]).expect("range must be valid");
115        assert_eq!(&buffer.index(0..9).unwrap(), &[1, 2, 3, 4, 5, 6, 7, 8, 9]);
116    }
117
118    #[test]
119    fn test_append_past_capacity_errors() {
120        let mut buffer = Buffer::new(4);
121        assert!(buffer.append(&[1, 2, 3, 4, 5]).is_err());
122    }
123}