Skip to content

Commit

Permalink
Merge pull request #14 from expressvpn/LIT-36-do-not-buffer-io
Browse files Browse the repository at this point in the history
Remove I/O buffering from the bindings layer.
  • Loading branch information
expressvpn-ian-c authored Aug 29, 2023
2 parents 7541611 + 1364f9c commit 7e2467b
Show file tree
Hide file tree
Showing 8 changed files with 407 additions and 406 deletions.
4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ description = "High-level bindings for WolfSSL"
repository = "https://github.com/expressvpn/wolfssl"
keywords = ["wolfssl", "vpn", "lightway", "post-quantum", "cryptography"]

[features]
default = []
debug = ["wolfssl-sys/debug"] # Note that application code must also call wolfssl::enable_debugging(true)

[dependencies]
bytes = "1"
log = "0.4"
Expand Down
112 changes: 32 additions & 80 deletions src/callback.rs
Original file line number Diff line number Diff line change
@@ -1,84 +1,36 @@
use crate::ssl::DataBuffer;
use bytes::Buf;
use std::ffi::{c_char, c_int, c_void};

/// The custom IO callback documented at [`EmbedRecieve`][0] (whose
/// inputs and outputs we need to emulate).
///
/// [0]: https://www.wolfssl.com/documentation/manuals/wolfssl/wolfio_8h.html#function-embedreceive
pub unsafe extern "C" fn wolf_tls_read_cb(
_ssl: *mut wolfssl_sys::WOLFSSL,
buf: *mut c_char,
sz: c_int,
ctx: *mut c_void,
) -> c_int {
debug_assert!(!_ssl.is_null());
debug_assert!(!buf.is_null());
debug_assert!(!ctx.is_null());

let read_buffer = unsafe { &mut *(ctx as *mut DataBuffer) };

// If our buffer is empty, there's nothing more to do here. Tell
// WolfSSL that we need more data
if read_buffer.is_empty() {
return wolfssl_sys::IOerrors_WOLFSSL_CBIO_ERR_WANT_READ;
}

// Find out how much we should or can copy to WolfSSL. WolfSSL
// asks for data piecemeal, so often it will ask for just 2 or 5
// bytes at a time. Passing more will cause it to error. On the
// other hand though, it might need a 1000 bytes, but all we have
// is 500 - in which case just send all that we can.
let num_of_bytes = std::cmp::min(read_buffer.len(), sz as usize);

// Now for some slight of hand - make the buffer provided by
// WolfSSL appear as a slice. Despite this being an unsafe piece
// of code, it will make further interactions far safer by
// conceptualising the buffer pointer and length together.
//
// We use `num_of_bytes` here to ensure that we are always dealing
// with valid memory
let wolf_buffer = unsafe { std::slice::from_raw_parts_mut(buf as *mut u8, num_of_bytes) };

// Copy the data into WolfSSL's buffer
wolf_buffer.copy_from_slice(&read_buffer[..num_of_bytes]);

// Drop the bytes read into WolfSSL
Buf::advance(read_buffer, num_of_bytes);

// WolfSSL expects that we return the number of bytes copied
num_of_bytes as ::std::os::raw::c_int
/// Result type to be returned by methods on [`IOCallbacks`]
pub enum IOCallbackResult<T> {
/// Success
Ok(T),
/// The I/O operation would block, this will surface to the
/// application as [`crate::Poll::PendingRead`] or [`crate::Poll::PendingWrite`]
WouldBlock,
/// Any other error
Err(std::io::Error),
}

/// The custom IO callback documented at [`EmbedSend`][0] (whose
/// inputs and outputs we need to emulate).
/// The application provided IO callbacks documented at
/// [`EmbedRecieve`][0] (whose inputs and outputs we need to
/// emulate). See also [`wolfSSL_CTX_SetIORecv`][0] which is the best
/// docs for `wolfSSL_CTX_SetIORecv` and `wolfSSL_CTX_SetIOSend`,
/// which are what we actually use.
///
/// Here the assumption is that WolfSSL is writing data _into_ the
/// callback (which will then ship it off somewhere)
///
/// [0]: https://www.wolfssl.com/documentation/manuals/wolfssl/wolfio_8h.html#function-embedsend
pub unsafe extern "C" fn wolf_tls_write_cb(
_ssl: *mut wolfssl_sys::WOLFSSL,
buf: *mut c_char,
sz: c_int,
ctx: *mut c_void,
) -> c_int {
debug_assert!(!_ssl.is_null());
debug_assert!(!buf.is_null());
debug_assert!(!ctx.is_null());

let write_buffer = unsafe { &mut *(ctx as *mut DataBuffer) };

// Create a slice using the c pointer and length from WolfSSL.
// This contains the bytes we need to write out
let wolf_buffer: &[u8] = unsafe { std::slice::from_raw_parts(buf as *const u8, sz as usize) };

// Copy bytes into our write buffer. Our buffer will resize as
// needed
write_buffer.extend_from_slice(wolf_buffer);

// Return the number of bytes WolfSSL gave us as we can consume
// all of them. At this point however WolfSSL believes that the
// send was successful, it has no way to know otherwise
wolf_buffer.len() as c_int
/// [0]: https://www.wolfssl.com/documentation/manuals/wolfssl/wolfio_8h.html#function-embedreceive
/// [1]: https://www.wolfssl.com/documentation/manuals/wolfssl/wolfio_8h.html#function-wolfssl_ctx_setiorecv
pub trait IOCallbacks {
/// Called when WolfSSL wishes to receive some data.
///
/// Receive as many bytes as possible into provided buffer, return
/// the number of bytes actually received. If the operation would
/// block [`std::io::ErrorKind::WouldBlock`] then return
/// [`IOCallbackResult::WouldBlock`].
fn recv(&self, buf: &mut [u8]) -> IOCallbackResult<usize>;

/// Called when WolfSSL wishes to send some data
///
/// Send as many bytes as possible from the provided buffer,
/// return the number of bytes actually consumed. If the operation would
/// block [`std::io::ErrorKind::WouldBlock`] then return
/// [`IOCallbackResult::WouldBlock`].
fn send(&self, buf: &[u8]) -> IOCallbackResult<usize>;
}
20 changes: 6 additions & 14 deletions src/context.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::{
callback::{wolf_tls_read_cb, wolf_tls_write_cb},
callback::IOCallbacks,
error::{Error, Result},
ssl::{Session, SessionConfig},
Protocol, RootCertificate, Secret,
Expand Down Expand Up @@ -242,25 +242,14 @@ impl ContextBuilder {
}

/// Finalizes a `WolfContext`.
pub fn build(mut self) -> Context {
self.register_io_callbacks();
pub fn build(self) -> Context {
Context {
protocol: self.protocol,
ctx: Mutex::new(self.ctx),
}
}
}

impl ContextBuilder {
fn register_io_callbacks(&mut self) {
let ctx = self.ctx;
unsafe {
wolfssl_sys::wolfSSL_CTX_SetIORecv(ctx.as_ptr(), Some(wolf_tls_read_cb));
wolfssl_sys::wolfSSL_CTX_SetIOSend(ctx.as_ptr(), Some(wolf_tls_write_cb));
}
}
}

/// A wrapper around a `WOLFSSL_CTX`.
pub struct Context {
protocol: Protocol,
Expand All @@ -280,7 +269,10 @@ impl Context {
}

/// Creates a new SSL session using this underlying context.
pub fn new_session(&self, config: SessionConfig) -> Option<Session> {
pub fn new_session<IOCB: IOCallbacks>(
&self,
config: SessionConfig<IOCB>,
) -> Option<Session<IOCB>> {
Session::new_from_context(self, config)
}
}
Expand Down
7 changes: 5 additions & 2 deletions src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@ use thiserror::Error;
#[derive(Debug)]
pub enum Poll<T> {
/// Underlying IO operations are still ongoing. No output has been generated
/// yet.
Pending,
/// yet. A write is pending
PendingWrite,
/// Underlying IO operations are still ongoing. No output has been generated
/// yet. A write is pending
PendingRead,
/// An output has been generated.
Ready(T),
/// When under secure renegotiation, WolfSSL can now sometimes emit an
Expand Down
21 changes: 21 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod error;
mod rng;
mod ssl;

pub use callback::*;
pub use context::*;
pub use rng::*;
pub use ssl::*;
Expand Down Expand Up @@ -52,6 +53,26 @@ pub fn wolf_cleanup() -> Result<()> {
}
}

/// Wraps [`wolfSSL_Debugging_ON`][0] and [`wolfSSL_Debugging_OFF`][1]
///
/// [0]: https://www.wolfssl.com/documentation/manuals/wolfssl/group__Debug.html#function-wolfssl_debugging_on
/// [1]: https://www.wolfssl.com/documentation/manuals/wolfssl/group__Debug.html#function-wolfssl_debugging_off
#[cfg(feature = "debug")]
pub fn enable_debugging(on: bool) {
if on {
match unsafe { wolfssl_sys::wolfSSL_Debugging_ON() } {
0 => {}
// This wrapper function is only enabled if we built wolfssl-sys with debugging on.
wolfssl_sys::NOT_COMPILED_IN => {
panic!("Inconsistent build, debug not enabled in wolfssl_sys")
}
e => unreachable!("{e:?}"),
}
} else {
unsafe { wolfssl_sys::wolfSSL_Debugging_OFF() }
}
}

/// Corresponds to the various `wolf*_{client,server}_method()` APIs
#[derive(Debug, Copy, Clone)]
pub enum Protocol {
Expand Down
Loading

0 comments on commit 7e2467b

Please sign in to comment.