diff --git a/Cargo.lock b/Cargo.lock index aecdda4..146fa11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "byteorder" +version = "1.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" + [[package]] name = "bytes" version = "0.5.6" @@ -594,8 +600,10 @@ name = "yuurei" version = "0.1.0" dependencies = [ "async-trait", + "byteorder", "chrono", "cryptohelpers", + "libc", "once_cell", "rustc_version", "tokio", diff --git a/Cargo.toml b/Cargo.toml index 63b101e..a180a78 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,8 @@ chrono = "0.4.15" uuid = {version = "0.8", features=["v4"]} once_cell = "1.4.1" crypto = {package= "cryptohelpers", version = "0.1", features= ["async", "sha256"]} +libc = "0.2.76" +byteorder = "1.3.4" [build-dependencies] rustc_version = "0.2" diff --git a/src/bytes.rs b/src/bytes.rs index 90177d4..d3de2ae 100644 --- a/src/bytes.rs +++ b/src/bytes.rs @@ -1,4 +1,8 @@ - +use std::{ + fmt, + error, +}; +use libc::c_void; pub unsafe fn refer<'a, T>(src: &'a T) -> &'a [u8] where T: ?Sized @@ -24,3 +28,79 @@ pub unsafe fn derefer_mut<'a, T>(src: &'a mut [u8]) -> &'a mut T &mut *(&mut src[0] as *mut u8 as *mut T) } +/// Represents a type that can be converted from bytes to itself +pub trait FromBytes: Sized +{ + type Error; + fn from_bytes>(bytes: T) -> Result; +} + +/// Represents a type that can be converted into bytes +pub trait IntoBytes:Sized +{ + fn into_bytes(self) -> Box<[u8]>; +} + +impl>> IntoBytes for T +{ + #[inline] fn into_bytes(self) -> Box<[u8]> + { + self.into() + } +} + +impl FromBytes for Vec +{ + type Error = !; + #[inline] fn from_bytes>(bytes: T) -> Result + { + Ok(Vec::from(bytes.as_ref())) + } +} +impl FromBytes for Box<[u8]> +{ + type Error = !; + #[inline] fn from_bytes>(bytes: T) -> Result + { + Ok(Vec::from(bytes.as_ref()).into_boxed_slice()) + } +} + +/// The error used when a `FromBytes` conversion fails because the buffer was not the correct size +#[derive(Debug)] +pub struct SizeError; +impl error::Error for SizeError{} +impl fmt::Display for SizeError +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result + { + write!(f,"buffer was not the correct size") + } +} + + +/// Copy slice of bytes only +/// +/// # Notes +/// `dst` and `src` must not overlap. See [move_slice]. +pub fn copy_slice(dst: &mut [u8], src: &[u8]) -> usize +{ + let sz = std::cmp::min(dst.len(),src.len()); + unsafe { + libc::memcpy(&mut dst[0] as *mut u8 as *mut c_void, &src[0] as *const u8 as *const c_void, sz); + } + sz +} + +/// Move slice of bytes only +/// +/// # Notes +/// `dst` and `src` can overlap. +pub fn move_slice(dst: &mut [u8], src: &[u8]) -> usize +{ + let sz = std::cmp::min(dst.len(),src.len()); + unsafe { + libc::memmove(&mut dst[0] as *mut u8 as *mut c_void, &src[0] as *const u8 as *const c_void, sz); + } + sz +} diff --git a/src/identity.rs b/src/identity.rs index 0a85b93..9f05cc7 100644 --- a/src/identity.rs +++ b/src/identity.rs @@ -9,6 +9,29 @@ use uuid::Uuid; #[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct PostID(Uuid); +impl bytes::IntoBytes for PostID +{ + fn into_bytes(self) -> Box<[u8]> + { + Box::from(*self.0.as_bytes()) + } +} + +impl bytes::FromBytes for PostID +{ + type Error = bytes::SizeError; + fn from_bytes>(bytes: T) -> Result { + let bytes = bytes.as_ref(); + if bytes.len() < 16 { + Err(bytes::SizeError) + } else { + let by = [0u8; 16]; + assert_eq!(bytes::copy_slice(&mut by[..], bytes), 16); + Ok(Self(Uuid::from_bytes(by))) + } + } +} + impl PostID { /// Generate a new `PostID`. diff --git a/src/main.rs b/src/main.rs index f98bcdc..cb8aad6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,6 +5,13 @@ use async_trait::async_trait; +use self::{ + bytes::{ + FromBytes as _, + IntoBytes as _, + }, +}; + mod bytes; mod suspend; diff --git a/src/post/mod.rs b/src/post/mod.rs index 5ad4b12..dddcf99 100644 --- a/src/post/mod.rs +++ b/src/post/mod.rs @@ -20,6 +20,54 @@ where T: TimeZone, pub last_edit: DateTime, } +impl bytes::IntoBytes for PostTimestamp +where T: TimeZone, +{ + fn into_bytes(self) -> Box<[u8]> { + const ESIZE: usize = std::mem::size_of::(); + let mut output =[0u8; ESIZE * 3]; + use byteorder::{ + LittleEndian, + WriteBytesExt, + }; + //FUCK this. why is ther eno from_timestamp_nanos()!??? wtf is the point of the nanos versions then?????? fuck + output[..ESIZE] .as_mut().write_i64::(self.opened.timestamp()).unwrap(); + output[ESIZE..ESIZE*2] .as_mut().write_i64::(self.closed.map(|x| x.timestamp()).unwrap_or(i64::MIN)).unwrap(); + output[ESIZE*2..ESIZE*3].as_mut().write_i64::(self.last_edit.timestamp()).unwrap(); + output.into() + } +} + +impl bytes::FromBytes for PostTimestamp +{ + type Error = bytes::SizeError; + fn from_bytes>(bytes: U) -> Result { + const ESIZE: usize = std::mem::size_of::(); + let bytes=bytes.as_ref(); + if bytes.len() < ESIZE * 3 { + return Err(bytes::SizeError); + } + + use byteorder::{ + LittleEndian, + ReadBytesExt, + }; + + let opened = bytes.read_i64::().unwrap(); + let closed = match bytes[ESIZE..].as_ref().read_i64::().unwrap() { + i64::MIN => None, + x => Some(x), + }; + let last_edit = bytes[ESIZE*2..].as_ref().read_i64::().unwrap(); + + Ok(Self{ + opened: DateTime::from_utc(NaiveDateTime::from_timestamp(opened, 0), Utc), + closed: closed.map(|closed| DateTime::from_utc(NaiveDateTime::from_timestamp(closed, 0), Utc)), + last_edit: DateTime::from_utc(NaiveDateTime::from_timestamp(last_edit, 0), Utc), + }) + } +} + impl Hash for PostTimestamp { fn hash(&self, state: &mut H) { self.opened.hash(state); @@ -62,3 +110,38 @@ pub struct Static /// Hash of the rest of the post data. . . . hash: crypto::sha256::Sha256Hash, } + +use suspend::{ + Suspendable, + SuspendStream, +}; + +#[async_trait] +impl Suspendable for Static +{ + async fn suspend(self, into: &mut S) -> Result<(), suspend::Error> + { + let mut output = suspend::Object::new(); + output.insert_value("id", self.id); + + output.insert_bool("Some(user-name)", self.user.name.is_some()); + output.insert_string("user-name", self.user.name.unwrap_or_default()); + output.insert_bool("Some(user-email)", self.user.email.is_some()); + output.insert_string("user-email", self.user.email.unwrap_or_default()); + //TODO: Tripcode + output.insert_bool("Some(title)", self.title.is_some()); + output.insert_string("title", self.title.unwrap_or_default()); + + output.insert_string("body", self.karada); + output.insert_value("timestamp", self.timestamp); + + output.insert_bytes("hash", self.hash); + + into.set_object(output).await + } + async fn load(from: &mut S) -> Result + { + fuck + todo!() //ehhhh... this is so dumb... + } +} diff --git a/src/state/local.rs b/src/state/local.rs index 1dc51a1..2ddd7bc 100644 --- a/src/state/local.rs +++ b/src/state/local.rs @@ -1,5 +1,8 @@ //! Handles updating posts use super::*; +use std::{ + sync::Arc, +}; use tokio::{ sync::{ RwLock, @@ -60,9 +63,9 @@ const _: [u8;(MAX_SINGLE_DELTA_SIZE < (!0u8 as usize)) as usize] = [0]; pub struct Karada { /// The post body so far as a vector of `char`s. - scape: RwLock>, + scape: Arc>>, /// All applied deltas so far. Last applied one is at the end. - deltas: RwLock>, + deltas: Arc>>, /// the latest render of the whole body string. Updated whenever a delta(s) are applied atomically. current_body: watch::Receiver, diff --git a/src/suspend.rs b/src/suspend.rs index bd0e7c1..ed8c5ac 100644 --- a/src/suspend.rs +++ b/src/suspend.rs @@ -4,6 +4,8 @@ use std::{ marker::{ PhantomData, Unpin, + Send, + Sync, }, io, collections::HashMap, @@ -22,10 +24,14 @@ use tokio::{ AsyncWrite, }, }; +use bytes::{ + IntoBytes, + FromBytes, +}; /// Represents an opaque bytes stream of serialised data to insert into `SuspendStream`. #[derive(Debug, Clone, PartialEq, Eq)] -pub struct SuspendObject +pub struct Object { data: Vec, data_instances: HashMap, Range>, @@ -48,9 +54,9 @@ where I: IntoIterator, max } -impl SuspendObject +impl Object { - /// Create a new empty `SuspendObject`. + /// Create a new empty `Object`. #[inline] pub fn new() -> Self { Self{data: Vec::new(), data_instances: HashMap::new()} @@ -68,6 +74,45 @@ impl SuspendObject true } + /// Insert a boolean + pub fn insert_bool(&mut self, name: impl Into>, value: bool) + { + self.insert_bytes(name, if value {&[1]} else {&[0]}) + } + /// Get a boolean + pub fn get_bool(&self, name: impl Borrow) -> Option + { + self.get_bytes(name).map(|x| if x[0]==0 {false} else {true}) + } + + /// Insert a UTF-8 string + pub fn insert_string(&mut self, name: impl Into>, stri: impl AsRef) + { + self.insert_bytes(name, stri.as_ref().as_bytes()) + } + + /// Try to get a UTF-8 string, returning `None` if either the name was not present, or the string was not formatted correctly + pub fn try_get_string(&self, name: impl Borrow) -> Option<&str> + { + if let Some(bytes) = self.get_bytes(name) { + std::str::from_utf8(bytes).ok() + } else { + None + } + } + /// Try to get a UTF-8 string, returning `None` if the name was not present + /// + /// # Panics + /// If the present string was not valid UTF-8 + pub fn get_string(&self, name: impl Borrow) -> Option<&str> + { + if let Some(bytes) = self.get_bytes(name) { + Some(std::str::from_utf8(bytes).expect("String contained invalid UTF-8")) + } else { + None + } + } + /// Insert bytes directly with this name pub fn insert_bytes(&mut self, name: impl Into>, bytes: impl AsRef<[u8]>) { @@ -80,13 +125,32 @@ impl SuspendObject } /// Insert a value's bytes directly with this name - pub unsafe fn insert_value(&mut self, name: U, value: &T) + pub unsafe fn insert_value_raw(&mut self, name: U, value: &T) where T: ?Sized, U: Into> { self.insert_bytes(name, bytes::refer(value)) } + /// Convert a value to bytes and insert it into this object + pub fn insert_value(&mut self, name: U, value: T) + where U: Into> + { + self.insert_bytes(name, value.into_bytes()) + } + + + /// Try to get a value from the bytes specified by name + pub fn get_value(&self, name: impl Borrow) -> Result, T::Error> + where T: FromBytes + { + if let Some(range) = self.data_instances.get(name.borrow()) { + Ok(Some(T::from_bytes(&self.data[range.clone()])?)) + } else { + Ok(None) + } + } + /// Try to get the bytes specified by name pub fn get_bytes(&self, name: impl Borrow) -> Option<&[u8]> { @@ -101,13 +165,13 @@ impl SuspendObject /// /// # Panics /// If `T` cannot fit into the size of the range - pub unsafe fn get_value(&self, name: impl Borrow) -> Option<&T> + pub unsafe fn get_value_raw(&self, name: impl Borrow) -> Option<&T> { self.get_bytes(name).map(|x| bytes::derefer(x)) } /// Try to get the value specified by name. Will return `None` if `T` cannot fit in the returned bytes. - pub unsafe fn try_get_value(&self, name: impl Borrow) -> Option<&T> + pub unsafe fn try_get_value_raw(&self, name: impl Borrow) -> Option<&T> { if let Some(bytes) = self.get_bytes(name) { if bytes.len() >= std::mem::size_of::() { @@ -249,22 +313,22 @@ impl SuspendObject pub trait SuspendStream { /// Write an object into the opaque stream. - async fn set_object(&mut self, obj: SuspendObject) -> Result<(), SuspendError>; + async fn set_object(&mut self, obj: Object) -> Result<(), Error>; /// Read an object from the opaque stream. - async fn get_object(&mut self) -> Result, SuspendError>; + async fn get_object(&mut self) -> Result, Error>; } /// An error that occoured in a suspend operation #[derive(Debug)] #[non_exhaustive] -pub enum SuspendError { +pub enum Error { BadObject, Corruption, IO(io::Error), Unknown, } -impl error::Error for SuspendError +impl error::Error for Error { fn source(&self) -> Option<&(dyn error::Error + 'static)> { Some(match &self { @@ -273,7 +337,7 @@ impl error::Error for SuspendError }) } } -impl fmt::Display for SuspendError +impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -291,14 +355,15 @@ impl fmt::Display for SuspendError #[async_trait] pub trait Suspendable: Sized { - async fn suspend(self, into: &mut S) -> Result<(), SuspendError>; - async fn load(from: &mut S) -> Result; + async fn suspend(self, into: &mut S) -> Result<(), Error>; + async fn load(from: &mut S) -> Result; } + /* pub struct SuspenceState where T: Suspendable { - + _phantom: PhantomData, }*/