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