1use aead::{AeadInPlace, KeyInit};
37use aes_gcm::Aes256Gcm as AesGcm256;
38use chacha20poly1305::XChaCha20Poly1305;
39use oxicrypto_core::{CryptoError, StreamingAead};
40use subtle::ConstantTimeEq as _;
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44enum StreamMode {
45 Encrypting,
47 Decrypting,
49 Finished,
51}
52
53fn build_nonce<const NONCE_FULL: usize>(
59 prefix: &[u8],
60 counter: u32,
61 is_final: bool,
62) -> [u8; NONCE_FULL] {
63 let mut nonce = [0u8; NONCE_FULL];
64 let prefix_len = NONCE_FULL - 5;
65 nonce[..prefix_len].copy_from_slice(prefix);
66 let counter_bytes = counter.to_be_bytes();
67 nonce[prefix_len..prefix_len + 4].copy_from_slice(&counter_bytes);
68 nonce[NONCE_FULL - 1] = if is_final { 0x01 } else { 0x00 };
69 nonce
70}
71
72fn stream_seal_chunk<C, const NONCE_FULL: usize>(
77 cipher: &C,
78 nonce: &[u8; NONCE_FULL],
79 aad: &[u8],
80 pt: &[u8],
81 ct_out: &mut [u8],
82) -> Result<usize, CryptoError>
83where
84 C: AeadInPlace,
85{
86 let tag_len = <<C as aead::AeadCore>::TagSize as aead::generic_array::typenum::Unsigned>::USIZE;
87 let required = pt.len().checked_add(tag_len).ok_or(CryptoError::BadInput)?;
88 if ct_out.len() < required {
89 return Err(CryptoError::BufferTooSmall);
90 }
91 ct_out[..pt.len()].copy_from_slice(pt);
92 let nonce_ga = aead::generic_array::GenericArray::from_slice(nonce.as_ref());
93 let tag = cipher
94 .encrypt_in_place_detached(nonce_ga, aad, &mut ct_out[..pt.len()])
95 .map_err(|_| CryptoError::Internal("STREAM encrypt chunk failed"))?;
96 ct_out[pt.len()..required].copy_from_slice(&tag);
97 Ok(required)
98}
99
100fn stream_open_chunk<C, const NONCE_FULL: usize>(
102 cipher: &C,
103 nonce: &[u8; NONCE_FULL],
104 aad: &[u8],
105 ct_and_tag: &[u8],
106 pt_out: &mut [u8],
107) -> Result<usize, CryptoError>
108where
109 C: AeadInPlace,
110{
111 let tag_len = <<C as aead::AeadCore>::TagSize as aead::generic_array::typenum::Unsigned>::USIZE;
112 if ct_and_tag.len() < tag_len {
113 return Err(CryptoError::BadInput);
114 }
115 let pt_len = ct_and_tag.len() - tag_len;
116 if pt_out.len() < pt_len {
117 return Err(CryptoError::BufferTooSmall);
118 }
119 pt_out[..pt_len].copy_from_slice(&ct_and_tag[..pt_len]);
120 let nonce_ga = aead::generic_array::GenericArray::from_slice(nonce.as_ref());
121 let tag_bytes = &ct_and_tag[pt_len..];
122 let tag = aead::Tag::<C>::clone_from_slice(tag_bytes);
123 cipher
124 .decrypt_in_place_detached(nonce_ga, aad, &mut pt_out[..pt_len], &tag)
125 .map_err(|_| CryptoError::InvalidTag)?;
126 Ok(pt_len)
127}
128
129pub struct Aes256GcmStream {
138 cipher: Option<AesGcm256>,
140 nonce_prefix: [u8; 7],
142 counter: u32,
144 aad: alloc::vec::Vec<u8>,
146 pending: alloc::vec::Vec<u8>,
148 mode: StreamMode,
150}
151
152impl core::fmt::Debug for Aes256GcmStream {
153 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
154 f.debug_struct("Aes256GcmStream")
155 .field("mode", &self.mode)
156 .field("counter", &self.counter)
157 .field("pending_len", &self.pending.len())
158 .finish()
159 }
160}
161
162extern crate alloc;
163use alloc::vec::Vec;
164
165impl Aes256GcmStream {
166 fn current_nonce(&self, is_final: bool) -> [u8; 12] {
168 build_nonce(&self.nonce_prefix, self.counter, is_final)
169 }
170
171 fn advance_counter(&mut self) -> Result<(), CryptoError> {
173 self.counter = self
174 .counter
175 .checked_add(1)
176 .ok_or(CryptoError::Internal("STREAM counter overflow"))?;
177 Ok(())
178 }
179}
180
181impl StreamingAead for Aes256GcmStream {
182 fn init(key: &[u8], nonce: &[u8], aad: &[u8]) -> Result<Self, CryptoError> {
186 if key.len() != 32 {
187 return Err(CryptoError::InvalidKey);
188 }
189 if nonce.len() != 7 {
190 return Err(CryptoError::InvalidNonce);
191 }
192 let cipher = AesGcm256::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
193 let mut nonce_prefix = [0u8; 7];
194 nonce_prefix.copy_from_slice(nonce);
195 Ok(Self {
196 cipher: Some(cipher),
197 nonce_prefix,
198 counter: 0,
199 aad: aad.to_vec(),
200 pending: Vec::new(),
201 mode: StreamMode::Encrypting,
202 })
203 }
204
205 fn encrypt_update(&mut self, chunk: &[u8], out: &mut [u8]) -> Result<usize, CryptoError> {
215 if self.mode != StreamMode::Encrypting {
216 return Err(CryptoError::BadInput);
217 }
218 let cipher = self.cipher.as_ref().ok_or(CryptoError::BadInput)?;
219
220 if self.pending.is_empty() {
221 self.pending = chunk.to_vec();
223 return Ok(0);
224 }
225
226 let nonce = self.current_nonce(false);
228 let prev = core::mem::replace(&mut self.pending, chunk.to_vec());
229 let written = stream_seal_chunk::<_, 12>(cipher, &nonce, &self.aad, &prev, out)?;
230 self.advance_counter()?;
231 Ok(written)
232 }
233
234 fn encrypt_finalize(mut self, out: &mut [u8]) -> Result<[u8; 16], CryptoError> {
239 if self.mode != StreamMode::Encrypting {
240 return Err(CryptoError::BadInput);
241 }
242 self.mode = StreamMode::Finished;
243 let cipher = self.cipher.take().ok_or(CryptoError::BadInput)?;
244
245 let nonce = self.current_nonce(true);
246 let last = self.pending.clone();
247 let written = stream_seal_chunk::<_, 12>(&cipher, &nonce, &self.aad, &last, out)?;
248
249 let tag_start = written - 16;
251 let mut tag = [0u8; 16];
252 tag.copy_from_slice(&out[tag_start..written]);
253 Ok(tag)
254 }
255
256 fn decrypt_update(&mut self, chunk: &[u8], out: &mut [u8]) -> Result<usize, CryptoError> {
261 if self.mode != StreamMode::Decrypting {
262 if self.mode == StreamMode::Encrypting && self.counter == 0 && self.pending.is_empty() {
264 self.mode = StreamMode::Decrypting;
265 } else {
266 return Err(CryptoError::BadInput);
267 }
268 }
269 let cipher = self.cipher.as_ref().ok_or(CryptoError::BadInput)?;
270
271 if self.pending.is_empty() {
272 self.pending = chunk.to_vec();
273 return Ok(0);
274 }
275
276 let nonce = self.current_nonce(false);
278 let prev = core::mem::replace(&mut self.pending, chunk.to_vec());
279 let written = stream_open_chunk::<_, 12>(cipher, &nonce, &self.aad, &prev, out)?;
280 self.advance_counter()?;
281 Ok(written)
282 }
283
284 fn decrypt_finalize(mut self, expected_tag: &[u8]) -> Result<(), CryptoError> {
289 if self.mode != StreamMode::Decrypting {
290 return Err(CryptoError::BadInput);
291 }
292 self.mode = StreamMode::Finished;
293 let cipher = self.cipher.take().ok_or(CryptoError::BadInput)?;
294
295 let pending = self.pending.clone();
299 let tag_len = 16usize;
300 if pending.len() < tag_len {
301 return Err(CryptoError::BadInput);
302 }
303
304 let embedded_tag = &pending[pending.len() - tag_len..];
306 if !bool::from(expected_tag.ct_eq(embedded_tag)) {
307 return Err(CryptoError::InvalidTag);
308 }
309
310 let nonce = self.current_nonce(true);
311 let mut pt = alloc::vec![0u8; pending.len() - tag_len];
312 stream_open_chunk::<_, 12>(&cipher, &nonce, &self.aad, &pending, &mut pt).map(|_| ())
313 }
314
315 fn reset(&mut self) {
317 self.counter = 0;
318 self.pending.clear();
319 self.mode = StreamMode::Encrypting;
320 self.cipher = None;
321 }
322}
323
324pub struct ChaCha20Poly1305Stream {
332 cipher: Option<XChaCha20Poly1305>,
333 nonce_prefix: [u8; 19],
334 counter: u32,
335 aad: Vec<u8>,
336 pending: Vec<u8>,
337 mode: StreamMode,
338}
339
340impl core::fmt::Debug for ChaCha20Poly1305Stream {
341 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
342 f.debug_struct("ChaCha20Poly1305Stream")
343 .field("mode", &self.mode)
344 .field("counter", &self.counter)
345 .field("pending_len", &self.pending.len())
346 .finish()
347 }
348}
349
350impl ChaCha20Poly1305Stream {
351 fn current_nonce(&self, is_final: bool) -> [u8; 24] {
352 build_nonce(&self.nonce_prefix, self.counter, is_final)
353 }
354
355 fn advance_counter(&mut self) -> Result<(), CryptoError> {
356 self.counter = self
357 .counter
358 .checked_add(1)
359 .ok_or(CryptoError::Internal("STREAM counter overflow"))?;
360 Ok(())
361 }
362}
363
364impl StreamingAead for ChaCha20Poly1305Stream {
365 fn init(key: &[u8], nonce: &[u8], aad: &[u8]) -> Result<Self, CryptoError> {
369 if key.len() != 32 {
370 return Err(CryptoError::InvalidKey);
371 }
372 if nonce.len() != 19 {
373 return Err(CryptoError::InvalidNonce);
374 }
375 let cipher = XChaCha20Poly1305::new_from_slice(key).map_err(|_| CryptoError::InvalidKey)?;
376 let mut nonce_prefix = [0u8; 19];
377 nonce_prefix.copy_from_slice(nonce);
378 Ok(Self {
379 cipher: Some(cipher),
380 nonce_prefix,
381 counter: 0,
382 aad: aad.to_vec(),
383 pending: Vec::new(),
384 mode: StreamMode::Encrypting,
385 })
386 }
387
388 fn encrypt_update(&mut self, chunk: &[u8], out: &mut [u8]) -> Result<usize, CryptoError> {
389 if self.mode != StreamMode::Encrypting {
390 return Err(CryptoError::BadInput);
391 }
392 let cipher = self.cipher.as_ref().ok_or(CryptoError::BadInput)?;
393
394 if self.pending.is_empty() {
395 self.pending = chunk.to_vec();
396 return Ok(0);
397 }
398
399 let nonce = self.current_nonce(false);
400 let prev = core::mem::replace(&mut self.pending, chunk.to_vec());
401 let written = stream_seal_chunk::<_, 24>(cipher, &nonce, &self.aad, &prev, out)?;
402 self.advance_counter()?;
403 Ok(written)
404 }
405
406 fn encrypt_finalize(mut self, out: &mut [u8]) -> Result<[u8; 16], CryptoError> {
407 if self.mode != StreamMode::Encrypting {
408 return Err(CryptoError::BadInput);
409 }
410 self.mode = StreamMode::Finished;
411 let cipher = self.cipher.take().ok_or(CryptoError::BadInput)?;
412
413 let nonce = self.current_nonce(true);
414 let last = self.pending.clone();
415 let written = stream_seal_chunk::<_, 24>(&cipher, &nonce, &self.aad, &last, out)?;
416
417 let tag_start = written - 16;
418 let mut tag = [0u8; 16];
419 tag.copy_from_slice(&out[tag_start..written]);
420 Ok(tag)
421 }
422
423 fn decrypt_update(&mut self, chunk: &[u8], out: &mut [u8]) -> Result<usize, CryptoError> {
424 if self.mode != StreamMode::Decrypting {
425 if self.mode == StreamMode::Encrypting && self.counter == 0 && self.pending.is_empty() {
426 self.mode = StreamMode::Decrypting;
427 } else {
428 return Err(CryptoError::BadInput);
429 }
430 }
431 let cipher = self.cipher.as_ref().ok_or(CryptoError::BadInput)?;
432
433 if self.pending.is_empty() {
434 self.pending = chunk.to_vec();
435 return Ok(0);
436 }
437
438 let nonce = self.current_nonce(false);
439 let prev = core::mem::replace(&mut self.pending, chunk.to_vec());
440 let written = stream_open_chunk::<_, 24>(cipher, &nonce, &self.aad, &prev, out)?;
441 self.advance_counter()?;
442 Ok(written)
443 }
444
445 fn decrypt_finalize(mut self, expected_tag: &[u8]) -> Result<(), CryptoError> {
446 if self.mode != StreamMode::Decrypting {
447 return Err(CryptoError::BadInput);
448 }
449 self.mode = StreamMode::Finished;
450 let cipher = self.cipher.take().ok_or(CryptoError::BadInput)?;
451
452 let pending = self.pending.clone();
453 let tag_len = 16usize;
454 if pending.len() < tag_len {
455 return Err(CryptoError::BadInput);
456 }
457
458 let embedded_tag = &pending[pending.len() - tag_len..];
459 if !bool::from(expected_tag.ct_eq(embedded_tag)) {
460 return Err(CryptoError::InvalidTag);
461 }
462
463 let nonce = self.current_nonce(true);
464 let mut pt = alloc::vec![0u8; pending.len() - tag_len];
465 stream_open_chunk::<_, 24>(&cipher, &nonce, &self.aad, &pending, &mut pt).map(|_| ())
466 }
467
468 fn reset(&mut self) {
469 self.counter = 0;
470 self.pending.clear();
471 self.mode = StreamMode::Encrypting;
472 self.cipher = None;
473 }
474}
475
476#[cfg(test)]
479mod tests {
480 use super::*;
481
482 const KEY_256: [u8; 32] = [0x42u8; 32];
483 const NONCE_PREFIX_7: [u8; 7] = [0x24u8; 7];
484 const NONCE_PREFIX_19: [u8; 19] = [0x24u8; 19];
485 const AAD: &[u8] = b"stream aad";
486 const TAG_LEN: usize = 16;
487
488 fn encrypt_chunks_aes256(chunks: &[&[u8]]) -> (Vec<Vec<u8>>, [u8; 16]) {
493 assert!(!chunks.is_empty());
494 let mut enc = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init enc");
495 let mut ct_chunks: Vec<Vec<u8>> = Vec::new();
496
497 let max_chunk_len = chunks.iter().map(|c| c.len()).max().unwrap_or(0);
501 let buf_cap = max_chunk_len + TAG_LEN;
502
503 for chunk in chunks {
504 let mut buf = alloc::vec![0u8; buf_cap];
505 let written = enc.encrypt_update(chunk, &mut buf).expect("encrypt_update");
506 if written > 0 {
507 ct_chunks.push(buf[..written].to_vec());
508 }
509 }
510
511 let last = *chunks.last().unwrap();
513 let mut final_buf = alloc::vec![0u8; last.len() + TAG_LEN];
514 let tag = enc
515 .encrypt_finalize(&mut final_buf)
516 .expect("encrypt_finalize");
517 ct_chunks.push(final_buf[..last.len() + TAG_LEN].to_vec());
518 (ct_chunks, tag)
519 }
520
521 fn decrypt_chunks_aes256(ct_chunks: &[Vec<u8>], final_tag: &[u8; 16]) -> Vec<u8> {
525 let mut dec = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init dec");
526 dec.mode = StreamMode::Decrypting;
527 let mut plaintext: Vec<u8> = Vec::new();
528
529 for ct in ct_chunks {
532 let buf_cap = ct.len(); let mut buf = alloc::vec![0u8; buf_cap];
534 let written = dec.decrypt_update(ct, &mut buf).expect("decrypt_update");
535 plaintext.extend_from_slice(&buf[..written]);
536 }
537
538 dec.decrypt_finalize(final_tag).expect("decrypt_finalize");
540 let last_ct = ct_chunks.last().unwrap();
545 let pt_len = last_ct.len().saturating_sub(TAG_LEN);
546 let mut dec2 = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init dec2");
548 dec2.mode = StreamMode::Decrypting;
549 for ct in ct_chunks {
550 let mut buf = alloc::vec![0u8; ct.len()];
551 let written = dec2.decrypt_update(ct, &mut buf).expect("decrypt_update2");
552 if written > 0 {
553 let _ = written;
555 }
556 }
557 let nonce_counter = (ct_chunks.len() as u32).wrapping_sub(1);
561 let nonce: [u8; 12] = build_nonce(&NONCE_PREFIX_7, nonce_counter, true);
562 let cipher = aes_gcm::Aes256Gcm::new_from_slice(&KEY_256).expect("cipher");
563 let nonce_ga = aead::generic_array::GenericArray::from_slice(nonce.as_ref());
564 let mut last_pt = last_ct[..pt_len].to_vec();
565 let tag_bytes = &last_ct[pt_len..];
566 let tag_ga = aead::Tag::<aes_gcm::Aes256Gcm>::clone_from_slice(tag_bytes);
567 cipher
568 .decrypt_in_place_detached(nonce_ga, AAD, &mut last_pt, &tag_ga)
569 .expect("last chunk decrypt");
570 plaintext.extend_from_slice(&last_pt);
571 plaintext
572 }
573
574 #[test]
575 fn aes256gcm_stream_three_chunks() {
576 let chunks: &[&[u8]] = &[b"chunk-one---", b"chunk-two---", b"chunk-three"];
577 let expected: Vec<u8> = chunks.iter().flat_map(|c| c.iter().copied()).collect();
578
579 let (ct_chunks, final_tag) = encrypt_chunks_aes256(chunks);
580 let recovered = decrypt_chunks_aes256(&ct_chunks, &final_tag);
581 assert_eq!(recovered, expected, "three-chunk round-trip failed");
582 }
583
584 #[test]
585 fn aes256gcm_stream_single_chunk() {
586 let chunk = b"only one chunk";
587
588 let mut enc = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init");
589 let mut buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
591 let written = enc.encrypt_update(chunk, &mut buf).expect("update");
592 assert_eq!(written, 0);
593
594 let mut final_buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
596 let tag = enc.encrypt_finalize(&mut final_buf).expect("finalize");
597 assert_eq!(final_buf.len(), chunk.len() + TAG_LEN);
598
599 let mut dec = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init dec");
601 dec.mode = StreamMode::Decrypting;
602 let mut pt_buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
603 let w = dec
604 .decrypt_update(&final_buf, &mut pt_buf)
605 .expect("decrypt_update");
606 assert_eq!(w, 0, "first update must buffer, not emit");
607 dec.decrypt_finalize(&tag).expect("decrypt_finalize");
609 }
610
611 #[test]
612 fn aes256gcm_stream_tamper_middle_chunk_fails() {
613 let chunks: &[&[u8]] = &[b"chunk-A-data---", b"chunk-B-tamper-", b"chunk-C-final--"];
615 let (mut ct_chunks, final_tag) = encrypt_chunks_aes256(chunks);
616
617 ct_chunks[1][0] ^= 0xFF; let mut dec = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init dec");
629 dec.mode = StreamMode::Decrypting;
630
631 let mut pt_buf = alloc::vec![0u8; ct_chunks[0].len()];
633 let w0 = dec
634 .decrypt_update(&ct_chunks[0], &mut pt_buf)
635 .expect("update0");
636 assert_eq!(w0, 0);
637
638 let mut pt_buf1 = alloc::vec![0u8; ct_chunks[0].len()];
641 let w1 = dec
642 .decrypt_update(&ct_chunks[1], &mut pt_buf1)
643 .expect("update1");
644 assert!(w1 > 0, "should have emitted decrypted chunk-A");
645
646 let mut pt_buf2 = alloc::vec![0u8; ct_chunks[1].len()];
648 let result = dec.decrypt_update(&ct_chunks[2], &mut pt_buf2);
649 assert!(
650 matches!(result, Err(CryptoError::InvalidTag)),
651 "expected InvalidTag on tampered chunk, got: {:?}",
652 result
653 );
654 let _ = final_tag;
656 }
657
658 #[test]
659 fn aes256gcm_stream_tamper_final_tag_fails() {
660 let chunks: &[&[u8]] = &[b"single"];
661 let (ct_chunks, mut final_tag) = encrypt_chunks_aes256(chunks);
662
663 final_tag[0] ^= 0xFF;
665
666 let mut dec = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init dec");
667 dec.mode = StreamMode::Decrypting;
668 let mut pt_buf = alloc::vec![0u8; ct_chunks[0].len()];
669 dec.decrypt_update(&ct_chunks[0], &mut pt_buf)
670 .expect("update");
671 let result = dec.decrypt_finalize(&final_tag);
672 assert!(
673 matches!(result, Err(CryptoError::InvalidTag)),
674 "expected InvalidTag, got: {:?}",
675 result
676 );
677 }
678
679 #[test]
680 fn aes256gcm_stream_reject_update_after_finalize() {
681 let chunk = b"data";
685 let mut enc = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init");
686 let mut buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
687 enc.encrypt_update(chunk, &mut buf).expect("update");
688 let mut final_buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
689 let _tag = enc.encrypt_finalize(&mut final_buf).expect("finalize");
690 }
692
693 #[test]
694 fn chacha20poly1305_stream_single_chunk_round_trip() {
695 let chunk = b"xchacha20 stream chunk";
696
697 let mut enc = ChaCha20Poly1305Stream::init(&KEY_256, &NONCE_PREFIX_19, AAD).expect("init");
698 let mut buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
699 let w = enc.encrypt_update(chunk, &mut buf).expect("update");
700 assert_eq!(w, 0);
701
702 let mut final_buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
703 let tag = enc.encrypt_finalize(&mut final_buf).expect("finalize");
704
705 let mut dec =
706 ChaCha20Poly1305Stream::init(&KEY_256, &NONCE_PREFIX_19, AAD).expect("init dec");
707 dec.mode = StreamMode::Decrypting;
708 let mut pt_buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
709 let _w = dec
710 .decrypt_update(&final_buf, &mut pt_buf)
711 .expect("decrypt_update");
712 dec.decrypt_finalize(&tag).expect("decrypt_finalize");
713 }
714
715 #[test]
716 fn aes256gcm_stream_wrong_nonce_prefix_length() {
717 let result = Aes256GcmStream::init(&KEY_256, &[0u8; 12], AAD);
718 assert!(
719 matches!(result, Err(CryptoError::InvalidNonce)),
720 "expected InvalidNonce, got: {:?}",
721 result.as_ref().map(|_| ()).map_err(|e| format!("{e:?}"))
722 );
723 }
724
725 #[test]
726 fn chacha20poly1305_stream_wrong_nonce_prefix_length() {
727 let result = ChaCha20Poly1305Stream::init(&KEY_256, &[0u8; 12], AAD);
728 assert!(
729 matches!(result, Err(CryptoError::InvalidNonce)),
730 "expected InvalidNonce, got: {:?}",
731 result.as_ref().map(|_| ()).map_err(|e| format!("{e:?}"))
732 );
733 }
734
735 #[test]
736 fn aes256gcm_stream_reset_clears_state() {
737 let chunk = b"some data";
741 let mut enc = Aes256GcmStream::init(&KEY_256, &NONCE_PREFIX_7, AAD).expect("init");
742 assert_eq!(enc.counter, 0, "initial counter");
743 assert!(enc.pending.is_empty(), "initial pending");
744 assert_eq!(enc.mode, StreamMode::Encrypting, "initial mode");
745
746 let mut buf = alloc::vec![0u8; chunk.len() + TAG_LEN];
747 let w = enc.encrypt_update(chunk, &mut buf).expect("encrypt_update");
748 assert_eq!(w, 0, "first update buffers; emits nothing");
749 assert!(!enc.pending.is_empty(), "pending filled after update");
750
751 enc.reset();
753 assert_eq!(enc.counter, 0, "counter after reset");
754 assert!(enc.pending.is_empty(), "pending after reset");
755 assert_eq!(enc.mode, StreamMode::Encrypting, "mode after reset");
756 assert!(enc.cipher.is_none(), "cipher cleared after reset");
757 }
758}