Skip to main content

bitwarden_encoding/
chunked.rs

1use data_encoding::Encoding;
2
3/// Encodes data using the provided encoding, transparently chunking to work around
4/// `data-encoding`'s `encode_len` length assertion (`len <= usize::MAX / 512`, ~8 MB on 32-bit
5/// targets like wasm32). On 64-bit targets the limit is effectively infinite so all input fits in a
6/// single chunk.
7pub(crate) fn chunked_encode(encoding: &Encoding, data: &[u8]) -> String {
8    chunked_encode_with_limit(encoding, data, usize::MAX / 512)
9}
10
11fn chunked_encode_with_limit(encoding: &Encoding, data: &[u8], max_safe: usize) -> String {
12    // Base64 output is ~4/3 of input; slightly over-estimate to avoid reallocations.
13    let mut output = String::with_capacity(data.len() / 3 * 4 + 4);
14
15    let mut enc = encoding.new_encoder(&mut output);
16    for chunk in data.chunks(max_safe) {
17        enc.append(chunk);
18    }
19    enc.finalize();
20    output
21}
22
23#[cfg(test)]
24mod tests {
25    use data_encoding::{BASE64, BASE64URL_NOPAD};
26
27    use super::*;
28
29    #[test]
30    fn single_chunk_matches_direct_encode() {
31        let data = b"Hello, World!";
32        assert_eq!(chunked_encode(&BASE64, data), BASE64.encode(data));
33        assert_eq!(
34            chunked_encode(&BASE64URL_NOPAD, data),
35            BASE64URL_NOPAD.encode(data)
36        );
37    }
38
39    #[test]
40    fn multi_chunk_matches_direct_encode_base64() {
41        // 30 bytes of input, chunked at max_safe=6
42        // This forces 5 separate chunks.
43        let data: Vec<u8> = (0..30).collect();
44        let expected = BASE64.encode(&data);
45        let result = chunked_encode_with_limit(&BASE64, &data, 6);
46        assert_eq!(result, expected);
47    }
48
49    #[test]
50    fn multi_chunk_matches_direct_encode_base64url() {
51        let data: Vec<u8> = (0..30).collect();
52        let expected = BASE64URL_NOPAD.encode(&data);
53        let result = chunked_encode_with_limit(&BASE64URL_NOPAD, &data, 6);
54        assert_eq!(result, expected);
55    }
56
57    #[test]
58    fn unaligned_chunk_boundary() {
59        // max_safe=7 is not a multiple of 3 (base64 input group size).
60        // The streaming encoder handles this correctly without manual alignment.
61        let data: Vec<u8> = (0..30).collect();
62        let expected = BASE64.encode(&data);
63        let result = chunked_encode_with_limit(&BASE64, &data, 7);
64        assert_eq!(result, expected);
65    }
66
67    #[test]
68    fn input_exactly_one_chunk() {
69        let data: Vec<u8> = (0..6).collect();
70        let expected = BASE64.encode(&data);
71        let result = chunked_encode_with_limit(&BASE64, &data, 6);
72        assert_eq!(result, expected);
73    }
74
75    #[test]
76    fn input_one_byte_over_chunk() {
77        let data: Vec<u8> = (0..7).collect();
78        let expected = BASE64.encode(&data);
79        let result = chunked_encode_with_limit(&BASE64, &data, 6);
80        assert_eq!(result, expected);
81    }
82
83    #[test]
84    fn empty_input() {
85        assert_eq!(chunked_encode_with_limit(&BASE64, &[], 6), "");
86        assert_eq!(chunked_encode_with_limit(&BASE64URL_NOPAD, &[], 6), "");
87    }
88}