use crate::{protocol::Protocol, runtime::Runtime};
use alloc::vec::Vec;
use core::marker::PhantomData;
pub struct I2cpPayloadBuilder<'a, R: Runtime> {
dst_port: u16,
payload: &'a [u8],
protocol: Option<Protocol>,
src_port: u16,
_runtime: PhantomData<R>,
}
impl<'a, R: Runtime> I2cpPayloadBuilder<'a, R> {
pub fn new(payload: &'a [u8]) -> Self {
Self {
dst_port: 0u16,
payload,
protocol: None,
src_port: 0u16,
_runtime: Default::default(),
}
}
#[allow(unused)]
pub fn with_source_port(mut self, src_port: u16) -> Self {
self.src_port = src_port;
self
}
#[allow(unused)]
pub fn with_destination_port(mut self, dst_port: u16) -> Self {
self.dst_port = dst_port;
self
}
pub fn with_protocol(mut self, protocol: Protocol) -> Self {
self.protocol = Some(protocol);
self
}
pub fn build(mut self) -> Option<Vec<u8>> {
R::gzip_compress(self.payload).map(|mut compressed| {
compressed[4..6].copy_from_slice(&self.src_port.to_be_bytes());
compressed[6..8].copy_from_slice(&self.dst_port.to_be_bytes());
compressed[9] = self.protocol.take().expect("protocol to exist").as_u8();
compressed
})
}
}
pub struct I2cpPayload {
pub dst_port: u16,
pub payload: Vec<u8>,
pub protocol: Protocol,
pub src_port: u16,
}
impl I2cpPayload {
pub fn decompress<R: Runtime>(payload: impl AsRef<[u8]>) -> Option<Self> {
if payload.as_ref().len() < 10 {
return None;
}
let src_port = TryInto::<[u8; 2]>::try_into(&payload.as_ref()[4..6]).expect("to succeed");
let dst_port = TryInto::<[u8; 2]>::try_into(&payload.as_ref()[6..8]).expect("to succeed");
let protocol = payload.as_ref()[9];
Some(Self {
src_port: u16::from_be_bytes(src_port),
dst_port: u16::from_be_bytes(dst_port),
protocol: Protocol::from_u8(protocol)?,
payload: R::gzip_decompress(&payload)?,
})
}
}
#[derive(Debug, PartialEq, Eq)]
pub struct I2cpParameters {
pub dst_port: u16,
pub protocol: Protocol,
pub src_port: u16,
}
impl I2cpParameters {
pub fn new(payload: impl AsRef<[u8]>) -> Option<I2cpParameters> {
if payload.as_ref().len() < 10 {
return None;
}
let src_port = TryInto::<[u8; 2]>::try_into(&payload.as_ref()[4..6]).expect("to succeed");
let dst_port = TryInto::<[u8; 2]>::try_into(&payload.as_ref()[6..8]).expect("to succeed");
let protocol = Protocol::from_u8(payload.as_ref()[9])?;
Some(Self {
dst_port: u16::from_be_bytes(dst_port),
protocol,
src_port: u16::from_be_bytes(src_port),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::{mock::MockRuntime, Runtime};
#[test]
fn compress_and_decompress() {
let payload = "hello, world".as_bytes();
let compressed = I2cpPayloadBuilder::<MockRuntime>::new(&payload)
.with_source_port(13)
.with_protocol(Protocol::Streaming)
.build()
.unwrap();
let decompressed = MockRuntime::gzip_decompress(&compressed).unwrap();
assert_eq!(decompressed, "hello, world".as_bytes());
let Some(I2cpPayload {
dst_port,
payload,
protocol,
src_port,
}) = I2cpPayload::decompress::<MockRuntime>(&compressed)
else {
panic!("invalid data");
};
assert_eq!(dst_port, 0u16);
assert_eq!(src_port, 13u16);
assert_eq!(protocol, Protocol::Streaming);
assert_eq!(payload, "hello, world".as_bytes());
}
#[test]
fn invalid_protocol() {
let payload = "hello, world".as_bytes();
let mut compressed = I2cpPayloadBuilder::<MockRuntime>::new(&payload)
.with_source_port(13)
.with_protocol(Protocol::Streaming)
.build()
.unwrap();
compressed[9] = 0xaa;
assert!(I2cpPayload::decompress::<MockRuntime>(&compressed).is_none());
assert!(I2cpParameters::new(&compressed).is_none());
}
#[test]
fn extract_parameters() {
let payload = "hello, world".as_bytes();
let compressed = I2cpPayloadBuilder::<MockRuntime>::new(&payload)
.with_source_port(13)
.with_destination_port(37)
.with_protocol(Protocol::Anonymous)
.build()
.unwrap();
assert_eq!(
I2cpParameters::new(&compressed),
Some(I2cpParameters {
dst_port: 37,
src_port: 13,
protocol: Protocol::Anonymous
})
);
}
}