use chacha20poly1305::{
aead::{Aead, AeadCore, KeyInit, OsRng},
ChaCha20Poly1305, Key, Nonce,
};
use peasub::ID_SIZE;
pub const MESSAGE_SIZE: usize = 512;
const NONCE: usize = 12;
const TAG: usize = 16;
const PAYLOAD: usize = MESSAGE_SIZE - ID_SIZE;
const INNER: usize = PAYLOAD - NONCE - TAG;
const LEN_HDR: usize = 2;
pub const MAX_POST: usize = INNER - LEN_HDR;
#[derive(Clone, Debug)]
pub struct Post {
pub board: String,
pub nick: String,
pub ts: u64,
pub text: String,
}
impl Post {
fn encode(&self) -> Option<Vec<u8>> {
let (b, n, t) = (
self.board.as_bytes(),
self.nick.as_bytes(),
self.text.as_bytes(),
);
if b.len() > u8::MAX as usize || n.len() > u8::MAX as usize || t.len() > u16::MAX as usize {
return None;
}
let mut out = Vec::new();
out.push(b.len() as u8);
out.extend_from_slice(b);
out.push(n.len() as u8);
out.extend_from_slice(n);
out.extend_from_slice(&self.ts.to_be_bytes());
out.extend_from_slice(&(t.len() as u16).to_be_bytes());
out.extend_from_slice(t);
(out.len() <= MAX_POST).then_some(out)
}
fn decode(buf: &[u8]) -> Option<Post> {
let mut c = 0;
let take = |c: &mut usize, n: usize| -> Option<&[u8]> {
let s = buf.get(*c..*c + n)?;
*c += n;
Some(s)
};
let bl = *buf.get(c)? as usize;
c += 1;
let board = std::str::from_utf8(take(&mut c, bl)?).ok()?.to_string();
let nl = *buf.get(c)? as usize;
c += 1;
let nick = std::str::from_utf8(take(&mut c, nl)?).ok()?.to_string();
let ts = u64::from_be_bytes(take(&mut c, 8)?.try_into().ok()?);
let tl = u16::from_be_bytes(take(&mut c, 2)?.try_into().ok()?) as usize;
let text = std::str::from_utf8(take(&mut c, tl)?).ok()?.to_string();
Some(Post {
board,
nick,
ts,
text,
})
}
}
pub fn seal(cipher: &ChaCha20Poly1305, post: &Post) -> Option<Vec<u8>> {
let body = post.encode()?;
let mut inner = vec![0u8; INNER];
inner[..LEN_HDR].copy_from_slice(&(body.len() as u16).to_be_bytes());
inner[LEN_HDR..LEN_HDR + body.len()].copy_from_slice(&body);
let nonce = ChaCha20Poly1305::generate_nonce(&mut OsRng);
let ct = cipher.encrypt(&nonce, inner.as_ref()).ok()?;
let mut out = Vec::with_capacity(PAYLOAD);
out.extend_from_slice(&nonce);
out.extend_from_slice(&ct);
debug_assert_eq!(out.len(), PAYLOAD);
Some(out)
}
pub fn open(cipher: &ChaCha20Poly1305, frame: &[u8]) -> Option<Post> {
let payload = frame.get(ID_SIZE..)?;
let nonce = Nonce::from_slice(payload.get(..NONCE)?);
let inner = cipher.decrypt(nonce, payload.get(NONCE..)?).ok()?;
let len = u16::from_be_bytes(inner.get(..LEN_HDR)?.try_into().ok()?) as usize;
Post::decode(inner.get(LEN_HDR..LEN_HDR + len)?)
}
pub fn board_key() -> ChaCha20Poly1305 {
ChaCha20Poly1305::new(Key::from_slice(&[0x42u8; 32]))
}