#![allow(unsafe_code)]
use super::lenient::{Halt, Lenient};
use crate::{
Error,
base64::{
Base64,
kernel::{Break, LineShape},
},
buffer::SpareCapacity,
};
const GROWTH: usize = 4096;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct FoldedValue {
pub end: usize,
pub next: usize,
}
impl Base64 {
pub fn decode_folded(
&self,
input: impl AsRef<[u8]>,
out: &mut Vec<u8>,
) -> Result<FoldedValue, Error> {
self.decode_folded_into(input.as_ref(), out)
}
fn decode_folded_into(&self, input: &[u8], out: &mut Vec<u8>) -> Result<FoldedValue, Error> {
let tables = self.decode_tables();
let shape = LineShape::detect_folded(input);
let mut state = Lenient::folded(shape);
let start = out.len();
let mut bound = LineShape::folded_extent(shape, input);
let mut read = 0;
let halt = loop {
let rest = input.get(read..).unwrap_or_default();
let room = self.decoded_len_estimate(bound.saturating_sub(read)) + 3;
let mut halt = Halt::End;
let mut consumed = 0;
unsafe {
out.append_with(room, |dst| {
let feed = state.feed(tables, rest, dst);
consumed = feed.read;
halt = feed.halt;
feed.written
})
};
read += consumed;
match halt {
Halt::Full if bound < input.len() => {
bound = (read + 2 * bound.saturating_sub(read).max(GROWTH)).min(input.len());
}
other => break other,
}
};
let value = match halt {
Halt::Byte(byte) => match Break::skip(input.get(read..).unwrap_or_default(), false) {
Some(len) => FoldedValue {
end: read,
next: read + len,
},
None => {
out.truncate(start);
return Err(Error::unexpected(byte, read));
}
},
Halt::End => FoldedValue {
end: input.len(),
next: input.len(),
},
Halt::Full => {
out.truncate(start);
return Err(Error::BufferTooSmall {
required: self.decoded_len_estimate(input.len()),
});
}
};
let mut result = Ok(());
unsafe {
out.append_with(2, |dst| match state.finish(dst) {
Ok(written) => written,
Err(err) => {
result = Err(err);
0
}
})
};
result.map(|()| value)
}
}