1use std::io;
4
5use generic_array::{
6 typenum::{Sum, Unsigned, U2},
7 ArrayLength, GenericArray,
8};
9
10const CRLF: [u8; 2] = [b'\r', b'\n'];
11const CR: [u8; 1] = [b'\r'];
12const LF: [u8; 1] = [b'\n'];
13
14#[derive(Clone, Copy, PartialEq, Eq)]
15#[repr(u8)]
16pub enum LineBreak {
17 Crlf,
18 Lf,
19 Cr,
20}
21
22impl AsRef<[u8]> for LineBreak {
23 fn as_ref(&self) -> &[u8] {
24 match self {
25 LineBreak::Crlf => &CRLF[..],
26 LineBreak::Lf => &LF[..],
27 LineBreak::Cr => &CR[..],
28 }
29 }
30}
31
32pub struct LineWriter<'a, W, N>
39where
40 W: io::Write,
41 N: Unsigned + ArrayLength<u8>,
42 N: std::ops::Add<U2>,
43 Sum<N, U2>: ArrayLength<u8>,
44{
45 line_break: LineBreak,
47 w: &'a mut W,
49 extra: GenericArray<u8, N>,
52 extra_len: usize,
54 buffer: GenericArray<u8, Sum<N, U2>>,
55 finished: bool,
57 panicked: bool,
59}
60
61impl<'a, W, N> LineWriter<'a, W, N>
62where
63 W: 'a + io::Write,
64 N: Unsigned + ArrayLength<u8>,
65 N: std::ops::Add<U2>,
66 Sum<N, U2>: ArrayLength<u8>,
67{
68 pub fn new(w: &'a mut W, line_break: LineBreak) -> Self {
70 LineWriter {
71 line_break,
72 w,
73 extra: Default::default(),
74 buffer: Default::default(),
75 extra_len: 0,
76 finished: false,
77 panicked: false,
78 }
79 }
80
81 pub fn finish(&mut self) -> io::Result<()> {
90 if self.finished {
91 return Ok(());
92 };
93
94 if self.extra_len > 0 {
95 self.panicked = true;
96 self.w.write_all(&self.extra[..self.extra_len])?;
97 self.w.write_all(self.line_break.as_ref())?;
98 self.panicked = false;
99 self.extra_len = 0;
101 }
102
103 self.finished = true;
104 Ok(())
105 }
106}
107
108impl<'a, W, N> io::Write for LineWriter<'a, W, N>
109where
110 W: 'a + io::Write,
111 N: Unsigned + ArrayLength<u8>,
112 N: std::ops::Add<U2>,
113 Sum<N, U2>: ArrayLength<u8>,
114{
115 fn write(&mut self, input: &[u8]) -> io::Result<usize> {
116 if self.finished {
117 panic!("Cannot write more after calling finish()");
118 }
119
120 if input.is_empty() {
121 return Ok(0);
122 }
123
124 let sl = N::to_usize();
131 let line_break = self.line_break.as_ref();
132
133 let orig_extra_len = self.extra_len;
134
135 if self.extra_len + input.len() < sl {
137 self.extra_len += input.len();
139 self.extra[orig_extra_len..self.extra_len].copy_from_slice(input);
140 Ok(input.len())
141 } else {
142 let mut buffer_pos = 0;
143 let mut input_pos = 0;
144
145 if self.extra_len > 0 {
146 let copied = ::std::cmp::min(orig_extra_len, self.buffer.len());
147 self.buffer[buffer_pos..buffer_pos + copied].copy_from_slice(&self.extra[..copied]);
148 self.extra_len -= copied;
149 buffer_pos += copied;
150 }
151
152 if buffer_pos < sl {
153 let missing = ::std::cmp::min(sl - buffer_pos, input.len() - input_pos);
154
155 self.buffer[buffer_pos..buffer_pos + missing]
156 .copy_from_slice(&input[input_pos..input_pos + missing]);
157
158 buffer_pos += missing;
159 input_pos += missing;
160 }
161
162 if buffer_pos < sl {
164 return Ok(input_pos);
165 }
166
167 self.buffer[buffer_pos..buffer_pos + line_break.len()].copy_from_slice(line_break);
169 buffer_pos += line_break.len();
170
171 self.panicked = true;
172 let r = self.w.write_all(&self.buffer[..buffer_pos]);
173 self.panicked = false;
174
175 match r {
176 Ok(_) => Ok(input_pos),
177 Err(err) => {
178 self.extra_len = orig_extra_len;
180 Err(err)
181 }
182 }
183 }
184 }
185
186 fn flush(&mut self) -> io::Result<()> {
189 self.w.flush()
190 }
191}
192
193impl<'a, W, N> Drop for LineWriter<'a, W, N>
194where
195 W: 'a + io::Write,
196 N: Unsigned + ArrayLength<u8>,
197 N: std::ops::Add<U2>,
198 Sum<N, U2>: ArrayLength<u8>,
199{
200 fn drop(&mut self) {
201 if !self.panicked {
202 let _ = self.finish();
204 }
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 #![allow(clippy::unwrap_used)]
211
212 use std::io::Write;
213
214 use base64::engine::general_purpose;
215 use generic_array::typenum::{self, U10};
216
217 use super::*;
218
219 fn write_all(writer: &mut impl io::Write, mut buf: &[u8]) -> io::Result<()> {
222 while !buf.is_empty() {
223 match writer.write(buf) {
224 Ok(0) => {}
225 Ok(n) => buf = &buf[n..],
226 Err(ref e) if e.kind() == io::ErrorKind::Interrupted => {}
227 Err(e) => return Err(e),
228 }
229 }
230 Ok(())
231 }
232
233 #[test]
234 fn simple_writes() {
235 let mut buf = Vec::new();
236
237 {
238 let mut w = LineWriter::<_, U10>::new(&mut buf, LineBreak::Crlf);
239
240 assert_eq!(w.write(&[0, 1, 2, 3]).unwrap(), 4);
242 assert_eq!(w.write(&[4, 5, 6, 7]).unwrap(), 4);
243 assert_eq!(w.write(&[8, 9, 10, 11]).unwrap(), 2);
244 assert_eq!(w.write(&[10, 11]).unwrap(), 2);
245
246 }
248
249 assert_eq!(
250 &buf[..],
251 &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, b'\r', b'\n', 10, 11, b'\r', b'\n'][..]
252 );
253 }
254
255 macro_rules! test_len {
256 ( $name:ident, $len:ty ) => {
257 #[test]
258 fn $name() {
259 use rand::{Rng, SeedableRng};
260 use rand_xorshift::XorShiftRng;
261
262 let rng = &mut XorShiftRng::from_seed([
263 0x3, 0x8, 0x3, 0xe, 0x3, 0x8, 0x3, 0xe, 0x3, 0x8, 0x3, 0xe, 0x3, 0x8, 0x3, 0xe,
264 ]);
265 let mut buf = Vec::new();
266
267 let mut list = Vec::new();
268 {
269 let mut w = LineWriter::<_, $len>::new(&mut buf, LineBreak::Crlf);
270 for i in 0..100 {
271 let data = (0..i).map(|_| rng.gen()).collect::<Vec<_>>();
272 w.write_all(&data).unwrap();
273 list.extend(&data);
274 }
275 }
276
277 let len = <$len as typenum::Unsigned>::to_usize();
278 let mut expected: Vec<u8> = list.chunks(len).fold(Vec::new(), |mut acc, line| {
279 acc.extend(line);
280
281 if line.len() == len {
282 acc.push(b'\r');
283 acc.push(b'\n');
284 }
285 acc
286 });
287
288 if expected.len() % len != 0 {
289 expected.push(b'\r');
290 expected.push(b'\n');
291 }
292
293 assert_eq!(&buf[..], &expected[..]);
294 }
295 };
296 }
297
298 test_len!(test_break_line_len_1, typenum::U1);
299 test_len!(test_break_line_len_2, typenum::U2);
300 test_len!(test_break_line_len_10, typenum::U10);
301 test_len!(test_break_line_len_74, typenum::U74);
302 test_len!(test_break_line_len_100, typenum::U100);
303 #[test]
306 fn test_key_roundtrip() {
307 let content = hex::decode("99010d04583c17af010800b9552ff9f4dae0e66c3ec0402793fbe2d02188f8ae6b1939b202bbb2fda892e2461f5098843eafc965809e350f464db24de4cf858afd5b870eb17847e5e05002fc0d14a37f5fbd448b247d95fbc953dc6c57b7c291631f0cbe8b6fa0a886d4346f827c11875aa26d7ebe86d25a84fc070d5894b85cf465e10f5a20b0ba830e10b9a24ecb845596d0b2fd3c07008ecc873733cb3add3b7030251ef8c061a9f46312eebf1adee68ae1865455c1f7a6a8d8ef6ed47b11edc523815429e89c062e02088a38f2d7aeccb3e7ca65eb6a03db73bb50b5480b4d622aa6014a2a186d581234bba00a6800a9870fe2c608c50f83977a6e1c3e3721e30c015624462fde41b70011010001b43554657374204b65792028646f206e6f742075736529203c61757468656e7469636174652d6f6e6c79406578616d706c652e6f72673e89014e041301080038162104927ef377fd1a1b6f795e40c02a87917d8ffba49f0502583c17af021b01050b09080702061508090a0b020416020301021e01021780000a09102a87917d8ffba49f7ecc07fd1556649c309608d638dbe448477e9fab69751acf0a7ccb17acd5ed7d83ed2ea8a83fc7f3d8b1342e8b9d4ac64c2d5ca0a273c6d190317485075dc15d52a3eb133b387c4c91e3169e392c6e8b643fbbafbd6e2ede8a5618cd53515a4bb2c764eb4506448cc5cc9ee25c5a9b466d15acfe2a5151904759f2e5dae74b97ed134482fb8678b4eb15421dbc04e6ccdc2e8cb3cef228c065400d716a786ee7b72eee44d64d003f9958c1f6274beca599544958bbcf55728330c8dd4e3648c26656a9d19880ac07740b23c36ce27c565cedb3f1f85e48572b3fe2c0718dd6e898272c2cefcaea20c675a67787f3af5881dc4d87732fecaf4720439c2dbac79046199396b9010d04583c17d9010800b4a71b058ac8aa1ddc453ab2663331c38f7645542815ac189a9af56d0e07a615469d3e08849650e03026d49259423cf00d089931cd700fd3a6e940bf83c81406e142a4b0a86f00738c7e1a9ff1b709f6bccc6cf900d0113a8e62e53d63be0a05105755b9efc6a4098c362c73fb422d40187d8e2382e88624d72caffceb13cec8fa0079c7d17883a46a1336471ab5be8cbb555c5d330d7fadb43318fa73b584edac312fa3302886bb5d04a05da3be2676c1fb94b3cf5c19d598659c3a7728ebab95f71721b662ac46aa9910726fe576d438f789c5ce2448f54546f254da814bcae1c35ee44b171e870ffa6403167a10e68573bdf155549274b431ff8e2418b6270011010001890136041801080020162104927ef377fd1a1b6f795e40c02a87917d8ffba49f0502583c17d9021b20000a09102a87917d8ffba49fac6b07ff7928f1c4082501da2517d94ad3bd2e566320ab81853ea27746a24f5058f010515260b5e48802e73065c34a639f3ea090e1cf8f5b0ba6161282cf83175430fcc7a9a2f59f87944c0831a0a7724ad24ee4393a0c0effabe4873e3639c80c6775909d67cd54cf236cd3fdbd7d6fae83de1cb15a3c6cbd28930cd19fe19422087b22bf6bdd335a54f950c7d1a2a35045b63f8a261d9a9bdfebd23d3e86c655ba3feda8594ee98f5b08e218eced3577aea39514680555c4c40160aa76c37c22976b07cd87d37d851233287ea14171e17973585ddb2f3b1a7d169eb8ad61e7e26ebc87229af539cd666d9f484ea62217a593a826fbccf6be43e19453e545f66e543c3f").unwrap();
308
309 {
311 let mut buf = Vec::new();
312 {
313 let mut enc =
314 base64::write::EncoderWriter::new(&mut buf, &general_purpose::STANDARD);
315 enc.write_all(&content).unwrap();
316 }
317
318 assert_eq!(
319 ::std::str::from_utf8(&buf).unwrap(),
320 "mQENBFg8F68BCAC5VS/59Nrg5mw+wEAnk/vi0CGI+K5rGTmyAruy/aiS4kYfUJiE\
321 Pq/JZYCeNQ9GTbJN5M+Fiv1bhw6xeEfl4FAC/A0Uo39fvUSLJH2V+8lT3GxXt8KR\
322 Yx8MvotvoKiG1DRvgnwRh1qibX6+htJahPwHDViUuFz0ZeEPWiCwuoMOELmiTsuE\
323 VZbQsv08BwCOzIc3M8s63TtwMCUe+MBhqfRjEu6/Gt7miuGGVFXB96ao2O9u1HsR\
324 7cUjgVQp6JwGLgIIijjy167Ms+fKZetqA9tzu1C1SAtNYiqmAUoqGG1YEjS7oApo\
325 AKmHD+LGCMUPg5d6bhw+NyHjDAFWJEYv3kG3ABEBAAG0NVRlc3QgS2V5IChkbyBu\
326 b3QgdXNlKSA8YXV0aGVudGljYXRlLW9ubHlAZXhhbXBsZS5vcmc+iQFOBBMBCAA4\
327 FiEEkn7zd/0aG295XkDAKoeRfY/7pJ8FAlg8F68CGwEFCwkIBwIGFQgJCgsCBBYC\
328 AwECHgECF4AACgkQKoeRfY/7pJ9+zAf9FVZknDCWCNY42+RIR36fq2l1Gs8KfMsX\
329 rNXtfYPtLqioP8fz2LE0LoudSsZMLVygonPG0ZAxdIUHXcFdUqPrEzs4fEyR4xae\
330 OSxui2Q/u6+9bi7eilYYzVNRWkuyx2TrRQZEjMXMnuJcWptGbRWs/ipRUZBHWfLl\
331 2udLl+0TRIL7hni06xVCHbwE5szcLoyzzvIowGVADXFqeG7nty7uRNZNAD+ZWMH2\
332 J0vspZlUSVi7z1VygzDI3U42SMJmVqnRmICsB3QLI8Ns4nxWXO2z8fheSFcrP+LA\
333 cY3W6JgnLCzvyuogxnWmd4fzr1iB3E2Hcy/sr0cgQ5wtuseQRhmTlrkBDQRYPBfZ\
334 AQgAtKcbBYrIqh3cRTqyZjMxw492RVQoFawYmpr1bQ4HphVGnT4IhJZQ4DAm1JJZ\
335 QjzwDQiZMc1wD9Om6UC/g8gUBuFCpLCobwBzjH4an/G3Cfa8zGz5ANAROo5i5T1j\
336 vgoFEFdVue/GpAmMNixz+0ItQBh9jiOC6IYk1yyv/OsTzsj6AHnH0XiDpGoTNkca\
337 tb6Mu1VcXTMNf620Mxj6c7WE7awxL6MwKIa7XQSgXaO+JnbB+5Szz1wZ1ZhlnDp3\
338 KOurlfcXIbZirEaqmRByb+V21Dj3icXOJEj1RUbyVNqBS8rhw17kSxcehw/6ZAMW\
339 ehDmhXO98VVUknS0Mf+OJBi2JwARAQABiQE2BBgBCAAgFiEEkn7zd/0aG295XkDA\
340 KoeRfY/7pJ8FAlg8F9kCGyAACgkQKoeRfY/7pJ+sawf/eSjxxAglAdolF9lK070u\
341 VmMgq4GFPqJ3RqJPUFjwEFFSYLXkiALnMGXDSmOfPqCQ4c+PWwumFhKCz4MXVDD8\
342 x6mi9Z+HlEwIMaCnckrSTuQ5OgwO/6vkhz42OcgMZ3WQnWfNVM8jbNP9vX1vroPe\
343 HLFaPGy9KJMM0Z/hlCIIeyK/a90zWlT5UMfRoqNQRbY/iiYdmpvf69I9PobGVbo/\
344 7ahZTumPWwjiGOztNXeuo5UUaAVVxMQBYKp2w3wil2sHzYfTfYUSMyh+oUFx4Xlz\
345 WF3bLzsafRaeuK1h5+JuvIcimvU5zWZtn0hOpiIXpZOoJvvM9r5D4ZRT5UX2blQ8\
346 Pw==",
347 );
348 }
349
350 let mut buf = Vec::new();
351 {
352 let mut line_wrapper = LineWriter::<_, typenum::U64>::new(&mut buf, LineBreak::Lf);
353 let mut enc =
354 base64::write::EncoderWriter::new(&mut line_wrapper, &general_purpose::STANDARD);
355 write_all(&mut enc, &content).unwrap();
356 }
357
358 assert_eq!(
359 ::std::str::from_utf8(&buf).unwrap(),
360 "mQENBFg8F68BCAC5VS/59Nrg5mw+wEAnk/vi0CGI+K5rGTmyAruy/aiS4kYfUJiE\n\
361 Pq/JZYCeNQ9GTbJN5M+Fiv1bhw6xeEfl4FAC/A0Uo39fvUSLJH2V+8lT3GxXt8KR\n\
362 Yx8MvotvoKiG1DRvgnwRh1qibX6+htJahPwHDViUuFz0ZeEPWiCwuoMOELmiTsuE\n\
363 VZbQsv08BwCOzIc3M8s63TtwMCUe+MBhqfRjEu6/Gt7miuGGVFXB96ao2O9u1HsR\n\
364 7cUjgVQp6JwGLgIIijjy167Ms+fKZetqA9tzu1C1SAtNYiqmAUoqGG1YEjS7oApo\n\
365 AKmHD+LGCMUPg5d6bhw+NyHjDAFWJEYv3kG3ABEBAAG0NVRlc3QgS2V5IChkbyBu\n\
366 b3QgdXNlKSA8YXV0aGVudGljYXRlLW9ubHlAZXhhbXBsZS5vcmc+iQFOBBMBCAA4\n\
367 FiEEkn7zd/0aG295XkDAKoeRfY/7pJ8FAlg8F68CGwEFCwkIBwIGFQgJCgsCBBYC\n\
368 AwECHgECF4AACgkQKoeRfY/7pJ9+zAf9FVZknDCWCNY42+RIR36fq2l1Gs8KfMsX\n\
369 rNXtfYPtLqioP8fz2LE0LoudSsZMLVygonPG0ZAxdIUHXcFdUqPrEzs4fEyR4xae\n\
370 OSxui2Q/u6+9bi7eilYYzVNRWkuyx2TrRQZEjMXMnuJcWptGbRWs/ipRUZBHWfLl\n\
371 2udLl+0TRIL7hni06xVCHbwE5szcLoyzzvIowGVADXFqeG7nty7uRNZNAD+ZWMH2\n\
372 J0vspZlUSVi7z1VygzDI3U42SMJmVqnRmICsB3QLI8Ns4nxWXO2z8fheSFcrP+LA\n\
373 cY3W6JgnLCzvyuogxnWmd4fzr1iB3E2Hcy/sr0cgQ5wtuseQRhmTlrkBDQRYPBfZ\n\
374 AQgAtKcbBYrIqh3cRTqyZjMxw492RVQoFawYmpr1bQ4HphVGnT4IhJZQ4DAm1JJZ\n\
375 QjzwDQiZMc1wD9Om6UC/g8gUBuFCpLCobwBzjH4an/G3Cfa8zGz5ANAROo5i5T1j\n\
376 vgoFEFdVue/GpAmMNixz+0ItQBh9jiOC6IYk1yyv/OsTzsj6AHnH0XiDpGoTNkca\n\
377 tb6Mu1VcXTMNf620Mxj6c7WE7awxL6MwKIa7XQSgXaO+JnbB+5Szz1wZ1ZhlnDp3\n\
378 KOurlfcXIbZirEaqmRByb+V21Dj3icXOJEj1RUbyVNqBS8rhw17kSxcehw/6ZAMW\n\
379 ehDmhXO98VVUknS0Mf+OJBi2JwARAQABiQE2BBgBCAAgFiEEkn7zd/0aG295XkDA\n\
380 KoeRfY/7pJ8FAlg8F9kCGyAACgkQKoeRfY/7pJ+sawf/eSjxxAglAdolF9lK070u\n\
381 VmMgq4GFPqJ3RqJPUFjwEFFSYLXkiALnMGXDSmOfPqCQ4c+PWwumFhKCz4MXVDD8\n\
382 x6mi9Z+HlEwIMaCnckrSTuQ5OgwO/6vkhz42OcgMZ3WQnWfNVM8jbNP9vX1vroPe\n\
383 HLFaPGy9KJMM0Z/hlCIIeyK/a90zWlT5UMfRoqNQRbY/iiYdmpvf69I9PobGVbo/\n\
384 7ahZTumPWwjiGOztNXeuo5UUaAVVxMQBYKp2w3wil2sHzYfTfYUSMyh+oUFx4Xlz\n\
385 WF3bLzsafRaeuK1h5+JuvIcimvU5zWZtn0hOpiIXpZOoJvvM9r5D4ZRT5UX2blQ8\n\
386 Pw==\n"
387 );
388 }
389}