#![allow(dead_code)]
use crate::e2e::error::{E2eError, Result};
use crate::e2e::{MAX_CHUNKS, MAX_PLAINTEXT_PER_CHUNK};
pub fn split_plaintext(plaintext: &str) -> Result<Vec<Vec<u8>>> {
if plaintext.is_empty() {
return Err(E2eError::Wire("empty plaintext".into()));
}
let bytes = plaintext.as_bytes();
let mut chunks: Vec<Vec<u8>> = Vec::new();
let mut cursor = 0usize;
while cursor < bytes.len() {
let mut end = (cursor + MAX_PLAINTEXT_PER_CHUNK).min(bytes.len());
while end > cursor && !plaintext.is_char_boundary(end) {
end -= 1;
}
if end == cursor {
return Err(E2eError::Wire(
"cannot split: single UTF-8 char exceeds chunk budget".into(),
));
}
chunks.push(bytes[cursor..end].to_vec());
cursor = end;
if chunks.len() > usize::from(MAX_CHUNKS) {
return Err(E2eError::ChunkLimit(
u8::try_from(chunks.len()).unwrap_or(u8::MAX),
));
}
}
let total = chunks.len();
if total > usize::from(MAX_CHUNKS) {
return Err(E2eError::ChunkLimit(u8::try_from(total).unwrap_or(u8::MAX)));
}
Ok(chunks)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn short_message_is_one_chunk() {
let chunks = split_plaintext("hello").unwrap();
assert_eq!(chunks.len(), 1);
assert_eq!(&chunks[0], b"hello");
}
#[test]
fn empty_plaintext_is_rejected() {
match split_plaintext("") {
Err(E2eError::Wire(msg)) => assert!(
msg.contains("empty plaintext"),
"expected 'empty plaintext', got: {msg}"
),
other => panic!("expected Err(Wire(empty plaintext)), got {other:?}"),
}
}
#[test]
fn long_message_is_multi_chunk() {
let s = "x".repeat(500);
let chunks = split_plaintext(&s).unwrap();
assert!(chunks.len() >= 2);
let rejoined: Vec<u8> = chunks.iter().flatten().copied().collect();
assert_eq!(rejoined, s.as_bytes());
}
#[test]
fn boundary_respects_utf8_chars() {
let prefix = "x".repeat(MAX_PLAINTEXT_PER_CHUNK - 2);
let s = format!("{prefix}💩{prefix}");
let chunks = split_plaintext(&s).unwrap();
for c in &chunks {
assert!(std::str::from_utf8(c).is_ok());
}
}
#[test]
fn message_beyond_max_chunks_errors() {
let s = "a".repeat(MAX_PLAINTEXT_PER_CHUNK * 17);
assert!(split_plaintext(&s).is_err());
}
}