1use super::{
4 ordinary::OneShotError,
5 specifications::{Base64, Codec, CodecSettings, EncodePadding},
6};
7
8#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub struct EncodedChunk {
15 bytes: [u8; 4],
16 len: usize,
17}
18
19impl EncodedChunk {
20 #[must_use]
22 pub fn as_bytes(&self) -> &[u8] {
23 &self.bytes[..self.len]
24 }
25
26 pub fn as_str(&self) -> Result<&str, core::str::Utf8Error> {
31 core::str::from_utf8(self.as_bytes())
32 }
33}
34
35impl core::fmt::Display for EncodedChunk {
36 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
37 formatter.write_str(self.as_str().map_err(|_| core::fmt::Error)?)
38 }
39}
40
41#[derive(Clone)]
48pub struct EncodedChunks<'a> {
49 settings: CodecSettings,
50 input: &'a [u8],
51 offset: usize,
52}
53
54impl core::fmt::Debug for EncodedChunks<'_> {
55 fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
56 formatter
57 .debug_struct("EncodedChunks")
58 .field("input_len", &self.input.len())
59 .field("offset", &self.offset)
60 .field("remaining_chunks", &self.remaining_chunks())
61 .finish_non_exhaustive()
62 }
63}
64
65impl<'a> EncodedChunks<'a> {
66 pub(super) const fn new(settings: CodecSettings, input: &'a [u8]) -> Self {
67 Self {
68 settings,
69 input,
70 offset: 0,
71 }
72 }
73
74 fn remaining_chunks(&self) -> usize {
75 self.input.len().saturating_sub(self.offset).div_ceil(3)
76 }
77}
78
79impl Iterator for EncodedChunks<'_> {
80 type Item = EncodedChunk;
81
82 fn next(&mut self) -> Option<Self::Item> {
83 let remaining = self.input.get(self.offset..)?;
84 if remaining.is_empty() {
85 return None;
86 }
87
88 let consumed = remaining.len().min(3);
89 let chunk = encode_chunk(self.settings, &remaining[..consumed]);
90 self.offset += consumed;
91 Some(chunk)
92 }
93
94 fn size_hint(&self) -> (usize, Option<usize>) {
95 let remaining = self.remaining_chunks();
96 (remaining, Some(remaining))
97 }
98}
99
100impl ExactSizeIterator for EncodedChunks<'_> {
101 fn len(&self) -> usize {
102 self.remaining_chunks()
103 }
104}
105
106impl core::iter::FusedIterator for EncodedChunks<'_> {}
107
108impl<S: Codec> Base64<S> {
109 pub fn encoded_chunks<'a>(&self, input: &'a [u8]) -> Result<EncodedChunks<'a>, OneShotError> {
115 self.encoded_len(input.len())?;
116 Ok(EncodedChunks::new(self.settings(), input))
117 }
118}
119
120fn encode_chunk(settings: CodecSettings, input: &[u8]) -> EncodedChunk {
121 let alphabet = settings.alphabet().as_array();
122 let first = input[0];
123 let second = input.get(1).copied().unwrap_or(0);
124 let third = input.get(2).copied().unwrap_or(0);
125 let mut bytes = [b'='; 4];
126
127 bytes[0] = alphabet[usize::from(first >> 2)];
128 bytes[1] = alphabet[usize::from(((first & 3) << 4) | (second >> 4))];
129 bytes[2] = alphabet[usize::from(((second & 15) << 2) | (third >> 6))];
130 bytes[3] = alphabet[usize::from(third & 63)];
131
132 let len = match input.len() {
133 3 => 4,
134 1 | 2 if settings.encode_padding() == EncodePadding::Padded => 4,
135 2 => 3,
136 1 => 2,
137 _ => 0,
138 };
139 if settings.encode_padding() == EncodePadding::Padded {
140 if input.len() == 1 {
141 bytes[2] = b'=';
142 bytes[3] = b'=';
143 } else if input.len() == 2 {
144 bytes[3] = b'=';
145 }
146 }
147
148 EncodedChunk { bytes, len }
149}