use log::warn;
use std::convert::Infallible;
use std::fmt::{Display, Formatter};
use std::io::{Cursor, Error, ErrorKind, Read};
use std::str::FromStr;
use time::OffsetDateTime;
#[derive(
Default,
Debug,
Copy,
Clone,
Ord,
PartialOrd,
Eq,
PartialEq,
serde::Serialize,
serde::Deserialize,
)]
pub enum ChiaProtocolVersion {
Chia0_0_34 = 34, Chia0_0_35 = 35, #[default]
Chia0_0_36 = 36, }
impl Display for ChiaProtocolVersion {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
ChiaProtocolVersion::Chia0_0_34 => f.write_str("0.0.34"),
ChiaProtocolVersion::Chia0_0_35 => f.write_str("0.0.35"),
ChiaProtocolVersion::Chia0_0_36 => f.write_str("0.0.36"),
}
}
}
impl FromStr for ChiaProtocolVersion {
type Err = Infallible;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"0.0.34" => ChiaProtocolVersion::Chia0_0_34,
"0.0.35" => ChiaProtocolVersion::Chia0_0_35,
"0.0.36" => ChiaProtocolVersion::Chia0_0_36,
_ => {
warn!(
"Failed to detect Protocol Version: {s}, defaulting to {}",
ChiaProtocolVersion::default()
);
ChiaProtocolVersion::default()
}
})
}
}
pub trait ChiaSerialize {
fn to_bytes(&self, version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized;
fn from_bytes<T: AsRef<[u8]>>(
bytes: &mut Cursor<T>,
version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized;
}
impl ChiaSerialize for OffsetDateTime {
fn to_bytes(&self, version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
(self.unix_timestamp() as u64).to_bytes(version)
}
fn from_bytes<T: AsRef<[u8]>>(
bytes: &mut Cursor<T>,
version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let timestamp: u64 = u64::from_bytes(bytes, version)?;
OffsetDateTime::from_unix_timestamp(timestamp as i64)
.map_err(|e| Error::new(ErrorKind::InvalidData, e))
}
}
impl ChiaSerialize for String {
fn to_bytes(&self, _version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
let mut bytes: Vec<u8> = Vec::new();
#[allow(clippy::cast_possible_truncation)]
bytes.extend((self.len() as u32).to_be_bytes());
bytes.extend(self.as_bytes());
bytes
}
fn from_bytes<T: AsRef<[u8]>>(
bytes: &mut Cursor<T>,
_version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let mut u32_len_ary: [u8; 4] = [0; 4];
bytes.read_exact(&mut u32_len_ary)?;
let vec_len = u32::from_be_bytes(u32_len_ary) as usize;
if vec_len > 2048 {
warn!("Serializing Large Vec: {vec_len}");
}
let mut buf = vec![0u8; vec_len];
bytes.read_exact(&mut buf[0..vec_len])?;
String::from_utf8(buf).map_err(|e| {
Error::new(
ErrorKind::InvalidInput,
format!("Failed to parse Utf-8 String from Bytes: {e:?}"),
)
})
}
}
impl ChiaSerialize for bool {
fn to_bytes(&self, _version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
vec![u8::from(*self)]
}
fn from_bytes<T: AsRef<[u8]>>(
bytes: &mut Cursor<T>,
_version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let mut bool_buf: [u8; 1] = [0; 1];
bytes.read_exact(&mut bool_buf)?;
match bool_buf[0] {
0 => Ok(false),
1 => Ok(true),
_ => Err(Error::new(
ErrorKind::InvalidInput,
format!("Failed to parse bool, invalid value: {:?}", bool_buf[0]),
)),
}
}
}
impl<T> ChiaSerialize for Option<T>
where
T: ChiaSerialize,
{
fn to_bytes(&self, version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
let mut bytes: Vec<u8> = Vec::new();
match &self {
Some(t) => {
bytes.push(1u8);
bytes.extend(t.to_bytes(version));
}
None => {
bytes.push(0u8);
}
}
bytes
}
fn from_bytes<B: AsRef<[u8]>>(
bytes: &mut Cursor<B>,
version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let mut bool_buf: [u8; 1] = [0; 1];
bytes.read_exact(&mut bool_buf)?;
if bool_buf[0] > 0 {
Ok(Some(T::from_bytes(bytes, version)?))
} else {
Ok(None)
}
}
}
impl<T, U> ChiaSerialize for (T, U)
where
T: ChiaSerialize,
U: ChiaSerialize,
{
fn to_bytes(&self, version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
let mut bytes: Vec<u8> = Vec::new();
bytes.extend(self.0.to_bytes(version));
bytes.extend(self.1.to_bytes(version));
bytes
}
fn from_bytes<B: AsRef<[u8]>>(
bytes: &mut Cursor<B>,
version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let t = T::from_bytes(bytes, version)?;
let u = U::from_bytes(bytes, version)?;
Ok((t, u))
}
}
impl<T, U, V> ChiaSerialize for (T, U, V)
where
T: ChiaSerialize,
U: ChiaSerialize,
V: ChiaSerialize,
{
fn to_bytes(&self, version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
let mut bytes: Vec<u8> = Vec::new();
bytes.extend(self.0.to_bytes(version));
bytes.extend(self.1.to_bytes(version));
bytes.extend(self.2.to_bytes(version));
bytes
}
fn from_bytes<B: AsRef<[u8]>>(
bytes: &mut Cursor<B>,
version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let t = T::from_bytes(bytes, version)?;
let u = U::from_bytes(bytes, version)?;
let v = V::from_bytes(bytes, version)?;
Ok((t, u, v))
}
}
impl<T> ChiaSerialize for Vec<T>
where
T: ChiaSerialize,
{
fn to_bytes(&self, version: ChiaProtocolVersion) -> Vec<u8>
where
Self: Sized,
{
let mut bytes: Vec<u8> = Vec::new();
#[allow(clippy::cast_possible_truncation)]
bytes.extend((self.len() as u32).to_be_bytes());
for e in self {
bytes.extend(e.to_bytes(version));
}
bytes
}
fn from_bytes<B: AsRef<[u8]>>(
bytes: &mut Cursor<B>,
version: ChiaProtocolVersion,
) -> Result<Self, Error>
where
Self: Sized,
{
let mut u32_buf: [u8; 4] = [0; 4];
bytes.read_exact(&mut u32_buf)?;
let buf: Vec<T> = Vec::new();
let vec_len = u32::from_be_bytes(u32_buf);
if vec_len > 2048 {
warn!("Serializing Large Vec: {vec_len}");
}
(0..vec_len).try_fold(buf, |mut vec, _| {
vec.push(T::from_bytes(bytes, version)?);
Ok(vec)
})
}
}
macro_rules! impl_primitives {
($($name: ident, $size:expr);*) => {
$(
impl ChiaSerialize for $name {
fn to_bytes(&self, _version: ChiaProtocolVersion) -> Vec<u8> {
self.to_be_bytes().to_vec()
}
fn from_bytes<T: AsRef<[u8]>>(bytes: &mut Cursor<T>, _version: ChiaProtocolVersion) -> Result<Self, std::io::Error> where Self: Sized,
{
let remaining = bytes.get_ref().as_ref().len().saturating_sub(bytes.position() as usize);
if remaining < $size {
Err(Error::new(std::io::ErrorKind::InvalidInput, format!("Failed to Parse $name, expected length $size, found {}", remaining)))
} else {
let mut buffer: [u8; $size] = [0; $size];
bytes.read_exact(&mut buffer)?;
Ok($name::from_be_bytes(buffer))
}
}
}
)*
};
()=>{};
}
impl_primitives!(
i8, 1;
i16, 2;
i32, 4;
i64, 8;
i128, 16;
u8, 1;
u16, 2;
u32, 4;
u64, 8;
u128, 16;
f32, 4;
f64, 8
);