@ -1,119 +1,289 @@
//! Container for switching between un/encrypted stream
use super ::* ;
use std ::mem ::{ self , MaybeUninit , ManuallyDrop } ;
use std ::ops ::{ Drop , Deref , DerefMut } ;
use std ::ptr ;
use std ::fmt ;
use chacha20stream ::AsyncSink ;
use std ::mem ;
use chacha20stream ::{
AsyncSink ,
Key , IV ,
} ;
use tokio ::io ::AsyncWrite ;
use std ::{
pin ::Pin ,
task ::{ Context , Poll } ,
io ,
marker ::Unpin ,
} ;
bool_type ! ( pub Encrypted ; "Is the value encrypted?" ) ;
bool_type ! ( pub Encryption ; "What way are we en/decrypting?" = > Encrypt , Decrypt ) ;
#[ derive(Debug) ]
pub enum DualStreamKind < S >
pub enum DualStream < S >
{
/// If there is a panic while switching modes, the stream is left in this invariant state.
Poisoned ,
Encrypted ( AsyncSink < S > ) ,
Plain ( S ) ,
}
pub struct DualStream < S > ( MaybeUninit < Box < DualStreamKind < S > > > ) ;
//pub type Error = openssl::error::ErrorStack;
impl < S : fmt ::Debug > fmt ::Debug for DualStream < S >
impl < ' a , S : AsyncWrite + Unpin > DualStream < S >
where S : ' a
{
fn fmt ( & self , f : & mut fmt ::Formatter < ' _ > ) -> fmt ::Result
/// Convert this stream into an encrypted one.
///
/// # Notes
/// This method makes sure to `flush()` the encrypted stream before dropping the cipher.
///
/// # Panics
/// If initialising the cipher fails, this method will panic.
/// # Poisons
/// If this method panics, then the inner stream will be dropped and this instance will be set to `Poisoned`. This is (currently) irrecoverable.
pub async fn to_plain ( & mut self ) -> io ::Result < ( ) >
{
fmt ::Debug ::fmt ( self . as_ref ( ) , f )
if ! self . is_encrypted ( ) {
// No need to do anything
return Ok ( ( ) ) ;
}
use tokio ::prelude ::* ;
self . flush ( ) . await ? ;
self . to_plain_now ( ) ;
Ok ( ( ) )
}
/// Convert this stream into an encrypted one.
///
/// # Notes
/// This method makes sure to `flush()` the encrypted stream before constructing the cipher.
///
/// # Panics
/// If initialising the cipher fails, this method will panic.
/// # Poisons
/// If this method panics, then the inner stream will be dropped and this instance will be set to `Poisoned`. This is (currently) irrecoverable.
pub async fn to_crypt ( & mut self , enc : Encryption , key : Key , iv : IV ) -> io ::Result < ( ) >
{
// NOTE: We can't skip this like `to_plain()` does by checking if the instance is already at `Encrypted` as the key/IV may differ.
use tokio ::prelude ::* ;
self . flush ( ) . await ? ;
self . to_crypt_now ( enc , key , iv ) ;
Ok ( ( ) )
}
}
impl < ' a , S : AsyncWrite > DualStream < S >
where S : ' a
{
/// Create a transparent wrapper
///
/// Identical to constructing the enum variant `Self::Plain` manually.
#[ inline(always) ] pub fn plain ( stream : S ) -> Self
{
Self ::Plain ( stream )
}
/// Construct an encrypting wrapper over this stream
///
/// # Panics
/// If constructing the cipher fails
#[ inline ] pub fn encrypt ( stream : S , key : Key , iv : IV ) -> Self
{
Self ::Encrypted ( AsyncSink ::encrypt ( stream , key , iv ) . expect ( "Initialising cipher failed" ) )
}
impl < S > DualStream < S >
{
fn as_mut_ref ( & mut self ) -> & mut Box < DualStreamKind < S > >
/// Construct a decrypting wrapper over this stream
///
/// # Panics
/// If constructing the cipher fails
#[ inline ] pub fn decrypt ( stream : S , key : Key , iv : IV ) -> Self
{
Self ::Encrypted ( AsyncSink ::decrypt ( stream , key , iv ) . expect ( "Initialising cipher failed" ) )
}
/// Construct an encrypting or decrypting wrapper over this stream
///
/// # Panics
/// If constructing the cipher fails
#[ inline(always) ] pub fn crypt ( stream : S , enc : Encryption , key : Key , iv : IV ) -> Self
{
// SAFETY: It is always initialised except exactly within the swap function
unsafe {
& mut * self . 0. as_mut_ptr ( )
match enc {
Encryption ::Encrypt = > Self ::encrypt ( stream , key , iv ) ,
Encryption ::Decrypt = > Self ::decrypt ( stream , key , iv ) ,
}
}
fn as_ref ( & self ) -> & Box < DualStreamKind < S > >
/// Is this stream set to encrypted?
#[ inline ] pub fn is_encrypted ( & self ) -> bool
{
// SAFETY: It is always initialised except exactly within the swap function
unsafe {
& * self . 0. as_ptr ( )
if let Self ::Encrypted ( _ ) = self {
true
} else {
false
}
}
/// Is this stream in an invalid state?
#[ inline(always) ] pub fn is_poisoned ( & self ) -> bool {
if let Self ::Poisoned = self {
true
} else {
false
}
}
pub fn
/// Move out of self, and in turn poison this insatance until self is assigned a proper value again.
#[ inline(always) ] fn poison ( & mut self ) -> Self
{
mem ::replace ( self , Self ::Poisoned )
}
/// Create explicit
pub fn new ( k : DualStreamKind < S > ) -> Self
/// Immediately convert this stream into an encrypted one.
///
/// # Notes
/// Make sure to `flush()` the stream **before** calling this or data may be lost.
///
/// # Panics
/// If initialising the cipher fails, this method will panic.
/// # Poisons
/// If this method panics, then the inner stream will be dropped and this instance will be set to `Poisoned`. This is (currently) irrecoverable.
//TODO: in chacha20stream: Add `try_encrypt()`, `try_decrypt()` which both return `Result<Self, (S, Error)>`, to not lose the inner stream if the cipher fails to initialise.
#[ inline ] pub fn to_crypt_now ( & mut self , enc : Encryption , key : Key , iv : IV )
{
Self ( MaybeUninit ::new ( Box ::new ( k ) ) )
let inner = match self . poison ( ) {
Self ::Encrypted ( enc ) = > enc . into_inner ( ) ,
Self ::Plain ( inner ) = > inner ,
_ = > panic! ( "Poisoned" ) ,
} ;
* self = Self ::Encrypted ( match enc {
Encryption ::Encrypt = > AsyncSink ::encrypt ( inner , key , iv ) ,
Encryption ::Decrypt = > AsyncSink ::decrypt ( inner , key , iv ) ,
} . expect ( "Initialising cipher failed" ) ) ;
}
/// Consume into explicit (non-swappable) dual stream
pub fn into_inner ( self ) -> Box < DualStreamKind < S > >
/// Immediately convert this stream into a plain one
///
/// # Notes
/// Make sure to `flush()` the stream **before** calling this or encrypted data may be lost.
///
/// # Panics
/// If dropping the cipher fails, this method will panic.
/// # Poisons
/// If this method panics, then the inner stream will be dropped and this instance will be set to `Poisoned`. This is (currently) irrecoverable.
#[ inline ] pub fn to_plain_now ( & mut self )
{
let mut md = ManuallyDrop ::new ( self ) ;
unsafe {
// We could just `read()` the pointer, but this is more semantiacally equivalent to moving the value, I think. Idk if it's more or less efficient or whatever.
mem ::replace ( & mut md . 0 , MaybeUninit ::uninit ( ) ) . assume_init ( )
}
* self = Self ::Plain ( match self . poison ( ) {
Self ::Plain ( p ) = > p ,
Self ::Encrypted ( e ) = > e . into_inner ( ) ,
_ = > panic! ( "Poisoned" ) ,
} ) ;
}
}
impl < S > Deref for DualStream < S >
{
type Target = DualStreamKind < S > ;
fn deref ( & self ) -> & Self ::Target {
self . as_ref ( )
/// A mutable reference to the inner (plain) stream, whether this instance is set to encrypted or not.
pub fn inner_plain_mut ( & mut self ) -> & mut S
{
match self {
Self ::Plain ( p ) = > p ,
Self ::Encrypted ( e ) = > e . inner_mut ( ) ,
_ = > panic! ( "Poisoned" )
}
}
}
impl < S > DerefMut for DualStream < S >
{
fn deref_mut ( & mut self ) -> & mut Self ::Target {
self . as_mut_ref ( )
/// A reference to the inner (plain) stream, whether this instance is set to encrypted or not.
pub fn inner_plain ( & self ) -> & S
{
match self {
Self ::Plain ( p ) = > p ,
Self ::Encrypted ( e ) = > e . inner ( ) ,
_ = > panic! ( "Poisoned" )
}
}
}
impl < S > From < Box < DualStreamKind < S > > > for DualStream < S >
{
fn from ( from : Box < DualStreamKind < S > > ) -> Self
/// As an immutable dynamic object
pub fn as_dyn ( & self ) -> & ( dyn AsyncWrite + ' a )
{
Self ( MaybeUninit ::new ( from ) )
match self {
Self ::Plain ( p ) = > p ,
Self ::Encrypted ( e ) = > e ,
_ = > panic! ( "Poisoned" )
}
}
}
impl < S > From < DualStreamKind < S > > for DualStream < S >
{
fn from ( from : DualStreamKind < S > ) -> Self
/// As a mutable dynamic object
pub fn as_dyn_mut ( & mut self ) -> & mut ( dyn AsyncWrite + ' a )
{
Self ::new ( from )
match self {
Self ::Plain ( p ) = > p ,
Self ::Encrypted ( e ) = > e ,
_ = > panic! ( "Poisoned" )
}
}
}
impl < S > From < DualStream < S > > for Box < DualStreamKind < S > >
{
fn from ( from : DualStream < S > ) -> Self
/// Consume into the inner (plain) stream
#[ inline ] pub fn into_inner ( self ) -> S
{
from . into_inner ( )
match self {
Self ::Plain ( p ) = > p ,
Self ::Encrypted ( e ) = > e . into_inner ( ) ,
_ = > panic! ( "Poisoned" )
}
}
}
impl < S > From < DualStream < S > > for DualStreamKind < S >
impl < S : AsyncWrite > AsyncWrite for DualStream < S >
{
fn from ( from : DualStream < S > ) -> Self
{
* from . into_inner ( )
fn poll_write ( self : Pin < & mut Self > , cx : & mut Context < ' _ > , buf : & [ u8 ] ) -> Poll < Result < usize , io ::Error > > {
let obj = unsafe {
self . map_unchecked_mut ( | this | this . as_dyn_mut ( ) )
} ;
obj . poll_write ( cx , buf )
}
fn poll_flush ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Result < ( ) , io ::Error > > {
let obj = unsafe {
self . map_unchecked_mut ( | this | this . as_dyn_mut ( ) )
} ;
obj . poll_flush ( cx )
}
fn poll_shutdown ( self : Pin < & mut Self > , cx : & mut Context < ' _ > ) -> Poll < Result < ( ) , io ::Error > > {
let obj = unsafe {
self . map_unchecked_mut ( | this | this . as_dyn_mut ( ) )
} ;
obj . poll_shutdown ( cx )
}
}
impl < S > Drop for DualStream < S >
#[ cfg(test) ]
mod tests
{
fn drop ( & mut self ) {
// SAFETY: Value is always initialised except exactly within swap function
unsafe {
ptr ::drop_in_place ( self . 0. as_mut_ptr ( ) )
}
use tokio ::prelude ::* ;
use chacha20stream ::keygen ;
#[ tokio::test ]
/// Write the whole `input` buffer encrypted, switch to plain, then write the first 5 bytes of `input` again.
// TODO: Read the `written` buffer back in the same way and order, and check for identity.
async fn wrapper_construct ( )
{
let input = "Hello world!" ;
let backing = Vec ::new ( ) ;
let ( key , iv ) = keygen ( ) ;
let written = {
let mut wrapper = super ::DualStream ::Plain ( backing ) ;
// Encrypted
wrapper . to_crypt ( super ::Encryption ::Encrypt , key , iv ) . await . unwrap ( ) ;
wrapper . write_all ( input . as_bytes ( ) ) . await . unwrap ( ) ;
// Unencrypted
wrapper . to_plain ( ) . await . unwrap ( ) ;
wrapper . write_all ( & input . as_bytes ( ) [ .. 5 ] ) . await . unwrap ( ) ;
// Shutdown the stream and consume it.
wrapper . flush ( ) . await . unwrap ( ) ;
wrapper . shutdown ( ) . await . unwrap ( ) ;
wrapper . into_inner ( )
} ;
eprintln! ( "Output bytes: {:?}" , written ) ;
eprintln! ( "Output attempted string: {:?}" , String ::from_utf8_lossy ( & written [ .. ] ) ) ;
}
}