1use arrow_array::{Array, GenericBinaryArray, GenericStringArray, OffsetSizeTrait};
23use arrow_buffer::{Buffer, OffsetBuffer};
24use arrow_schema::ArrowError;
25use base64::encoded_len;
26use base64::engine::Config;
27
28pub use base64::prelude::*;
29
30pub fn b64_encode<E: Engine, O: OffsetSizeTrait>(
38 engine: &E,
39 array: &GenericBinaryArray<O>,
40) -> GenericStringArray<O> {
41 let lengths = array.offsets().windows(2).map(|w| {
42 let len = w[1].as_usize() - w[0].as_usize();
43 encoded_len(len, engine.config().encode_padding()).unwrap()
44 });
45 let offsets = OffsetBuffer::<O>::from_lengths(lengths);
46 let buffer_len = offsets.last().unwrap().as_usize();
47 let mut buffer = vec![0_u8; buffer_len];
48 let mut offset = 0;
49
50 for i in 0..array.len() {
51 let len = engine
52 .encode_slice(array.value(i), &mut buffer[offset..])
53 .unwrap();
54 offset += len;
55 }
56 assert_eq!(offset, buffer_len);
57
58 GenericStringArray::try_new(offsets, Buffer::from_vec(buffer), array.nulls().cloned())
60 .expect("Engine produced invalid UTF-8")
61}
62
63pub fn b64_decode<E: Engine, O: OffsetSizeTrait>(
65 engine: &E,
66 array: &GenericBinaryArray<O>,
67) -> Result<GenericBinaryArray<O>, ArrowError> {
68 let estimated_len = array.values().len(); let mut buffer = vec![0; estimated_len];
70
71 let mut offsets = Vec::with_capacity(array.len() + 1);
72 offsets.push(O::usize_as(0));
73 let mut offset = 0;
74
75 for v in array.iter() {
76 if let Some(v) = v {
77 let len = engine.decode_slice(v, &mut buffer[offset..]).unwrap();
78 offset += len;
80 }
81 offsets.push(O::usize_as(offset));
82 }
83
84 let offsets = unsafe { OffsetBuffer::new_unchecked(offsets.into()) };
86
87 GenericBinaryArray::try_new(offsets, Buffer::from_vec(buffer), array.nulls().cloned())
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use arrow_array::BinaryArray;
94 use rand::{RngExt, rng};
95
96 fn test_engine<E: Engine>(e: &E, a: &BinaryArray) {
97 let encoded = b64_encode(e, a);
98 encoded.to_data().validate_full().unwrap();
99
100 let to_decode = encoded.into();
101 let decoded = b64_decode(e, &to_decode).unwrap();
102 decoded.to_data().validate_full().unwrap();
103
104 assert_eq!(&decoded, a);
105 }
106
107 #[test]
108 #[cfg_attr(miri, ignore)] fn test_b64() {
110 let mut rng = rng();
111 let len = rng.random_range(1024..1050);
112 let data: BinaryArray = (0..len)
113 .map(|_| {
114 let len = rng.random_range(0..16);
115 Some((0..len).map(|_| rng.random::<u8>()).collect::<Vec<u8>>())
116 })
117 .collect();
118
119 test_engine(&BASE64_STANDARD, &data);
120 test_engine(&BASE64_STANDARD_NO_PAD, &data);
121 }
122
123 struct EvilEngine;
127
128 impl Engine for EvilEngine {
129 type Config = <base64::engine::GeneralPurpose as Engine>::Config;
130 type DecodeEstimate = <base64::engine::GeneralPurpose as Engine>::DecodeEstimate;
131
132 fn internal_encode(&self, input: &[u8], output: &mut [u8]) -> usize {
133 BASE64_STANDARD.internal_encode(input, output)
134 }
135 fn internal_decoded_len_estimate(&self, input_len: usize) -> Self::DecodeEstimate {
136 BASE64_STANDARD.internal_decoded_len_estimate(input_len)
137 }
138 fn internal_decode(
139 &self,
140 input: &[u8],
141 output: &mut [u8],
142 estimate: Self::DecodeEstimate,
143 ) -> Result<base64::engine::DecodeMetadata, base64::DecodeSliceError> {
144 BASE64_STANDARD.internal_decode(input, output, estimate)
145 }
146 fn config(&self) -> &Self::Config {
147 BASE64_STANDARD.config()
148 }
149 fn encode_slice<T: AsRef<[u8]>>(
150 &self,
151 input: T,
152 output_buf: &mut [u8],
153 ) -> Result<usize, base64::EncodeSliceError> {
154 let len = BASE64_STANDARD.encode_slice(input, output_buf)?;
155 for b in output_buf[..len].iter_mut() {
156 *b = 0xFF; }
158 Ok(len)
159 }
160
161 fn padding(&self) -> base64::alphabet::Symbol {
162 base64::alphabet::Symbol::new(b'=').unwrap()
163 }
164 }
165
166 #[test]
167 #[should_panic(expected = "produced invalid UTF-8")]
168 fn test_b64_encode_rejects_invalid_utf8() {
169 let data: BinaryArray = vec![Some(b"hello".to_vec())].into_iter().collect();
170 let _ = b64_encode(&EvilEngine, &data);
171 }
172}