parent
66748ff65d
commit
0e68aa4f7f
@ -1,2 +1,9 @@
|
||||
/target
|
||||
*~
|
||||
|
||||
|
||||
# Added by cargo
|
||||
#
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,2 +0,0 @@
|
||||
* yuurei
|
||||
Anonymous real-time single page textboard
|
@ -1,24 +0,0 @@
|
||||
|
||||
extern crate rustc_version;
|
||||
use rustc_version::{version, version_meta, Channel};
|
||||
|
||||
fn main() {
|
||||
// Assert we haven't travelled back in time
|
||||
assert!(version().unwrap().major >= 1);
|
||||
|
||||
// Set cfg flags depending on release channel
|
||||
match version_meta().unwrap().channel {
|
||||
Channel::Stable => {
|
||||
println!("cargo:rustc-cfg=stable");
|
||||
}
|
||||
Channel::Beta => {
|
||||
println!("cargo:rustc-cfg=beta");
|
||||
}
|
||||
Channel::Nightly => {
|
||||
println!("cargo:rustc-cfg=nightly");
|
||||
}
|
||||
Channel::Dev => {
|
||||
println!("cargo:rustc-cfg=dev");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,108 +0,0 @@
|
||||
use std::{
|
||||
fmt,
|
||||
error,
|
||||
};
|
||||
use libc::c_void;
|
||||
|
||||
pub unsafe fn refer<'a, T>(src: &'a T) -> &'a [u8]
|
||||
where T: ?Sized
|
||||
{
|
||||
std::slice::from_raw_parts(src as *const T as *const u8, std::mem::size_of_val(src))
|
||||
}
|
||||
|
||||
pub unsafe fn refer_mut<'a, T>(src: &'a mut T) -> &'a mut [u8]
|
||||
where T: ?Sized
|
||||
{
|
||||
std::slice::from_raw_parts_mut(src as *mut T as *mut u8, std::mem::size_of_val(src))
|
||||
}
|
||||
|
||||
pub unsafe fn derefer<'a, T>(src: &'a [u8]) -> &'a T
|
||||
{
|
||||
assert!(src.len() >= std::mem::size_of::<T>());
|
||||
&*(&src[0] as *const u8 as *const T)
|
||||
}
|
||||
|
||||
pub unsafe fn derefer_mut<'a, T>(src: &'a mut [u8]) -> &'a mut T
|
||||
{
|
||||
assert!(src.len() >= std::mem::size_of::<T>());
|
||||
&mut *(&mut src[0] as *mut u8 as *mut T)
|
||||
}
|
||||
mod old {
|
||||
use super::*;
|
||||
/// Represents a type that can be converted from bytes to itself
|
||||
pub trait FromBytes: Sized
|
||||
{
|
||||
type Error;
|
||||
fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self,Self::Error>;
|
||||
}
|
||||
|
||||
/// Represents a type that can be converted into bytes
|
||||
pub trait IntoBytes:Sized
|
||||
{
|
||||
fn into_bytes(self) -> Box<[u8]>;
|
||||
}
|
||||
|
||||
impl<T: Into<Box<[u8]>>> IntoBytes for T
|
||||
{
|
||||
#[inline] fn into_bytes(self) -> Box<[u8]>
|
||||
{
|
||||
self.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromBytes for Vec<u8>
|
||||
{
|
||||
type Error = !;
|
||||
#[inline] fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self,Self::Error>
|
||||
{
|
||||
Ok(Vec::from(bytes.as_ref()))
|
||||
}
|
||||
}
|
||||
impl FromBytes for Box<[u8]>
|
||||
{
|
||||
type Error = !;
|
||||
#[inline] fn from_bytes<T: AsRef<[u8]>>(bytes: T) -> Result<Self,Self::Error>
|
||||
{
|
||||
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
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
//! Caching interfaces
|
||||
|
||||
//TODO
|
@ -1,94 +0,0 @@
|
||||
use super::*;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
net::{
|
||||
SocketAddr,
|
||||
Ipv4Addr,
|
||||
},
|
||||
};
|
||||
use tokio::{
|
||||
time,
|
||||
};
|
||||
use cidr::Cidr;
|
||||
|
||||
//TODO: Use tokio Watcher instead, to allow hotreloading?
|
||||
use once_cell::sync::OnceCell;
|
||||
|
||||
static INSTANCE: OnceCell<Config> = OnceCell::new();
|
||||
|
||||
/// Set the global config instance.
|
||||
/// This can only be done once for the duration of the program's runtime
|
||||
pub fn set(conf: Config)
|
||||
{
|
||||
INSTANCE.set(conf).expect("Failed to set global config state: Already set");
|
||||
}
|
||||
|
||||
/// Get the global config instance.
|
||||
/// `set()` must have been previously called or this function panics.
|
||||
pub fn get() -> &'static Config
|
||||
{
|
||||
INSTANCE.get().expect("Global config tried to be accessed before it was set")
|
||||
}
|
||||
|
||||
/// Get the global config instance or return default config instance.
|
||||
pub fn get_or_default() -> Cow<'static, Config>
|
||||
{
|
||||
match INSTANCE.get() {
|
||||
Some(e) => Cow::Borrowed(e),
|
||||
_ => {
|
||||
warn!("Config instance not initialised, using default.");
|
||||
Cow::Owned(Config::default())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A salt for secure tripcode
|
||||
pub type Salt = [u8; khash::salt::SIZE];
|
||||
|
||||
/// Config for this run
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Config
|
||||
{
|
||||
/// Name for nanashi
|
||||
pub anon_name: Cow<'static, str>,
|
||||
/// The server will listen on this address
|
||||
pub listen: SocketAddr,
|
||||
/// Accept all connections in this match
|
||||
pub accept_mask: Vec<cidr::IpCidr>,
|
||||
/// Deny all connections in this match, even if previously matched by allow
|
||||
pub deny_mask: Vec<cidr::IpCidr>,
|
||||
/// Accept by default
|
||||
pub accept_default: bool,
|
||||
/// The number of connections allowed to be processed at once on one route
|
||||
pub dos_max: usize,
|
||||
|
||||
/// The timeout for any routing dispatch
|
||||
pub req_timeout_local: Option<time::Duration>,
|
||||
/// The timeout for *all* routing dispatchs
|
||||
pub req_timeout_global: Option<time::Duration>,
|
||||
|
||||
/// The salt for secure tripcode
|
||||
pub tripcode_salt: Salt,
|
||||
}
|
||||
|
||||
impl Default for Config
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self {
|
||||
anon_name: Cow::Borrowed("Nanashi"),
|
||||
listen: SocketAddr::from(([0,0,0,0], 8088)),
|
||||
accept_mask: vec![cidr::Ipv4Cidr::new(Ipv4Addr::new(127,0,0,1), 32).unwrap().into()],
|
||||
deny_mask: Vec::new(),
|
||||
accept_default: false,
|
||||
dos_max: 16,
|
||||
|
||||
req_timeout_local: Some(time::Duration::from_millis(500)),
|
||||
req_timeout_global: Some(time::Duration::from_secs(1)),
|
||||
|
||||
tripcode_salt: hex!("770d64c6bf46da1a812d067fd224bbe671b7607d3274265abfcdda45c44ca3c1"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,333 +0,0 @@
|
||||
//! Extensions
|
||||
use super::*;
|
||||
use std::{
|
||||
marker::PhantomData,
|
||||
fmt,
|
||||
ops,
|
||||
};
|
||||
|
||||
/// Wrapper to derive debug for types that don't implement it.
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, PartialEq, Eq, Ord,PartialOrd, Hash)]
|
||||
pub struct OpaqueDebug<T>(T);
|
||||
|
||||
impl<T> OpaqueDebug<T>
|
||||
{
|
||||
/// Create a new wrapper
|
||||
#[inline] pub const fn new(value: T) -> Self
|
||||
{
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Consume into the value
|
||||
#[inline] pub fn into_inner(self) -> T
|
||||
{
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsRef<T> for OpaqueDebug<T>
|
||||
{
|
||||
#[inline] fn as_ref(&self) -> &T
|
||||
{
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> AsMut<T> for OpaqueDebug<T>
|
||||
{
|
||||
#[inline] fn as_mut(&mut self) -> &mut T
|
||||
{
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::Deref for OpaqueDebug<T>
|
||||
{
|
||||
type Target = T;
|
||||
#[inline] fn deref(&self) -> &Self::Target
|
||||
{
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ops::DerefMut for OpaqueDebug<T>
|
||||
{
|
||||
#[inline] fn deref_mut(&mut self) -> &mut Self::Target
|
||||
{
|
||||
&mut self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for OpaqueDebug<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "<opaque value>")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A trait for types that can insert objects at their end.
|
||||
pub trait BackInserter<T>
|
||||
{
|
||||
/// Insert an object at the end of this container
|
||||
fn push_back(&mut self, value: T);
|
||||
}
|
||||
|
||||
impl<T> BackInserter<T> for Vec<T>
|
||||
{
|
||||
#[inline]
|
||||
fn push_back(&mut self, value: T)
|
||||
{
|
||||
self.push(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Absracts a closure for `BackInserter<T>`.
|
||||
pub struct BackInsertPass<T,F>(F, PhantomData<T>)
|
||||
where F: FnMut(T);
|
||||
|
||||
impl<T,F: FnMut(T)> BackInsertPass<T,F>
|
||||
{
|
||||
/// Create a new instance with this closure
|
||||
#[inline] pub fn new(func: F) -> Self
|
||||
{
|
||||
Self(func, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, F: FnMut(T)> BackInserter<T> for BackInsertPass<T,F>
|
||||
{
|
||||
#[inline] fn push_back(&mut self, value: T)
|
||||
{
|
||||
self.0(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// A `BackInserter<T>` that will only add a max capacity of items before it starts dropping input to its `push_back` function.
|
||||
pub struct CappedBackInserter<'a, T>(&'a mut T, usize, usize)
|
||||
where T: BackInserter<T>;
|
||||
|
||||
impl<'a, T> CappedBackInserter<'a, T>
|
||||
where T: BackInserter<T>
|
||||
{
|
||||
/// Create a new instance with this max capacity
|
||||
#[inline] pub fn new(from: &'a mut T, cap: usize) -> Self
|
||||
{
|
||||
Self(from, 0, cap)
|
||||
}
|
||||
|
||||
/// The number of elements pushed so far
|
||||
#[inline] pub fn len(&self) -> usize {
|
||||
self.1
|
||||
}
|
||||
|
||||
/// The max number of elemnts allowed to be pushed
|
||||
#[inline] pub fn cap(&self) -> usize {
|
||||
self.2
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> BackInserter<T> for CappedBackInserter<'a, T>
|
||||
where T: BackInserter<T>
|
||||
{
|
||||
#[inline] fn push_back(&mut self, value: T)
|
||||
{
|
||||
if self.1 < self.2 {
|
||||
self.0.push_back(value);
|
||||
self.1+=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub trait VecExt<T>
|
||||
{
|
||||
/// Insert many elements with exact size iterator
|
||||
fn insert_exact<Ex, I: IntoIterator<Item = T, IntoIter = Ex>>(&mut self, location: usize, slice: I)
|
||||
where Ex: ExactSizeIterator<Item = T>;
|
||||
|
||||
/// Insert many elements
|
||||
fn insert_many<I: IntoIterator<Item =T>>(&mut self, location: usize, slice: I);
|
||||
}
|
||||
|
||||
|
||||
impl<T> VecExt<T> for Vec<T>
|
||||
{
|
||||
#[cfg(not(feature="experimental_inserter"))]
|
||||
#[inline(always)]
|
||||
fn insert_exact<Ex, I: IntoIterator<Item = T, IntoIter = Ex>>(&mut self, location: usize, slice: I)
|
||||
where Ex: ExactSizeIterator<Item = T>
|
||||
{
|
||||
self.insert_many(location, slice)
|
||||
}
|
||||
#[cfg(feature="experimental_inserter")]
|
||||
fn insert_exact<Ex, I: IntoIterator<Item = T, IntoIter = Ex>>(&mut self, location: usize, slice: I)
|
||||
where Ex: ExactSizeIterator<Item = T>,
|
||||
{
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn panic_len(l1: usize, l2: usize) -> !
|
||||
{
|
||||
panic!("Location must be in range 0..{}, got {}", l1,l2)
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
fn inv_sz() -> !
|
||||
{
|
||||
panic!("ExactSizeIterator returned invalid size");
|
||||
}
|
||||
|
||||
if location >= self.len() {
|
||||
panic_len(self.len(), location);
|
||||
}
|
||||
let mut slice = slice.into_iter();
|
||||
|
||||
let slen = slice.len();
|
||||
match slen {
|
||||
0 => return,
|
||||
1 => {
|
||||
self.insert(location, slice.next().unwrap());
|
||||
return
|
||||
},
|
||||
_ => (),
|
||||
};
|
||||
self.reserve(slice.len());
|
||||
|
||||
unsafe {
|
||||
let this = self.as_mut_ptr().add(location);
|
||||
let len = self.len();
|
||||
let rest = std::mem::size_of::<T>() * (location..len).len();
|
||||
|
||||
libc::memmove(this.add(slen) as *mut libc::c_void, this as *mut libc::c_void, rest);
|
||||
let mut sent=0;
|
||||
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
let mut this = this;
|
||||
for item in slice {
|
||||
if sent >= slen {
|
||||
inv_sz();
|
||||
}
|
||||
|
||||
this.write(item);
|
||||
this = this.add(1);
|
||||
sent+=1;
|
||||
}
|
||||
if sent != slen {
|
||||
inv_sz();
|
||||
}
|
||||
})) {
|
||||
Err(e) => {
|
||||
// memory at (location+sent)..slen is now invalid, move the old one back before allowing unwind to contine
|
||||
libc::memmove(this.add(sent) as *mut libc::c_void, this.add(slen) as *mut libc::c_void, rest);
|
||||
self.set_len(len + sent);
|
||||
std::panic::resume_unwind(e)
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
self.set_len(len + sent);
|
||||
}
|
||||
}
|
||||
#[inline]
|
||||
fn insert_many<I: IntoIterator<Item =T>>(&mut self, location: usize, slice: I)
|
||||
{
|
||||
let slice = slice.into_iter();
|
||||
match slice.size_hint() {
|
||||
(0, Some(0)) | (0, None) => (),
|
||||
(_, Some(bound)) | (bound, _) => self.reserve(bound),
|
||||
};
|
||||
|
||||
self.splice(location..location, slice);
|
||||
|
||||
//let splice = self.split_off(location);
|
||||
//self.extend(slice.chain(splice.into_iter()));
|
||||
|
||||
/*
|
||||
// shift everything across, replacing with the new values
|
||||
let splice: Vec<_> = self.splice(location.., slice).collect();
|
||||
// ^ -- this allocation bugs me, but we violate aliasing rules if we don't somehow collect it before adding it back in so...
|
||||
// add tail back
|
||||
self.extend(splice);*/
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
use super::*;
|
||||
#[test]
|
||||
fn vec_insert_exact()
|
||||
{
|
||||
let mut vec = vec![0,1,2,8,9,10];
|
||||
vec.insert_exact(3, [3,4,5,6, 7].iter().copied());
|
||||
|
||||
assert_eq!(&vec[..],
|
||||
&[0,1,2,3,4,5,6,7,8,9,10]
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
fn vec_insert_exact_nt()
|
||||
{
|
||||
macro_rules! string {
|
||||
($str:literal) => (String::from($str));
|
||||
}
|
||||
let mut vec = vec![
|
||||
string!("Hello"),
|
||||
string!("world"),
|
||||
string!("foo"),
|
||||
string!("uhh"),
|
||||
];
|
||||
let vec2 = vec![
|
||||
string!("Hello"),
|
||||
string!("world"),
|
||||
string!("hi"),
|
||||
string!("hello"),
|
||||
string!("foo"),
|
||||
string!("uhh"),
|
||||
];
|
||||
|
||||
vec.insert_exact(2, vec![string!("hi"), string!("hello")]);
|
||||
|
||||
assert_eq!(&vec[..], &vec2[..]);
|
||||
}
|
||||
|
||||
#[cfg(nightly)]
|
||||
mod benchmatks
|
||||
{
|
||||
use super::super::*;
|
||||
use test::{
|
||||
Bencher, black_box,
|
||||
};
|
||||
#[cfg(not(feature="experimental_inserter"))]
|
||||
#[bench]
|
||||
fn move_exact(b: &mut Bencher)
|
||||
{
|
||||
let mut vec = vec![0,10,11,12];
|
||||
let span = [0,1,2,3];
|
||||
b.iter(|| {
|
||||
black_box(vec.insert_exact(vec.len()/2, span.iter().copied()));
|
||||
});
|
||||
}
|
||||
#[bench]
|
||||
fn move_via_splice(b: &mut Bencher)
|
||||
{
|
||||
let mut vec = vec![0,10,11,12];
|
||||
let span = [0,1,2,3];
|
||||
|
||||
b.iter(|| {
|
||||
black_box(vec.insert_many(vec.len()/2, span.iter().copied()));
|
||||
});
|
||||
}
|
||||
|
||||
#[cfg(feature="experimental_inserter")]
|
||||
#[bench]
|
||||
fn move_via_unsafe(b: &mut Bencher)
|
||||
{
|
||||
let mut vec = vec![0,10,11,12];
|
||||
let span = [0,1,2,3];
|
||||
b.iter(|| {
|
||||
black_box(vec.insert_exact(vec.len()/2, span.iter().copied()));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -1,165 +0,0 @@
|
||||
//! HTML rendering
|
||||
use super::*;
|
||||
use std::{
|
||||
fmt::{
|
||||
self,
|
||||
Display,
|
||||
},
|
||||
io,
|
||||
};
|
||||
|
||||
pub struct HtmlTokenStream<W: io::Write>(W);
|
||||
|
||||
impl<W: io::Write> HtmlTokenStream<W>
|
||||
{
|
||||
fn push<T: DisplayHtml>(&mut self, token: T) -> io::Result<()>
|
||||
{
|
||||
write!(&mut self.0, "{}", display_html(&token)) //TODO: How to do async version of this?
|
||||
}
|
||||
}
|
||||
|
||||
/// Coerce a `DisplayHtml` value into an opaque implementor of `fmt::Display`.
|
||||
#[inline] pub fn display_html<'a, T: DisplayHtml+?Sized>(from: &'a T) -> impl fmt::Display +'a
|
||||
{
|
||||
struct Wrap<'a, T: DisplayHtml+?Sized>(&'a T);
|
||||
|
||||
impl<'a,T> fmt::Display for Wrap<'a,T>
|
||||
where T: DisplayHtml+?Sized
|
||||
{
|
||||
#[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
DisplayHtml::fmt(self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
Wrap(from)
|
||||
}
|
||||
|
||||
/// Coerse a `Display` value into a `DisplayHtml` by HTML-escaping its output.
|
||||
///
|
||||
/// To output raw HTML, see `Safe<T>`.
|
||||
#[inline] pub fn escape_html<'a, T: Display+?Sized>(from: &'a T) -> impl DisplayHtml +'a
|
||||
{
|
||||
struct Wrap<'a, T: Display+?Sized>(&'a T);
|
||||
|
||||
struct FilterStream<T,F>(T, F)
|
||||
where F: FnMut(&mut T, char) -> fmt::Result;
|
||||
|
||||
impl<T,F> fmt::Write for FilterStream<T,F>
|
||||
where T: fmt::Write,
|
||||
F: FnMut(&mut T, char) -> fmt::Result
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result
|
||||
{
|
||||
for c in s.chars()
|
||||
{
|
||||
self.1(&mut self.0, c)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn write_char(&mut self, c: char) -> fmt::Result
|
||||
{
|
||||
self.1(&mut self.0, c)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a,T> DisplayHtml for Wrap<'a,T>
|
||||
where T: Display +?Sized
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
use smallmap::Map;
|
||||
lazy_static! {
|
||||
static ref HTML_EQ_TABLE: Map<char, &'static str> = {
|
||||
let mut map = Map::new();
|
||||
map.insert('&', "&");
|
||||
map.insert('<', "<");
|
||||
map.insert('>', ">");
|
||||
map.insert('"', """);
|
||||
map
|
||||
};
|
||||
}
|
||||
let mut output = FilterStream(f, |output, c| {
|
||||
output.write_str(match HTML_EQ_TABLE.get(&c) {
|
||||
Some(s) => s,
|
||||
None => {
|
||||
return output.write_char(c);
|
||||
},
|
||||
})
|
||||
});
|
||||
use fmt::Write;
|
||||
write!(&mut output, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
Wrap(from)
|
||||
}
|
||||
|
||||
/// HTML-safe wrapper around
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub struct Safe<T: Display>(T);
|
||||
|
||||
impl<T: Display> Safe<T>
|
||||
{
|
||||
/// Create a new instance with this value
|
||||
#[inline] pub fn new(value: T) -> Self
|
||||
{
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Consume this instance into its inner value
|
||||
pub fn into_inner(self) -> T
|
||||
{
|
||||
self.0
|
||||
}
|
||||
|
||||
/// Gets a reference to the inner value
|
||||
pub fn display(&self) -> &T
|
||||
{
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Display> From<T> for Safe<T>
|
||||
{
|
||||
#[inline] fn from(from: T) -> Self
|
||||
{
|
||||
Self(from)
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait for displaying as html.
|
||||
///
|
||||
/// Any type implementing `Display` will implement `DisplayHtml` by HTML-escaping the output from it's display formatter.
|
||||
/// You can also use `Safe<T>` to render as raw HTML.
|
||||
pub trait DisplayHtml
|
||||
{
|
||||
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result;
|
||||
|
||||
#[inline] fn to_html(&self) -> String
|
||||
{
|
||||
display_html(self).to_string()
|
||||
}
|
||||
}
|
||||
impl<T: Display> DisplayHtml for Safe<T>
|
||||
{
|
||||
#[inline] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
self.0.fmt(fmt)
|
||||
}
|
||||
|
||||
#[inline] fn to_html(&self) -> String
|
||||
{
|
||||
self.0.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl<T: ?Sized> DisplayHtml for T
|
||||
where T: Display
|
||||
{
|
||||
#[inline] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
escape_html(self).fmt(fmt)
|
||||
}
|
||||
}
|
@ -1,66 +0,0 @@
|
||||
//! Handles post and user identity.
|
||||
use super::*;
|
||||
use std::{
|
||||
fmt,
|
||||
};
|
||||
use uuid::Uuid;
|
||||
|
||||
/// A globally unique post identifier.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PostID(Uuid);
|
||||
|
||||
impl PostID
|
||||
{
|
||||
/// Generate a new `PostID`.
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self(Uuid::new_v4())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PostID
|
||||
{
|
||||
#[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
self.0.fmt(f) //TODO: Change to use kana-hash.
|
||||
}
|
||||
}
|
||||
|
||||
/// A user's data, if set.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct User
|
||||
{
|
||||
pub name: Option<String>,
|
||||
pub email: Option<String>,
|
||||
pub tripcode: Option<tripcode::Tripcode>,
|
||||
}
|
||||
|
||||
impl User
|
||||
{
|
||||
/// The name string of this instance, or the default name.
|
||||
pub fn name_str(&self) -> &str
|
||||
{
|
||||
self.name.as_ref().map(|x| &x[..]).unwrap_or(&config::get().anon_name[..])
|
||||
}
|
||||
|
||||
/// The email string of this instance, if it is set
|
||||
pub fn email_str(&self) -> Option<&str>
|
||||
{
|
||||
self.email.as_ref().map(|x| &x[..])
|
||||
}
|
||||
|
||||
// No str for tripcode because it's not just a string (yet)
|
||||
|
||||
/// Anon with no detials.
|
||||
pub const fn anon() -> Self
|
||||
{
|
||||
Self {
|
||||
name: None,
|
||||
email: None,
|
||||
tripcode: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,92 +1,3 @@
|
||||
#![cfg_attr(nightly, feature(option_unwrap_none))]
|
||||
#![cfg_attr(nightly, feature(never_type))]
|
||||
|
||||
#![allow(dead_code)]
|
||||
|
||||
#![cfg_attr(nightly, feature(test))]
|
||||
|
||||
#[cfg(all(nightly, test))] extern crate test;
|
||||
|
||||
#[macro_use] extern crate log;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use serde::{
|
||||
Serialize, Deserialize,
|
||||
};
|
||||
use color_eyre::{
|
||||
eyre,
|
||||
Help,
|
||||
SectionExt,
|
||||
};
|
||||
use futures::{
|
||||
FutureExt as _,
|
||||
prelude::*,
|
||||
};
|
||||
use hex_literal::hex;
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
macro_rules! cfg_debug {
|
||||
(if {$($if:tt)*} else {$($else:tt)*}) => {
|
||||
{
|
||||
#[cfg(debug_assertions)] {
|
||||
$($if)*
|
||||
}
|
||||
#[cfg(not(debug_assertions))] {
|
||||
$($else)*
|
||||
}
|
||||
}
|
||||
};
|
||||
(if $if:expr) => {
|
||||
{
|
||||
#[cfg(debug_assertions)] $if
|
||||
}
|
||||
};
|
||||
(else $else:expr) => {
|
||||
{
|
||||
#[cfg(not(debug_assertions))] $else
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
mod ext;
|
||||
use ext::*;
|
||||
mod bytes;
|
||||
mod suspend;
|
||||
|
||||
mod regex;
|
||||
mod cache;
|
||||
|
||||
mod config;
|
||||
mod tripcode;
|
||||
mod identity;
|
||||
mod post;
|
||||
mod state;
|
||||
|
||||
mod html;
|
||||
mod web;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), eyre::Report>{
|
||||
color_eyre::install()?;
|
||||
pretty_env_logger::init();
|
||||
|
||||
trace!("Setting default config");
|
||||
|
||||
config::set(Default::default());
|
||||
|
||||
web::serve(Default::default()).await?;
|
||||
|
||||
info!("Server shutdown gracefully");
|
||||
/*
|
||||
let mut vec = vec![vec![1, 0, 0],
|
||||
vec![0, 0, 1]];
|
||||
let span = vec![vec![0, 1, 0],
|
||||
vec![1, 0, 1],
|
||||
vec![0, 1, 0]];
|
||||
|
||||
for _ in 0..10000 {
|
||||
vec.insert_exact(1, span.iter().cloned());
|
||||
}*/
|
||||
Ok(())
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
|
@ -1,156 +0,0 @@
|
||||
//! Structure for updating posts in-place
|
||||
use super::*;
|
||||
use std::{
|
||||
fmt,
|
||||
hash::{
|
||||
Hash,Hasher,
|
||||
},
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
|
||||
/// Represents all valid timestamps for a post.
|
||||
///
|
||||
/// Usually in UTC, pls keep it in Utc...
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PostTimestamp
|
||||
{
|
||||
pub opened: DateTime<Utc>,
|
||||
pub closed: Option<DateTime<Utc>>,
|
||||
pub last_edit: DateTime<Utc>,
|
||||
}
|
||||
|
||||
impl PostTimestamp
|
||||
{
|
||||
/// Create a new timestamp for a new post
|
||||
pub fn new() -> Self
|
||||
{
|
||||
let now = Utc::now();
|
||||
Self {
|
||||
opened: now.clone(),
|
||||
closed: None,
|
||||
last_edit: now,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for PostTimestamp {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.opened.hash(state);
|
||||
self.closed.hash(state);
|
||||
self.last_edit.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PostTimestamp
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
writeln!(f, " Opened - {}", self.opened)?;
|
||||
writeln!(f, " Edited - {}", self.last_edit)?;
|
||||
write!(f, " Closed - ")?;
|
||||
if let Some(closed) = &self.closed {
|
||||
writeln!(f, "{}", closed)?;
|
||||
} else {
|
||||
writeln!(f, "Stil open")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
/// A closed and finished post. The inverse of `Imouto`.
|
||||
pub struct Static
|
||||
{
|
||||
id: identity::PostID,
|
||||
|
||||
user: identity::User,
|
||||
|
||||
title: Option<String>,
|
||||
karada: String,
|
||||
|
||||
timestamp: PostTimestamp,
|
||||
|
||||
/// Hash of the rest of the post data. . . .
|
||||
hash: crypto::sha256::Sha256Hash,
|
||||
}
|
||||
|
||||
impl Static
|
||||
{
|
||||
/// Compute the hash for this instance
|
||||
pub fn into_hashed(mut self) -> Self
|
||||
{
|
||||
self.hash = Default::default();
|
||||
let bytes = serde_cbor::to_vec(&self).expect("Failed to serialise static post to CBOR");
|
||||
Self {
|
||||
hash: crypto::sha256::compute_slice(&bytes),
|
||||
..self
|
||||
}
|
||||
}
|
||||
|
||||
/// Validate the internal hash of this instance
|
||||
pub fn validate_hash(&self) -> bool
|
||||
{
|
||||
self.clone().into_hashed().hash == self.hash
|
||||
}
|
||||
}
|
||||
|
||||
use suspend::{
|
||||
Suspendable,
|
||||
SuspendStream,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
impl Suspendable for Static
|
||||
{
|
||||
async fn suspend<S: SuspendStream +Send+Sync + ?Sized>(self, into: &mut S) -> Result<(), suspend::Error>
|
||||
{
|
||||
let mut output = suspend::Object::new();
|
||||
output.insert("post-static", self);
|
||||
into.set_object(output).await
|
||||
}
|
||||
async fn load<S: SuspendStream +Send+Sync+ ?Sized>(from: &mut S) -> Result<Self, suspend::Error>
|
||||
{
|
||||
let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?;
|
||||
input.try_get("post-static").ok_or(suspend::Error::BadObject)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn static_post_ser()
|
||||
{
|
||||
let mut output = suspend::MemorySuspendStream::new();
|
||||
let post1 = Static {
|
||||
id: identity::PostID::new(),
|
||||
user: Default::default(),
|
||||
title: None,
|
||||
karada: "hello world".to_owned(),
|
||||
timestamp: PostTimestamp::new(),
|
||||
hash: Default::default(),
|
||||
}.into_hashed();
|
||||
|
||||
let post1c = post1.clone();
|
||||
assert!(post1.validate_hash());
|
||||
post1.suspend(&mut output).await.expect("Suspend failed");
|
||||
|
||||
let buffer = output.buffer().clone();
|
||||
let post2 = Static::load(&mut output).await.expect("Suspend load failed");
|
||||
|
||||
assert_eq!(post1c, post2);
|
||||
assert!(post1c.validate_hash());
|
||||
assert!(post2.validate_hash());
|
||||
|
||||
let buffer2 = suspend::oneshot(post2.clone()).await.expect("Oneshot failed");
|
||||
|
||||
assert_eq!(&buffer2[..], &buffer[..]);
|
||||
|
||||
let post3: Static = suspend::single(buffer2).await.expect("Single failed");
|
||||
|
||||
assert!(post3.validate_hash());
|
||||
assert_eq!(post3, post2);
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
//! PCRE
|
||||
use super::*;
|
||||
use pcre::{
|
||||
Pcre,
|
||||
};
|
||||
use std::{
|
||||
sync::{
|
||||
Mutex,
|
||||
},
|
||||
error,
|
||||
fmt,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Regex(Mutex<Pcre>, String);
|
||||
|
||||
/// A regex error
|
||||
#[derive(Debug)]
|
||||
pub struct Error(pcre::CompilationError);
|
||||
|
||||
impl error::Error for Error{}
|
||||
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f,"regex failed to compile: ")?;
|
||||
self.0.fmt(f)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<pcre::CompilationError> for Error
|
||||
{
|
||||
#[inline] fn from(from: pcre::CompilationError) -> Self
|
||||
{
|
||||
Self(from)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Iter<'a>(Option<pcre::Match<'a>>, usize);
|
||||
|
||||
impl<'a> Iterator for Iter<'a>
|
||||
{
|
||||
type Item = &'a str;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
if let Some(m) = &mut self.0 {
|
||||
if self.1 >= m.string_count() {
|
||||
None
|
||||
} else {
|
||||
let string = m.group(self.1);
|
||||
self.1 +=1;
|
||||
Some(string)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[inline] fn size_hint(&self) -> (usize, Option<usize>) {
|
||||
(self.len(), Some(self.len()))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::iter::FusedIterator for Iter<'a>{}
|
||||
impl<'a> ExactSizeIterator for Iter<'a>
|
||||
{
|
||||
fn len(&self) -> usize
|
||||
{
|
||||
match &self.0 {
|
||||
Some(m) => m.string_count(),
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Regex
|
||||
{
|
||||
/// Create a new regular expression from a string
|
||||
pub fn new<I>(from: impl AsRef<str>, opt: I) -> Result<Self, Error>
|
||||
where I: IntoIterator<Item=pcre::CompileOption>
|
||||
{
|
||||
let string = from.as_ref();
|
||||
Ok(Self(Mutex::new(Pcre::compile_with_options(string, &opt.into_iter().collect())?), string.to_owned()))
|
||||
}
|
||||
|
||||
/// Test against string
|
||||
pub fn test(&self, string: impl AsRef<str>) -> bool
|
||||
{
|
||||
let mut regex = self.0.lock().unwrap();
|
||||
regex.exec(string.as_ref()).is_some()
|
||||
}
|
||||
|
||||
/// Get the groups of a match against string
|
||||
pub fn exec_ref<'a>(&mut self, string: &'a (impl AsRef<str> + ?Sized)) -> Iter<'a>
|
||||
{
|
||||
let regex = self.0.get_mut().unwrap();
|
||||
Iter(regex.exec(string.as_ref()), 0)
|
||||
}
|
||||
|
||||
/// Get the groups as an owned string vector from map against string
|
||||
pub fn exec(&self, string: impl AsRef<str>) -> Vec<String>
|
||||
{
|
||||
let mut regex = self.0.lock().unwrap();
|
||||
Iter(regex.exec(string.as_ref()), 0).map(std::borrow::ToOwned::to_owned).collect()
|
||||
}
|
||||
|
||||
/// Get the string used to create this regex
|
||||
#[inline] pub fn as_str(&self) -> &str{
|
||||
&self.1[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Regex> for Pcre
|
||||
{
|
||||
#[inline] fn from(from: Regex) -> Self
|
||||
{
|
||||
from.0.into_inner().unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Regex
|
||||
{
|
||||
#[inline] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "{}", self.1)
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
//! Container state for all posts
|
||||
use super::*;
|
||||
|
||||
/// Contains all posts in an instance, open or closed.
|
||||
pub struct Oneesan
|
||||
{
|
||||
|
||||
}
|
@ -1,255 +0,0 @@
|
||||
//! Deltas and applying them
|
||||
use super::*;
|
||||
use difference::{
|
||||
Changeset,
|
||||
Difference,
|
||||
};
|
||||
|
||||
const MAX_SINGLE_DELTA_SIZE: usize = 14;
|
||||
const DELTA_BREAK: &str = "";
|
||||
|
||||
/// Infer all deltas needed to be sequentially applied to `orig` to transform it to `new`, return the number inserted into `output`.
|
||||
pub fn infer_deltas<T: BackInserter<Delta> + ?Sized>(output: &mut T, orig: &str, new: &str) -> usize
|
||||
{
|
||||
let set = Changeset::new(orig, new, DELTA_BREAK);
|
||||
|
||||
let mut done=0;
|
||||
let mut position =0;
|
||||
for diff in set.diffs.into_iter() {
|
||||
match diff {
|
||||
Difference::Same(string) => {
|
||||
position += string.len();
|
||||
},
|
||||
Difference::Rem(string) => {
|
||||
output.push_back(Delta {
|
||||
location: position,
|
||||
kind: DeltaKind::RemoveAhead{span_len: string.len()},
|
||||
});
|
||||
done+=1;
|
||||
},
|
||||
Difference::Add(string) => {
|
||||
let mut passer = BackInsertPass::new(|(span, span_len)| {
|
||||
output.push_back(Delta {
|
||||
location: position,
|
||||
kind: DeltaKind::Insert {
|
||||
span, span_len,
|
||||
},
|
||||
});
|
||||
position+= usize::from(span_len);
|
||||
done+=1;
|
||||
});
|
||||
delta_span_many(&mut passer, string.chars());
|
||||
},
|
||||
}
|
||||
}
|
||||
done
|
||||
}
|
||||
|
||||
/// Create a delta span from an input iterator.
|
||||
///
|
||||
/// This function can take no more than `min(255, MAX_SINGLE_DELTA_SIZE)` chars from the input. The number of chars inserted is also returned as `u8`.
|
||||
pub(super) fn delta_span<I>(from: I) -> ([char; MAX_SINGLE_DELTA_SIZE], u8)
|
||||
where I: IntoIterator<Item = char>
|
||||
{
|
||||
let mut output: [char; MAX_SINGLE_DELTA_SIZE] = Default::default();
|
||||
let mut sz: u8 = 0;
|
||||
|
||||
for (d, s) in output.iter_mut().zip(from.into_iter().take(usize::from(u8::MAX)))
|
||||
{
|
||||
*d = s;
|
||||
sz += 1;
|
||||
}
|
||||
|
||||
(output, sz)
|
||||
}
|
||||
|
||||
/// Create as many delta spans needed from an input iterator.
|
||||
///
|
||||
/// This function can take as many inputs as needed, and outputs the needed amount of spans into `into`, and then returns the number added.
|
||||
pub(super) fn delta_span_many<T: BackInserter<([char; MAX_SINGLE_DELTA_SIZE], u8)> + ?Sized, I>(into: &mut T, from: I) -> usize
|
||||
where I: IntoIterator<Item = char>
|
||||
{
|
||||
let mut iter = from.into_iter();
|
||||
let mut added=0;
|
||||
loop {
|
||||
match delta_span(&mut iter) {
|
||||
(_, 0) => break,
|
||||
other => into.push_back(other),
|
||||
}
|
||||
added+=1;
|
||||
}
|
||||
added
|
||||
}
|
||||
|
||||
/// Information about the delta to be applied in `Delta`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub enum DeltaKind
|
||||
{
|
||||
/// Append to the end of body. Equivilant to `Insert` with a location at `karada.scape.len()` (the end of the buffer).
|
||||
Append{
|
||||
span: [char; MAX_SINGLE_DELTA_SIZE],
|
||||
span_len: u8,
|
||||
},
|
||||
/// Insert `span_len` chars from `span` into body starting *at* `location` and moving ahead
|
||||
Insert{
|
||||
span: [char; MAX_SINGLE_DELTA_SIZE],
|
||||
span_len: u8,
|
||||
},
|
||||
/// Remove `span_len` chars from `location`.
|
||||
RemoveAhead{
|
||||
span_len: usize,
|
||||
},
|
||||
/// Remove `span_len` chars up to but not including `location`.
|
||||
RemoveBehind{
|
||||
span_len: usize,
|
||||
},
|
||||
/// Remove everything from `location` to the end.
|
||||
Truncate,
|
||||
/// Remove everything from 0 to `location`.
|
||||
Shift,
|
||||
/// Remove char at `location`
|
||||
RemoveSingle,
|
||||
/// Remove entire post body
|
||||
Clear,
|
||||
}
|
||||
|
||||
/// A delta to apply to `Karada`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
|
||||
pub struct Delta
|
||||
{
|
||||
/// Location to insert into. This is the INclusive range of: 0..=(karada.scape.len()).
|
||||
///
|
||||
/// Insertions off the end of the buffer are to be appened instead.
|
||||
location: usize,
|
||||
/// The kind of delta to insert
|
||||
kind: DeltaKind,
|
||||
}
|
||||
|
||||
/// Static assertion: `MAX_SINGLE_DELTA_SIZE` can fit into `u8`.
|
||||
const _: [u8;(MAX_SINGLE_DELTA_SIZE < (!0u8 as usize)) as usize] = [0];
|
||||
|
||||
impl Delta
|
||||
{
|
||||
pub fn insert(&self, inserter: &mut MessageSpan)
|
||||
{
|
||||
match self.kind {
|
||||
DeltaKind::Append{span, span_len} => {
|
||||
inserter.extend_from_slice(&span[..usize::from(span_len)]);
|
||||
},
|
||||
DeltaKind::Insert{span, span_len} => {
|
||||
let span = &span[..usize::from(span_len)];
|
||||
if self.location == inserter.len() {
|
||||
inserter.extend_from_slice(span);
|
||||
} else if span.len() == 1 {
|
||||
inserter.insert(self.location, span[0]);
|
||||
} else if span.len() > 1 {
|
||||
inserter.insert_exact(self.location, span.iter().copied());
|
||||
}
|
||||
},
|
||||
DeltaKind::RemoveAhead{span_len} => {
|
||||
inserter.drain(self.location..(self.location+span_len));
|
||||
},
|
||||
DeltaKind::RemoveBehind{span_len} => {
|
||||
let span_st = self.location.checked_sub(span_len).unwrap_or(0);
|
||||
inserter.drain(span_st..self.location);
|
||||
},
|
||||
DeltaKind::RemoveSingle => {
|
||||
inserter.remove(self.location);
|
||||
},
|
||||
DeltaKind::Clear => inserter.clear(),
|
||||
DeltaKind::Truncate => inserter.truncate(self.location),
|
||||
DeltaKind::Shift => ((),inserter.drain(..self.location)).0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn infer_and_insert()
|
||||
{
|
||||
let orig = "the quick brown fox jumped over the lazy dog !!!!";
|
||||
let new = "the quick brown dog jumped over the lazy fox twice";
|
||||
|
||||
let mut deltas = Vec::new();
|
||||
infer_deltas(&mut deltas, orig, new);
|
||||
|
||||
println!("{:#?}", deltas);
|
||||
let output: String = {
|
||||
let mut scape: Vec<_> = orig.chars().collect();
|
||||
for delta in deltas.into_iter() {
|
||||
delta.insert(&mut scape);
|
||||
}
|
||||
scape.into_iter().collect()
|
||||
};
|
||||
|
||||
assert_eq!(&output[..], &new[..]);
|
||||
}
|
||||
|
||||
macro_rules! insert {
|
||||
($expects:literal; $start:literal, $ins:literal, $where:expr) => {
|
||||
{
|
||||
|
||||
let mut message: Vec<char> = $start.chars().collect();
|
||||
|
||||
let delta = {
|
||||
let (span, span_len) = delta_span($ins.chars());
|
||||
Delta {
|
||||
location: $where,
|
||||
kind: DeltaKind::Insert{span, span_len},
|
||||
}
|
||||
};
|
||||
|
||||
println!("from: {:?}", message);
|
||||
println!("delta: {:?}", delta);
|
||||
delta.insert(&mut message);
|
||||
|
||||
assert_eq!(&message.into_iter().collect::<String>()[..], $expects);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn insert_body()
|
||||
{
|
||||
insert!("123456789"; "126789", "345", 2);
|
||||
}
|
||||
#[test]
|
||||
fn insert_end()
|
||||
{
|
||||
insert!("123456789"; "1289", "34567", 2);
|
||||
}
|
||||
#[test]
|
||||
fn insert_end_rev()
|
||||
{
|
||||
insert!("123456789"; "1234569", "78", 6);
|
||||
}
|
||||
#[test]
|
||||
fn insert_begin_rev()
|
||||
{
|
||||
insert!("123456789"; "1456789", "23", 1);
|
||||
}
|
||||
#[test]
|
||||
fn insert_begin()
|
||||
{
|
||||
insert!("123456789"; "789", "123456", 0);
|
||||
}
|
||||
#[test]
|
||||
fn insert_end_f()
|
||||
{
|
||||
insert!("123456789"; "123", "456789", 3);
|
||||
}
|
||||
#[test]
|
||||
fn insert_end_f_rev()
|
||||
{
|
||||
insert!("123456789"; "1234567", "89", 7);
|
||||
}
|
||||
#[test]
|
||||
fn insert_single()
|
||||
{
|
||||
insert!("123456789"; "12346789", "5",4);
|
||||
}
|
||||
}
|
@ -1,26 +0,0 @@
|
||||
//! Local state change error
|
||||
use super::*;
|
||||
use std::{
|
||||
error,
|
||||
fmt,
|
||||
};
|
||||
|
||||
/// Local state updating errors
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
BroadcastUpdate,
|
||||
Unknown,
|
||||
}
|
||||
impl error::Error for Error{}
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::BroadcastUpdate => write!(f, "failed to broadcast state body update"),
|
||||
_ => write!(f, "unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
//! Hosts `Imouto`'s `Kokoro`.
|
||||
|
||||
use super::*;
|
||||
use std::{
|
||||
pin::Pin,
|
||||
task::{
|
||||
Poll,
|
||||
Context,
|
||||
},
|
||||
mem,
|
||||
};
|
||||
use pin_project::{
|
||||
pin_project,
|
||||
};
|
||||
use futures::{
|
||||
future::Future,
|
||||
};
|
||||
use tokio::{
|
||||
task,
|
||||
sync::{
|
||||
oneshot,
|
||||
mpsc,
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Worker
|
||||
{
|
||||
Attached(task::JoinHandle<Result<(), eyre::Report>>),
|
||||
/// Worker has not been attached yet
|
||||
Suspended(Kokoro),
|
||||
/// Worker filed to spawn,
|
||||
Crashed,
|
||||
}
|
||||
|
||||
/// Host this `Kokoro`
|
||||
async fn work(mut kokoro: Kokoro, mut recv: mpsc::Receiver<Vec<Delta>>) -> Result<(), eyre::Report>
|
||||
{
|
||||
while let Some(new) = recv.recv().await {
|
||||
kokoro.apply(new).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
impl Worker
|
||||
{
|
||||
/// Host this suspended state worker with a channel to receive updates from.
|
||||
///
|
||||
/// # Panics
|
||||
/// If we are not in the `Suspended` state.
|
||||
pub fn host(&mut self, recv: mpsc::Receiver<Vec<Delta>>) -> WorkerHandle
|
||||
{
|
||||
match mem::replace(self, Self::Crashed) {
|
||||
Self::Suspended(kokoro) => {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
*self = Self::Attached(task::spawn(async move {
|
||||
let vl = work(kokoro, recv).await;
|
||||
tx.send(()).unwrap();
|
||||
vl
|
||||
}));
|
||||
WorkerHandle(rx)
|
||||
},
|
||||
_ => panic!("Attempted to host non-suspended worker"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A handle on a spawned worker
|
||||
#[pin_project]
|
||||
pub struct WorkerHandle(#[pin] oneshot::Receiver<()>);
|
||||
|
||||
impl Future for WorkerHandle
|
||||
{
|
||||
type Output = ();
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
this.0.poll(cx).map(|x| x.unwrap())
|
||||
}
|
||||
}
|
@ -1,101 +0,0 @@
|
||||
//! Mutating post body
|
||||
use super::*;
|
||||
|
||||
/// A message that can be mutated by deltas.
|
||||
pub type MessageSpan = Vec<char>;
|
||||
|
||||
/// Contains post deltas and an intermediate representation of the still-open post body.
|
||||
/// Created and modified with `Kokoro` worker instances.
|
||||
///
|
||||
/// This should not be created by itself, instead `Kokoro` should create instances of this, so that it can retain the `watch::Sender` and other such things.
|
||||
#[derive(Debug)]
|
||||
pub struct Karada
|
||||
{
|
||||
/// The post body so far as a vector of `char`s.
|
||||
pub(super) scape: Arc<RwLock<MessageSpan>>,
|
||||
/// All applied deltas so far. Last applied one is at the end.
|
||||
pub(super) deltas: Arc<RwLock<Vec<Delta>>>,
|
||||
|
||||
/// the latest render of the whole body string. Updated whenever a delta(s) are applied atomically.
|
||||
pub(super) current_body: watch::Receiver<String>,
|
||||
}
|
||||
|
||||
impl Karada
|
||||
{
|
||||
/// Clone the body string
|
||||
pub fn body(&self) -> String
|
||||
{
|
||||
self.current_body.borrow().to_owned()
|
||||
}
|
||||
|
||||
/// Create the deltas required to atomically transform current body into `new`.
|
||||
///
|
||||
/// Return the number of deltas added to `output`.
|
||||
pub fn create_body_deltas<T: BackInserter<Delta> + ?Sized, U: AsRef<str>>(&self, output: &mut T, new: U) -> usize
|
||||
{
|
||||
let body = self.body();
|
||||
let new = new.as_ref();
|
||||
delta::infer_deltas(output, &body[..], new)
|
||||
}
|
||||
|
||||
/// Consume this instance into a suspension
|
||||
///
|
||||
/// This will only acquire locks if needed, but since they might be needed, it must be awaited in case of `Kokoro` instances potentially owning the data.
|
||||
pub async fn into_suspended(self) -> Suspension
|
||||
{
|
||||
let scape: String = {
|
||||
let scape = self.scape;
|
||||
match Arc::try_unwrap(scape) { //try to unwrap if possible, to avoid acquiring useless lock
|
||||
Ok(scape) => scape.into_inner().into_iter().collect(),
|
||||
Err(scape) => scape.read().await.iter().collect(),
|
||||
}
|
||||
};
|
||||
let deltas: Vec<Delta> = {
|
||||
let deltas = self.deltas;
|
||||
match Arc::try_unwrap(deltas) {
|
||||
Ok(deltas) => deltas.into_inner(),
|
||||
Err(deltas) => deltas.read().await.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
Suspension{scape,deltas}
|
||||
}
|
||||
|
||||
pub(super) fn from_suspended(susp: Suspension, current_body: watch::Receiver<String>) -> Self
|
||||
{
|
||||
Self {
|
||||
scape: Arc::new(RwLock::new(susp.scape.chars().collect())),
|
||||
deltas: Arc::new(RwLock::new(susp.deltas)),
|
||||
current_body,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspension of [`Karada`](Karada).
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||
pub struct Suspension
|
||||
{
|
||||
pub(super) scape: String,
|
||||
pub(super) deltas: Vec<Delta>,
|
||||
}
|
||||
|
||||
use suspend::{
|
||||
Suspendable,
|
||||
SuspendStream,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
impl Suspendable for Suspension
|
||||
{
|
||||
async fn suspend<S: SuspendStream +Send+Sync + ?Sized>(self, into: &mut S) -> Result<(), suspend::Error>
|
||||
{
|
||||
let mut output = suspend::Object::new();
|
||||
output.insert("post-dynamic", self);
|
||||
into.set_object(output).await
|
||||
}
|
||||
async fn load<S: SuspendStream +Send+Sync+ ?Sized>(from: &mut S) -> Result<Self, suspend::Error>
|
||||
{
|
||||
let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?;
|
||||
input.try_get("post-dynamic").ok_or(suspend::Error::BadObject)
|
||||
}
|
||||
}
|
@ -1,128 +0,0 @@
|
||||
//! Handles updating posts
|
||||
use super::*;
|
||||
use std::{
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::{
|
||||
sync::{
|
||||
RwLock,
|
||||
watch,
|
||||
},
|
||||
};
|
||||
|
||||
mod karada;
|
||||
pub use karada::*;
|
||||
|
||||
mod delta;
|
||||
pub use delta::*;
|
||||
|
||||
mod work;
|
||||
pub use work::*;
|
||||
|
||||
//mod host;
|
||||
//pub use host::*;
|
||||
|
||||
mod error;
|
||||
|
||||
pub use error::Error as LocalError;
|
||||
|
||||
/// An open, as yet unfinied post
|
||||
#[derive(Debug)]
|
||||
pub struct Imouto
|
||||
{
|
||||
id: identity::PostID,
|
||||
|
||||
user: identity::User,
|
||||
|
||||
title: Option<String>,
|
||||
|
||||
karada: Karada,
|
||||
worker: Kokoro,
|
||||
|
||||
timestamp: post::PostTimestamp, //Note that `closed` should always be `None` in `Imouto`. We use this for post lifetimes and such
|
||||
}
|
||||
|
||||
use suspend::{
|
||||
Suspendable,
|
||||
SuspendStream,
|
||||
};
|
||||
|
||||
#[async_trait]
|
||||
impl Suspendable for Imouto
|
||||
{
|
||||
async fn suspend<S: SuspendStream +Send+Sync + ?Sized>(self, into: &mut S) -> Result<(), suspend::Error>
|
||||
{
|
||||
let mut output = suspend::Object::new();
|
||||
output.insert("id", self.id);
|
||||
output.insert("user", self.user);
|
||||
output.insert("title", self.title);
|
||||
output.insert("body", self.worker.into_suspended().await); // consume worker if possible
|
||||
//output.insert("hash", self.hash);
|
||||
output.insert("timestamp", self.timestamp);
|
||||
into.set_object(output).await
|
||||
}
|
||||
async fn load<S: SuspendStream +Send+Sync+ ?Sized>(from: &mut S) -> Result<Self, suspend::Error>
|
||||
{
|
||||
let mut input = from.get_object().await?.ok_or(suspend::Error::BadObject)?;
|
||||
macro_rules! get {
|
||||
($name:literal) => {
|
||||
input.try_get($name).ok_or_else(|| suspend::Error::MissingObject(std::borrow::Cow::Borrowed($name)))?
|
||||
};
|
||||
}
|
||||
let id = get!("id");
|
||||
let user = get!("user");
|
||||
let title = get!("title");
|
||||
let body = get!("body");
|
||||
//let hash = get!("hash");
|
||||
let timestamp = get!("timestamp");
|
||||
|
||||
let (karada, worker) = {
|
||||
let mut kokoro = Kokoro::from_suspended(body);
|
||||
(kokoro.spawn().unwrap(), kokoro)
|
||||
};
|
||||
Ok(Self {
|
||||
id,
|
||||
user,
|
||||
title,
|
||||
karada,
|
||||
worker,
|
||||
//hash,
|
||||
timestamp,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests
|
||||
{
|
||||
use super::*;
|
||||
#[tokio::test]
|
||||
async fn basic_ser()
|
||||
{
|
||||
let mut output = suspend::MemorySuspendStream::new();
|
||||
let mut lolis = std::iter::repeat_with(|| {
|
||||
let (karada, kokoro) = {
|
||||
let mut kokoro = Kokoro::new();
|
||||
(kokoro.spawn().unwrap(), kokoro)
|
||||
};
|
||||
Imouto {
|
||||
id: identity::PostID::new(),
|
||||
user: Default::default(),
|
||||
title: None,
|
||||
|
||||
karada,
|
||||
worker: kokoro,
|
||||
|
||||
timestamp: post::PostTimestamp::new()
|
||||
}
|
||||
});
|
||||
let imouto = lolis.next().unwrap();
|
||||
imouto.suspend(&mut output).await.expect("Suspension failed");
|
||||
|
||||
let imouto2 = lolis.next().unwrap();
|
||||
|
||||
let imouto3 = Imouto::load(&mut output).await.expect("Load failed");
|
||||
|
||||
assert_eq!(imouto2.karada.body(), imouto3.karada.body());
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
//! Worker that mutates `Kokoro`.
|
||||
use super::*;
|
||||
use tokio::{
|
||||
sync::{
|
||||
watch,
|
||||
oneshot,
|
||||
},
|
||||
};
|
||||
|
||||
/// Handles working on `Karada` instances.
|
||||
#[derive(Debug)]
|
||||
pub struct Kokoro
|
||||
{
|
||||
scape: Arc<RwLock<MessageSpan>>,
|
||||
deltas: Arc<RwLock<Vec<Delta>>>,
|
||||
update_body: watch::Sender<String>,
|
||||
|
||||
body_recv: Option<watch::Receiver<String>>,
|
||||
}
|
||||
|
||||
impl Kokoro
|
||||
{
|
||||
/// Create a new instance. This instance can spawn a `Karada` instance.
|
||||
pub fn new() -> Self
|
||||
{
|
||||
let (tx, rx) = watch::channel(String::new());
|
||||
Self {
|
||||
scape: Arc::new(RwLock::new(MessageSpan::new())),
|
||||
deltas: Arc::new(RwLock::new(Vec::new())),
|
||||
|
||||
update_body: tx,
|
||||
body_recv: Some(rx),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new worker instance from a suspension of `Karada`.
|
||||
pub fn from_suspended(susp: Suspension) -> Self
|
||||
{
|
||||
let span = susp.scape.chars().collect();
|
||||
let (tx, rx) = watch::channel(susp.scape);
|
||||
Self {
|
||||
scape: Arc::new(RwLock::new(span)),
|
||||
deltas: Arc::new(RwLock::new(susp.deltas)),
|
||||
|
||||
update_body: tx,
|
||||
body_recv: Some(rx),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume this instance into a suspension
|
||||
///
|
||||
/// This will only acquire locks if needed, but since they might be needed, it must be awaited in case of `Kokoro` instances potentially owning the data.
|
||||
pub async fn into_suspended(self) -> Suspension
|
||||
{
|
||||
|
||||
let scape: String = {
|
||||
let scape = self.scape;
|
||||
match Arc::try_unwrap(scape) { //try to unwrap if possible, to avoid acquiring useless lock
|
||||
Ok(scape) => scape.into_inner().into_iter().collect(),
|
||||
Err(scape) => scape.read().await.iter().collect(),
|
||||
}
|
||||
};
|
||||
let deltas: Vec<Delta> = {
|
||||
let deltas = self.deltas;
|
||||
match Arc::try_unwrap(deltas) {
|
||||
Ok(deltas) => deltas.into_inner(),
|
||||
Err(deltas) => deltas.read().await.clone(),
|
||||
}
|
||||
};
|
||||
|
||||
Suspension{scape,deltas}
|
||||
}
|
||||
|
||||
/// Spawn one `Karada` instance. If this has already been called, it returns `None`.
|
||||
pub fn spawn(&mut self) -> Option<Karada>
|
||||
{
|
||||
self.body_recv.take()
|
||||
.map(|current_body| {
|
||||
Karada {
|
||||
scape: Arc::clone(&self.scape),
|
||||
deltas: Arc::clone(&self.deltas),
|
||||
|
||||
current_body,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Spawn a clone `Karada` into a new instance. This function can be called many times to yield `Karada` instances that are all identical and controlled by this instance.
|
||||
///
|
||||
/// # Panics
|
||||
/// If `spawn` was previously called.
|
||||
pub fn spawn_clone(&self) -> Karada
|
||||
{
|
||||
Karada {
|
||||
scape: Arc::clone(&self.scape),
|
||||
deltas: Arc::clone(&self.deltas),
|
||||
current_body: self.body_recv.as_ref().unwrap().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Apply a delta to this instance.
|
||||
pub async fn apply<I: IntoIterator<Item= Delta>>(&mut self, delta: I) -> Result<(), error::Error>
|
||||
{
|
||||
let (mut scape, mut deltas) = tokio::join!{
|
||||
self.scape.write(),
|
||||
self.deltas.write(),
|
||||
};
|
||||
for delta in delta.into_iter() {
|
||||
// Only start mutating now that both locks are writable. Is this needed, or can we do the mutation concurrently?
|
||||
delta.insert(scape.as_mut());
|
||||
deltas.push(delta);
|
||||
}
|
||||
|
||||
self.update_body.broadcast(scape.iter().collect()).map_err(|_| error::Error::BroadcastUpdate)
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
//! Structures for handling global and local post state.
|
||||
use super::*;
|
||||
|
||||
mod global;
|
||||
pub use global::*;
|
||||
|
||||
mod local;
|
||||
pub use local::*;
|
||||
|
||||
#[derive(Debug)]
|
||||
/// An open or closed post
|
||||
pub enum Post
|
||||
{
|
||||
Open(Imouto),
|
||||
Closed(post::Static),
|
||||
}
|
||||
|
||||
impl Post
|
||||
{
|
||||
/// Is this post open
|
||||
#[inline] pub fn is_open(&self) -> bool
|
||||
{
|
||||
if let Self::Open(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
@ -1,533 +0,0 @@
|
||||
//! Handles suspending and reloading posts
|
||||
use super::*;
|
||||
use std::{
|
||||
marker::{
|
||||
PhantomData,
|
||||
Unpin,
|
||||
Send,
|
||||
Sync,
|
||||
},
|
||||
io,
|
||||
collections::HashMap,
|
||||
ops::Range,
|
||||
borrow::{
|
||||
Cow,
|
||||
Borrow,
|
||||
},
|
||||
error,
|
||||
fmt,
|
||||
};
|
||||
use tokio::{
|
||||
prelude::*,
|
||||
io::{
|
||||
AsyncRead,
|
||||
AsyncWrite,
|
||||
},
|
||||
};
|
||||
|
||||
/// Represents an opaque bytes stream of serialised data to insert into `SuspendStream`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct Object
|
||||
{
|
||||
data: Vec<u8>,
|
||||
data_instances: HashMap<Cow<'static, str>, Range<usize>>,
|
||||
}
|
||||
|
||||
fn calc_len<T,I,U>(from: I) -> U
|
||||
where I: IntoIterator<Item = T>,
|
||||
T: Borrow<Range<U>>,
|
||||
U: std::cmp::Ord + Default + Copy,
|
||||
{
|
||||
let mut max = Default::default();
|
||||
|
||||
for i in from.into_iter()
|
||||
{
|
||||
let i = i.borrow().end;
|
||||
if i > max {
|
||||
max = i
|
||||
}
|
||||
}
|
||||
max
|
||||
}
|
||||
|
||||
impl Object
|
||||
{
|
||||
|
||||
/// Create a new empty `Object`.
|
||||
#[inline] pub fn new() -> Self
|
||||
{
|
||||
Self{data: Vec::new(), data_instances: HashMap::new()}
|
||||
}
|
||||
|
||||
/// Are the internal mappings of this object valid?
|
||||
pub fn validate(&self) -> bool
|
||||
{
|
||||
let len = self.data.len();
|
||||
for (_, range) in self.data_instances.iter() {
|
||||
if range.end > len {//range.start + range.end > len {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
/// Try to get a value of type `T` from `name`.
|
||||
pub fn get<'a, T>(&'a mut self, name: impl Borrow<str>) -> Option<T>
|
||||
where T: Deserialize<'a>
|
||||
{
|
||||
if let Some(bytes) = self.get_bytes(name) {
|
||||
serde_cbor::from_slice(&bytes[..]).expect("Failed to deserialize CBOR value")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get a value of type `T` from `name`.
|
||||
pub fn try_get<'a, T>(&'a mut self, name: impl Borrow<str>) -> Option<T>
|
||||
where T: Deserialize<'a>
|
||||
{
|
||||
if let Some(bytes) = self.get_bytes(name) {
|
||||
serde_cbor::from_slice(&bytes[..]).ok()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Serialize and insert `value` into the stream with `name`.
|
||||
pub fn insert<T>(&mut self, name: impl Into<Cow<'static, str>>, value: T)
|
||||
where T: Serialize
|
||||
{
|
||||
let len = self.data.len();
|
||||
match serde_cbor::to_writer(&mut self.data, &value) {
|
||||
Ok(()) => {
|
||||
let nlen = self.data.len();
|
||||
self.data_instances.insert(name.into(), len..nlen).unwrap_none();
|
||||
},
|
||||
Err(err) => {
|
||||
self.data.resize(len, 0); //Roll back result
|
||||
panic!("Failed inserting CBOR object: {}", err) //TODO: Return Err instead of panic
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert bytes directly with this name
|
||||
pub fn insert_bytes(&mut self, name: impl Into<Cow<'static, str>>, bytes: impl AsRef<[u8]>)
|
||||
{
|
||||
let bytes= bytes.as_ref();
|
||||
let start = self.data.len();
|
||||
self.data.extend_from_slice(bytes);
|
||||
let end = self.data.len();
|
||||
|
||||
self.data_instances.insert(name.into(), start..end).unwrap_none();
|
||||
}
|
||||
|
||||
/// Insert a value's bytes directly with this name
|
||||
pub unsafe fn insert_value_raw<T,U>(&mut self, name: U, value: &T)
|
||||
where T: ?Sized,
|
||||
U: Into<Cow<'static, str>>
|
||||
{
|
||||
self.insert_bytes(name, bytes::refer(value))
|
||||
}
|
||||
|
||||
/// Try to get the bytes specified by name
|
||||
pub fn get_bytes(&self, name: impl Borrow<str>) -> Option<&[u8]>
|
||||
{
|
||||
if let Some(range) = self.data_instances.get(name.borrow()) {
|
||||
Some(&self.data[range.clone()])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Try to get the value spcified by name
|
||||
///
|
||||
/// # Panics
|
||||
/// If `T` cannot fit into the size of the range
|
||||
pub unsafe fn get_value_raw<T>(&self, name: impl Borrow<str>) -> 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_raw<T>(&self, name: impl Borrow<str>) -> Option<&T>
|
||||
{
|
||||
if let Some(bytes) = self.get_bytes(name) {
|
||||
if bytes.len() >= std::mem::size_of::<T>() {
|
||||
Some(bytes::derefer(bytes))
|
||||
} else {
|
||||
#[cfg(debug_assertions)] eprintln!("Warning! Likely data corruption as {} (size {}) cannot fit into {} bytes",std::any::type_name::<T>(), std::mem::size_of::<T>(), bytes.len());
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume into the data
|
||||
pub fn into_bytes(self) -> Box<[u8]>
|
||||
{
|
||||
let mut output = Vec::new(); //TOOO: Get cap
|
||||
|
||||
debug_assert!(self.validate(), "passing invalid object to serialise");
|
||||
|
||||
macro_rules! bin {
|
||||
($bytes:expr) => {
|
||||
{
|
||||
let bytes = $bytes;
|
||||
output.extend_from_slice(bytes.as_ref());
|
||||
}
|
||||
};
|
||||
(usize $value:expr) => {
|
||||
{
|
||||
use std::convert::TryInto;
|
||||
use byteorder::{
|
||||
WriteBytesExt,
|
||||
LittleEndian,
|
||||
};
|
||||
let val: u64 = $value.try_into().expect("Value could not fit into `u64`");
|
||||
WriteBytesExt::write_u64::<LittleEndian>(&mut output,val).expect("Failed to append `u64` to output buffer");
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
bin!(usize self.data_instances.len());
|
||||
for (name, &Range{start, end}) in self.data_instances.iter() {
|
||||
let name = name.as_bytes();
|
||||
|
||||
bin!(usize name.len());
|
||||
bin!(name);
|
||||
|
||||
bin!(usize start);
|
||||
bin!(usize end);
|
||||
}
|
||||
|
||||
bin!(usize self.data.len()); //for additional checks
|
||||
output.extend(self.data);
|
||||
|
||||
output.into_boxed_slice()
|
||||
}
|
||||
|
||||
/// Read an object from a stream
|
||||
pub async fn from_stream<T>(input: &mut T) -> io::Result<Self>
|
||||
where T: AsyncRead + Unpin + ?Sized
|
||||
{
|
||||
let mut ext_buf = Vec::new();
|
||||
|
||||
macro_rules! bin {
|
||||
(usize) => {
|
||||
{
|
||||
use std::convert::TryFrom;
|
||||
let value: u64 = input.read_u64().await?;
|
||||
usize::try_from(value).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "u64 cannot fit in usize"))?
|
||||
}
|
||||
};
|
||||
($size:expr) => {
|
||||
{
|
||||
let sz = $size;
|
||||
ext_buf.resize(sz, 0);
|
||||
assert_eq!(input.read_exact(&mut ext_buf[..sz]).await?, ext_buf.len());
|
||||
&ext_buf[..sz]
|
||||
}
|
||||
}
|
||||
}
|
||||
let entries = bin!(usize);
|
||||
|
||||
let mut instances = HashMap::with_capacity(entries);
|
||||
|
||||
for _i in 0..entries
|
||||
{
|
||||
let name_len = bin!(usize);
|
||||
|
||||
let name_bytes = bin!(name_len);
|
||||
let name_str = std::str::from_utf8(name_bytes).map_err(|_| io::Error::new(io::ErrorKind::InvalidData,"item name was corrupted"))?;
|
||||
|
||||
let start = bin!(usize);
|
||||
let end = bin!(usize);
|
||||
|
||||
instances.insert(Cow::Owned(name_str.to_owned()), start..end);
|
||||
}
|
||||
|
||||
let expected_len = bin!(usize);
|
||||
if expected_len != calc_len(instances.iter().map(|(_, v)| v))
|
||||
{
|
||||
return Err(io::Error::new(io::ErrorKind::InvalidData, "expected and read sizes differ"));
|
||||
}
|
||||
|
||||
let mut data = vec![0; expected_len];
|
||||
assert_eq!(input.read_exact(&mut data[..]).await?, expected_len);
|
||||
|
||||
Ok(Self {
|
||||
data,
|
||||
data_instances: instances,
|
||||
})
|
||||
}
|
||||
|
||||
/// Write this instance into an async stream
|
||||
pub async fn into_stream<T>(&self, output: &mut T) -> io::Result<usize>
|
||||
where T: AsyncWrite + Unpin + ?Sized
|
||||
{
|
||||
//eprintln!("{}: {:?}", self.data.len(), self);
|
||||
debug_assert!(self.validate(), "passing invalid object to serialise");
|
||||
|
||||
let mut written=0usize;
|
||||
macro_rules! bin {
|
||||
($bytes:expr) => {
|
||||
{
|
||||
let bytes = $bytes;
|
||||
output.write_all(bytes).await?;
|
||||
written+=bytes.len();
|
||||
}
|
||||
};
|
||||
(usize $value:expr) => {
|
||||
{
|
||||
use std::convert::TryInto;
|
||||
let val: u64 = $value.try_into().map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "size cannot fit in u64"))?;
|
||||
output.write_u64(val).await?;
|
||||
written += std::mem::size_of::<u64>();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unsafe {
|
||||
bin!(usize self.data_instances.len());
|
||||
for (name, &Range{start, end}) in self.data_instances.iter() {
|
||||
let name = name.as_bytes();
|
||||
|
||||
bin!(usize name.len());
|
||||
bin!(name);
|
||||
|
||||
bin!(usize start);
|
||||
bin!(usize end);
|
||||
}
|
||||
}
|
||||
|
||||
bin!(usize self.data.len()); //for additional checks
|
||||
bin!(&self.data);
|
||||
Ok(written)
|
||||
}
|
||||
}
|
||||
|
||||
/// A suspend stream represents a stream of objects of _the same type_. Can be any number of them, but they all must be for the same type.
|
||||
#[async_trait]
|
||||
pub trait SuspendStream
|
||||
{
|
||||
/// Write an object into the opaque stream.
|
||||
async fn set_object(&mut self, obj: Object) -> Result<(), Error>;
|
||||
/// Read an object from the opaque stream.
|
||||
async fn get_object(&mut self) -> Result<Option<Object>, Error>;
|
||||
}
|
||||
|
||||
/// An error that occoured in a suspend operation
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
BadObject,
|
||||
MissingObject(Cow<'static, str>),
|
||||
Corruption,
|
||||
IO(io::Error),
|
||||
|
||||
Other(eyre::Report),
|
||||
Unknown,
|
||||
}
|
||||
impl error::Error for Error
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
Some(match &self {
|
||||
Self::IO(io) => io,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::BadObject => write!(f, "unexpected object in stream"),
|
||||
Self::MissingObject(string) => write!(f, "missing object from stream: {}", string),
|
||||
Self::Corruption => write!(f, "data stream corruption"),
|
||||
Self::IO(_) => write!(f, "i/o error"),
|
||||
Self::Other(report) => write!(f, "internal: {}", report),
|
||||
_ => write!(f, "unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error
|
||||
{
|
||||
#[inline] fn from(from: io::Error) -> Self
|
||||
{
|
||||
Self::IO(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<eyre::Report> for Error
|
||||
{
|
||||
fn from(from: eyre::Report) -> Self
|
||||
{
|
||||
Self::Other(from)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// A suspendable type, that can save and reload its data atomically
|
||||
#[async_trait]
|
||||
pub trait Suspendable: Sized
|
||||
{
|
||||
async fn suspend<S: SuspendStream + Send + Sync+ ?Sized>(self, into: &mut S) -> Result<(), Error>;
|
||||
async fn load<S: SuspendStream + Send+ Sync+?Sized>(from: &mut S) -> Result<Self, Error>;
|
||||
}
|
||||
|
||||
/// An in-memory `SuspendStream`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MemorySuspendStream(Vec<u8>);
|
||||
|
||||
impl MemorySuspendStream
|
||||
{
|
||||
/// Create a new empty instance
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self(Vec::new())
|
||||
}
|
||||
|
||||
/// Create from a vector of bytes
|
||||
pub fn from_bytes(from: impl Into<Vec<u8>>) -> Self
|
||||
{
|
||||
Self(from.into())
|
||||
}
|
||||
|
||||
/// Create from a slice of bytes
|
||||
pub fn from_slice(from: impl AsRef<[u8]>) -> Self
|
||||
{
|
||||
Self(Vec::from(from.as_ref()))
|
||||
}
|
||||
|
||||
/// Return the internal bytes
|
||||
pub fn into_bytes(self) -> Vec<u8>
|
||||
{
|
||||
self.0
|
||||
}
|
||||
|
||||
/// The internal buffer
|
||||
pub fn buffer(&self) -> &Vec<u8>
|
||||
{
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// The internal buffer
|
||||
pub fn buffer_mut(&mut self) -> &mut Vec<u8>
|
||||
{
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for MemorySuspendStream
|
||||
{
|
||||
fn as_ref(&self) -> &[u8]
|
||||
{
|
||||
&self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl AsMut<[u8]> for MemorySuspendStream
|
||||
{
|
||||
fn as_mut(&mut self) -> &mut [u8]
|
||||
{
|
||||
&mut self.0[..]
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<u8>> for MemorySuspendStream
|
||||
{
|
||||
#[inline] fn from(from: Vec<u8>) -> Self
|
||||
{
|
||||
Self(from.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<[u8]>> for MemorySuspendStream
|
||||
{
|
||||
fn from(from: Box<[u8]>) -> Self
|
||||
{
|
||||
Self::from_bytes(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MemorySuspendStream> for Box<[u8]>
|
||||
{
|
||||
fn from(from: MemorySuspendStream) -> Self
|
||||
{
|
||||
from.0.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<MemorySuspendStream> for Vec<u8>
|
||||
{
|
||||
#[inline] fn from(from: MemorySuspendStream) -> Self
|
||||
{
|
||||
from.0
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SuspendStream for MemorySuspendStream
|
||||
{
|
||||
async fn get_object(&mut self) -> Result<Option<Object>, Error> {
|
||||
if self.0.len() ==0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut ptr = &self.0[..];
|
||||
let vl = Object::from_stream(&mut ptr).await?;
|
||||
let diff = (ptr.as_ptr() as usize) - ((&self.0[..]).as_ptr() as usize);
|
||||
self.0.drain(0..diff);
|
||||
|
||||
Ok(Some(vl))
|
||||
}
|
||||
async fn set_object(&mut self, obj: Object) -> Result<(), Error> {
|
||||
obj.into_stream(&mut self.0).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Suspend a single object to memory
|
||||
pub async fn oneshot<T: Suspendable>(value: T) -> Result<Vec<u8>, Error>
|
||||
{
|
||||
let mut output = MemorySuspendStream::new();
|
||||
value.suspend(&mut output).await?;
|
||||
Ok(output.into_bytes())
|
||||
}
|
||||
|
||||
/// Load a single value from memory
|
||||
pub async fn single<T: Suspendable>(from: impl AsRef<[u8]>) -> Result<T, Error>
|
||||
{
|
||||
struct BorrowedStream<'a>(&'a [u8]);
|
||||
|
||||
#[async_trait]
|
||||
impl<'a> SuspendStream for BorrowedStream<'a>
|
||||
{
|
||||
async fn get_object(&mut self) -> Result<Option<Object>, Error> {
|
||||
if self.0.len() ==0 {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
let mut ptr = &self.0[..];
|
||||
let vl = Object::from_stream(&mut ptr).await?;
|
||||
let diff = (ptr.as_ptr() as usize) - ((&self.0[..]).as_ptr() as usize);
|
||||
self.0 = &self.0[diff..];
|
||||
|
||||
Ok(Some(vl))
|
||||
}
|
||||
async fn set_object(&mut self, _: Object) -> Result<(), Error> {
|
||||
panic!("Cannot write to borrowed stream")
|
||||
}
|
||||
}
|
||||
|
||||
let bytes = from.as_ref();
|
||||
let mut stream = BorrowedStream(bytes);
|
||||
|
||||
T::load(&mut stream).await
|
||||
}
|
@ -1,109 +0,0 @@
|
||||
//! Tripcode. TODO: Use kana-hash
|
||||
use super::*;
|
||||
use khash::{
|
||||
ctx::{
|
||||
self,
|
||||
Algorithm,
|
||||
},
|
||||
salt,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
error,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Error(khash::error::Error);
|
||||
|
||||
impl From<khash::error::Error> for Error
|
||||
{
|
||||
#[inline] fn from(from: khash::error::Error) -> Self
|
||||
{
|
||||
Self(from)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "failed to compute tripcode")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl error::Error for Error
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
Some(&self.0)
|
||||
}
|
||||
}
|
||||
|
||||
///A kana-hash or special tripcode
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub enum Tripcode
|
||||
{
|
||||
/// A secure tripcode computed with the user set configured salt
|
||||
Secure(String),
|
||||
/// A normal tripcode computed with the default embedded salt
|
||||
Normal(String),
|
||||
/// A special tripcode with a pre-set string
|
||||
Special(String),
|
||||
}
|
||||
|
||||
impl Tripcode
|
||||
{
|
||||
/// Generate a new normal tripcode
|
||||
pub fn new_normal(from: impl AsRef<[u8]>) -> Result<Self, Error>
|
||||
{
|
||||
Ok(Self::Normal(khash::generate(&ctx::Context::new(Algorithm::Sha256Truncated, salt::Salt::internal()), from)?))
|
||||
}
|
||||
/// Generate a new `secure' tripcode.
|
||||
///
|
||||
/// # Panics
|
||||
/// If global config has not been set yet.
|
||||
pub fn new_secure(from: impl AsRef<[u8]>) -> Result<Self, Error>
|
||||
{
|
||||
Ok(Self::Secure(khash::generate(&ctx::Context::new(Algorithm::Sha256Truncated, salt::Salt::fixed(config::get().tripcode_salt)), from)?))
|
||||
}
|
||||
|
||||
/// Get the internal string representing the tripcode.
|
||||
///
|
||||
/// # Notes
|
||||
/// This does not include the prefixes.
|
||||
pub fn as_str(&self) -> &str
|
||||
{
|
||||
match self {
|
||||
Self::Secure(s) | Self::Normal(s) | Self::Special(s) => s.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume the instance returning the inner string.
|
||||
///
|
||||
/// # Notes
|
||||
/// This does not include the prefixes.
|
||||
pub fn into_inner(self) -> String
|
||||
{
|
||||
match self {
|
||||
Self::Secure(s) | Self::Normal(s) | Self::Special(s) => s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Tripcode
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::Secure(sec) => write!(f, "!!{}", sec),
|
||||
Self::Normal(nor) => write!(f, "!{}", nor),
|
||||
Self::Special(spec) => write!(f, "{}", spec),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -1,61 +0,0 @@
|
||||
//! Internal errors
|
||||
use super::*;
|
||||
use std::{
|
||||
fmt,
|
||||
error,
|
||||
net::SocketAddr,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[non_exhaustive]
|
||||
pub enum Error {
|
||||
Denied(SocketAddr, bool),
|
||||
TimeoutReached,
|
||||
NoResponse,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct HandleError;
|
||||
|
||||
impl Error
|
||||
{
|
||||
/// Print this error as a warning
|
||||
#[inline(never)] #[cold] pub fn warn(self) -> Self
|
||||
{
|
||||
warn!("{}", self);
|
||||
self
|
||||
}
|
||||
|
||||
/// Print this error as info
|
||||
#[inline(never)] #[cold] pub fn info(self) -> Self
|
||||
{
|
||||
info!("{}", self);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error{}
|
||||
impl error::Error for HandleError{}
|
||||
impl fmt::Display for Error
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
match self {
|
||||
Self::Denied(sock, true) => write!(f, "denied connection (explicit): {}", sock),
|
||||
Self::Denied(sock, _) => write!(f, "denied connection (implicit): {}", sock),
|
||||
Self::TimeoutReached => write!(f, "timeout reached"),
|
||||
Self::NoResponse => write!(f, "no handler for this request"),
|
||||
_ => write!(f, "unknown error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for HandleError
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "handle response had already been sent or timed out by the time we tried to access it")
|
||||
}
|
||||
}
|
||||
|
@ -1,269 +0,0 @@
|
||||
//! Handle web serving and managing state of web clients
|
||||
use super::*;
|
||||
use std::{
|
||||
sync::{
|
||||
Arc,
|
||||
Weak,
|
||||
},
|
||||
marker::{
|
||||
Send, Sync,
|
||||
},
|
||||
iter,
|
||||
};
|
||||
use hyper::{
|
||||
service::{
|
||||
make_service_fn,
|
||||
service_fn,
|
||||
},
|
||||
server::{
|
||||
Server,
|
||||
conn::AddrStream,
|
||||
},
|
||||
Request,
|
||||
Response,
|
||||
Body,
|
||||
};
|
||||
use futures::{
|
||||
TryStreamExt as _,
|
||||
};
|
||||
use cidr::{
|
||||
Cidr,
|
||||
};
|
||||
use tokio::{
|
||||
sync::{
|
||||
RwLock,
|
||||
mpsc,
|
||||
},
|
||||
};
|
||||
|
||||
pub mod error;
|
||||
pub mod route;
|
||||
|
||||
/// A unique ID generated each time a request is sent through router.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Nonce(uuid::Uuid);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Handle
|
||||
{
|
||||
state: Arc<State>,
|
||||
nonce: Nonce,
|
||||
req: Arc<Request<Body>>,
|
||||
/// We can let multiple router hooks mutate body if they desire. Such as adding headers, etc.
|
||||
resp: Arc<RwLock<Response<Body>>>,
|
||||
}
|
||||
|
||||
impl Handle
|
||||
{
|
||||
/// Attempt to upgrade the response handle into a potentially mutateable `Response`.
|
||||
///
|
||||
/// Function fails if the reference count to the response has expired (i.e. the response has been sent or timed out already)
|
||||
pub fn access_response(&self) -> Result<Arc<RwLock<Response<Body>>>, error::HandleError>
|
||||
{
|
||||
Ok(self.resp.clone())
|
||||
//self.resp.upgrade().ok_or(error::HandleError)
|
||||
}
|
||||
|
||||
/// Replace the response with a new one if possible.
|
||||
///
|
||||
/// Fails if `access_response()` fails.
|
||||
pub async fn set_response(&self, rsp: Response<Body>) -> Result<Response<Body>, error::HandleError>
|
||||
{
|
||||
use std::ops::DerefMut;
|
||||
match self.access_response() {
|
||||
Ok(resp) => Ok(std::mem::replace(resp.write().await.deref_mut(), rsp)),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Contains all web-server state
|
||||
#[derive(Debug)]
|
||||
pub struct State
|
||||
{
|
||||
config: config::Config,
|
||||
router: RwLock<route::Router<Handle>>,
|
||||
}
|
||||
|
||||
impl State
|
||||
{
|
||||
/// Create a new state with this specific config instance.
|
||||
///
|
||||
/// # Notes
|
||||
/// You'll almost always want to use the *global* config instance, in which case use `default()` to create this.
|
||||
pub fn new(config: config::Config) -> Self
|
||||
{
|
||||
Self{
|
||||
config,
|
||||
router: RwLock::new(route::Router::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for State
|
||||
{
|
||||
#[inline]
|
||||
fn default() -> Self
|
||||
{
|
||||
Self::new(config::get().clone())
|
||||
}
|
||||
}
|
||||
|
||||
fn mask_contains(mask: &[cidr::IpCidr], value: &std::net::IpAddr) -> bool
|
||||
{
|
||||
for mask in mask.iter()
|
||||
{
|
||||
if mask.contains(value) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn handle_test(state: Arc<State>) -> tokio::task::JoinHandle<()>
|
||||
{
|
||||
tokio::task::spawn(async move {
|
||||
let (hook, mut recv) = {
|
||||
let mut router = state.router.write().await;
|
||||
router.hook(None, route::PrefixRouter::new("/hello"))
|
||||
};
|
||||
while let Some((uri, handle)) = recv.recv().await
|
||||
{
|
||||
match handle.set_response(Response::builder()
|
||||
.status(200)
|
||||
.body(format!("Hello world! You are at {}", uri).into())
|
||||
.unwrap()).await {
|
||||
Ok(_) => (),
|
||||
Err(e) => {
|
||||
error!("{}", e);
|
||||
break;
|
||||
},
|
||||
}
|
||||
}
|
||||
{
|
||||
let mut router = state.router.write().await;
|
||||
router.unhook(iter::once(hook));
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_conn(state: Arc<State>, req: Request<Body>) -> Result<Response<Body>, error::Error>
|
||||
{
|
||||
let response = Arc::new(RwLock::new(Response::new(Body::empty())));
|
||||
|
||||
let nonce = Nonce(uuid::Uuid::new_v4());
|
||||
let req = Arc::new(req);
|
||||
let resp_num = {
|
||||
let resp = Arc::clone(&response);
|
||||
async {
|
||||
let mut route = state.router.write().await;
|
||||
let handle = Handle {
|
||||
state: state.clone(),
|
||||
nonce,
|
||||
req: Arc::clone(&req),
|
||||
resp,
|
||||
};
|
||||
match route.dispatch(req.method(), req.uri().path(), handle, state.config.req_timeout_local).await {
|
||||
Ok(num) => {
|
||||
num
|
||||
},
|
||||
Err((num, _)) => {
|
||||
num
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
tokio::pin!(resp_num);
|
||||
|
||||
match match state.config.req_timeout_global {
|
||||
Some(timeout) => tokio::time::timeout(timeout, resp_num).await,
|
||||
None => Ok(resp_num.await),
|
||||
} {
|
||||
Ok(0) => {
|
||||
// No handlers matched this
|
||||
trace!(" x {}", req.uri().path());
|
||||
Ok(Response::builder()
|
||||
.status(404)
|
||||
.body("404 not found".into())
|
||||
.unwrap())
|
||||
},
|
||||
Ok(_) => {
|
||||
let resp = {
|
||||
let mut resp = response;
|
||||
|
||||
loop {
|
||||
match Arc::try_unwrap(resp) {
|
||||
Err(e) => {
|
||||
resp = e;
|
||||
tokio::task::yield_now().await;
|
||||
},
|
||||
Ok(n) => break n,
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(resp.into_inner())
|
||||
},
|
||||
Err(_) => {
|
||||
// Timeout reached
|
||||
Err(error::Error::TimeoutReached.info())
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn serve(state: State) -> Result<(), eyre::Report>
|
||||
{
|
||||
cfg_debug!(if {
|
||||
if &state.config != config::get() {
|
||||
panic!("Our config is not the same as global? This is unsound.");
|
||||
}
|
||||
} else {
|
||||
|
||||
if &state.config != config::get() {
|
||||
warn!("Our config is not the same as global? This is unsound.");
|
||||
}
|
||||
});
|
||||
let h = {
|
||||
let state = Arc::new(state);
|
||||
|
||||
let h = handle_test(state.clone());
|
||||
|
||||
let service = make_service_fn(|conn: &AddrStream| {
|
||||
let state = Arc::clone(&state);
|
||||
|
||||
let remote_addr = conn.remote_addr();
|
||||
let remote_ip = remote_addr.ip();
|
||||
let denied = mask_contains(&state.config.deny_mask[..], &remote_ip);
|
||||
let allowed = mask_contains(&state.config.accept_mask[..], &remote_ip);
|
||||
async move {
|
||||
if denied {
|
||||
Err(error::Error::Denied(remote_addr, true).warn())
|
||||
} else if allowed || state.config.accept_default {
|
||||
trace!("Accepted conn: {}", remote_addr);
|
||||
|
||||
Ok(service_fn(move |req: Request<Body>| {
|
||||
handle_conn(Arc::clone(&state), req)
|
||||
}))
|
||||
} else {
|
||||
Err(error::Error::Denied(remote_addr,false).info())
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&state.config.listen).serve(service)
|
||||
.with_graceful_shutdown(async {
|
||||
tokio::signal::ctrl_c().await.expect("Failed to catch SIGINT");
|
||||
info!("Going down for shutdown now!");
|
||||
});
|
||||
|
||||
server.await?;
|
||||
// remove all handles now
|
||||
let mut wr= state.router.write().await;
|
||||
wr.clear();
|
||||
|
||||
h
|
||||
};
|
||||
trace!("server down");
|
||||
h.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
@ -1,254 +0,0 @@
|
||||
//! Basic router
|
||||
use super::*;
|
||||
use hyper::{
|
||||
Method,
|
||||
};
|
||||
use std::{
|
||||
fmt,
|
||||
marker::{
|
||||
Send,
|
||||
Sync,
|
||||
},
|
||||
iter,
|
||||
};
|
||||
use tokio::{
|
||||
sync::{
|
||||
mpsc::{
|
||||
self,
|
||||
error::SendTimeoutError,
|
||||
},
|
||||
},
|
||||
time,
|
||||
};
|
||||
use futures::{
|
||||
future::{
|
||||
self,
|
||||
Future,
|
||||
},
|
||||
};
|
||||
use generational_arena::{
|
||||
Index,
|
||||
Arena,
|
||||
};
|
||||
|
||||
pub trait UriRoute
|
||||
{
|
||||
fn is_match(&self, uri: &str) -> bool;
|
||||
#[inline] fn as_string(&self) -> &str
|
||||
{
|
||||
""
|
||||
}
|
||||
|
||||
#[inline] fn type_name(&self) -> &str
|
||||
{
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
|
||||
#[inline] fn mutate_uri(&self, uri: String) -> String
|
||||
{
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
impl UriRoute for str
|
||||
{
|
||||
#[inline] fn is_match(&self, uri: &str) -> bool {
|
||||
self.eq(uri)
|
||||
}
|
||||
#[inline] fn as_string(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> UriRoute for T
|
||||
{
|
||||
#[inline] fn is_match(&self, uri: &str) -> bool {
|
||||
self.as_ref().eq(uri)
|
||||
}
|
||||
#[inline] fn as_string(&self) -> &str {
|
||||
self.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl UriRoute for regex::Regex
|
||||
{
|
||||
#[inline] fn is_match(&self, uri: &str) -> bool {
|
||||
self.test(uri)
|
||||
}
|
||||
#[inline] fn as_string(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
/// A router for all under a prefix
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct PrefixRouter(String);
|
||||
|
||||
impl PrefixRouter
|
||||
{
|
||||
/// Create a new instance with this string
|
||||
pub fn new(string: impl Into<String>) -> Self
|
||||
{
|
||||
Self(string.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl UriRoute for PrefixRouter
|
||||
{
|
||||
#[inline] fn is_match(&self, uri: &str) -> bool {
|
||||
uri.starts_with(self.0.as_str())
|
||||
}
|
||||
#[inline] fn as_string(&self) -> &str {
|
||||
self.0.as_str()
|
||||
}
|
||||
|
||||
fn mutate_uri(&self, mut uri: String) -> String {
|
||||
uri.replace_range(..self.0.len(), "");
|
||||
uri
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for PrefixRouter
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "{}*", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Contains a routing table
|
||||
#[derive(Debug)]
|
||||
pub struct Router<T: Send>
|
||||
{
|
||||
routes: Arena<(Option<Method>, OpaqueDebug<Box<dyn UriRoute + Send + Sync + 'static>>, mpsc::Sender<(String, T)>)>,
|
||||
}
|
||||
|
||||
impl<T: Send> fmt::Display for Router<T>
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result
|
||||
{
|
||||
write!(f, "Router {{ routes: ")?;
|
||||
for (i, (method, route, _)) in self.routes.iter() {
|
||||
writeln!(f, "\t ({:?} => ({:?}, {} ({:?}))),", i, method, route.type_name(), route.as_string())?;
|
||||
}
|
||||
write!(f, "}}")
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Send + Clone> Router<T>
|
||||
{
|
||||
/// Create an empty routing table
|
||||
pub fn new() -> Self
|
||||
{
|
||||
Self {
|
||||
routes: Arena::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Push a new route into the router.
|
||||
///
|
||||
/// # Returns
|
||||
/// The hook's new index, and the receiver that `dispatch()` sends to.
|
||||
pub fn hook<Uri: UriRoute + Send + Sync + 'static>(&mut self, method: Option<Method>, uri: Uri) -> (Index, mpsc::Receiver<(String, T)>)
|
||||
{
|
||||
let (tx, rx) = mpsc::channel(config::get_or_default().dos_max);
|
||||
(self.routes.insert((method, OpaqueDebug::new(Box::new(uri)), tx)), rx)
|
||||
}
|
||||
|
||||
/// Remove all hooks
|
||||
pub fn clear(&mut self)
|
||||
{
|
||||
self.routes.clear();
|
||||
}
|
||||
|
||||
/// Dispatch the URI location across this router, sending to all that match it.
|
||||
///
|
||||
/// # Timeout
|
||||
/// The timeout is waited on the *individual* dispatches. If you want a global timeout, please timeout the future returned by this function instead.
|
||||
/// Timed-out dispatches are counted the same as sending errors.
|
||||
///
|
||||
/// # Returns
|
||||
/// When one or more dispatchers match but faile, `Err` is returned. Inside the `Err` tuple is the amount of successful dispatches, and also a vector containing the indecies of the failed hook sends.
|
||||
pub async fn dispatch(&mut self, method: &Method, uri: impl AsRef<str>, nonce: T, timeout: Option<time::Duration>) -> Result<usize, (usize, Vec<Index>)>
|
||||
{
|
||||
let string = uri.as_ref();
|
||||
let mut success=0usize;
|
||||
let vec: Vec<_> =
|
||||
future::join_all(self.routes.iter_mut()
|
||||
.filter_map(|(i, (a_method, route, sender))| {
|
||||
match a_method {
|
||||
Some(x) if x != method => None,
|
||||
_ => {
|
||||
if route.is_match(string) {
|
||||
trace!("{:?} `{}`: -> {}",i, route.as_string(), string);
|
||||
let timeout = timeout.clone();
|
||||
let nonce= nonce.clone();
|
||||
macro_rules! send {
|
||||
() => {
|
||||
{
|
||||
let string = route.mutate_uri(string.to_owned());
|
||||
match timeout {
|
||||
None => sender.send((string, nonce)).await
|
||||
.map_err(|e| SendTimeoutError::Closed(e.0)),
|
||||
Some(time) => sender.send_timeout((string, nonce), time).await
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Some(async move {
|
||||
match send!() {
|
||||
Err(SendTimeoutError::Closed(er)) => {
|
||||
error!("{:?}: Dispatch failed on hooked route for `{}`", i, er.0);
|
||||
Err(i)
|
||||
},
|
||||
Err(SendTimeoutError::Timeout(er)) => {
|
||||
warn!("{:?}: Dispatch timed out on hooked route for `{}`", i, er.0);
|
||||
Err(i)
|
||||
},
|
||||
_ => Ok(()),
|
||||
}
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
},
|
||||
}
|
||||
})).await.into_iter()
|
||||
.filter_map(|res| {
|
||||
if res.is_ok() {
|
||||
success+=1;
|
||||
}
|
||||
res.err()
|
||||
}).collect();
|
||||
if vec.len() > 0 {
|
||||
Err((success, vec))
|
||||
} else {
|
||||
Ok(success)
|
||||
}
|
||||
}
|
||||
|
||||
/// Forcefully dispatch `uri` on hook `which`, regardless of method or URI matching.
|
||||
///
|
||||
/// # Returns
|
||||
/// If `which` is not contained within the table, immediately returns `None`, otherwise returns a future that completes when the dispatch is complete.
|
||||
/// Note: This future must be `await`ed for the dispatch to happen.
|
||||
pub fn dispatch_force(&mut self, which: Index, uri: String, nonce: T, timeout: Option<time::Duration>) -> Option<impl Future<Output = Result<(), SendTimeoutError<(String, T)>>> + '_>
|
||||
{
|
||||
self.routes.get_mut(which).map(move |(_,_,send)| {
|
||||
match timeout {
|
||||
Some(timeout) => send.send_timeout((uri, nonce), timeout).boxed(),
|
||||
None => send.send((uri, nonce)).map(|res| res.map_err(|e| SendTimeoutError::Closed(e.0))).boxed(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/// Attempt to unhook these hooks. If one or more of the provided indecies does not exist in the routing table, it is ignored.
|
||||
pub fn unhook<I>(&mut self, items: I)
|
||||
where I: IntoIterator<Item = Index>
|
||||
{
|
||||
for item in items.into_iter() {
|
||||
self.routes.remove(item);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue