Skip to content

Commit

Permalink
refactor(bindings/bench): make harness own IO (#4847)
Browse files Browse the repository at this point in the history
  • Loading branch information
jmayclin authored Dec 16, 2024
1 parent a70b2b0 commit e50f319
Show file tree
Hide file tree
Showing 8 changed files with 148 additions and 251 deletions.
19 changes: 4 additions & 15 deletions bindings/rust/bench/benches/handshake.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@
// SPDX-License-Identifier: Apache-2.0

use bench::{
harness::TlsBenchConfig, CipherSuite, ConnectedBuffer, CryptoConfig, HandshakeType, KXGroup,
Mode, OpenSslConnection, RustlsConnection, S2NConnection, SigType, TlsConnPair, TlsConnection,
harness::TlsBenchConfig, CipherSuite, CryptoConfig, HandshakeType, KXGroup, Mode,
OpenSslConnection, RustlsConnection, S2NConnection, SigType, TlsConnPair, TlsConnection,
PROFILER_FREQUENCY,
};
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup, Criterion,
};
use pprof::criterion::{Output, PProfProfiler};
use std::error::Error;
use strum::IntoEnumIterator;

fn bench_handshake_for_library<T>(
Expand All @@ -33,19 +32,9 @@ fn bench_handshake_for_library<T>(
// only include negotiation and not config/connection initialization
bench_group.bench_function(T::name(), |b| {
b.iter_batched_ref(
|| -> Result<TlsConnPair<T, T>, Box<dyn Error>> {
let connected_buffer = ConnectedBuffer::default();
let client = T::new_from_config(&client_config, connected_buffer.clone_inverse())?;
let server = T::new_from_config(&server_config, connected_buffer)?;
Ok(TlsConnPair::wrap(client, server))
},
|| -> TlsConnPair<T, T> { TlsConnPair::from_configs(&client_config, &server_config) },
|conn_pair| {
// harnesses with certain parameters fail to initialize for
// some past versions of s2n-tls, but missing data can be
// visually interpolated in the historical performance graph
if let Ok(conn_pair) = conn_pair {
let _ = conn_pair.handshake();
}
conn_pair.handshake().unwrap();
},
BatchSize::SmallInput,
)
Expand Down
20 changes: 7 additions & 13 deletions bindings/rust/bench/benches/throughput.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
use bench::OpenSslConnection;
use bench::RustlsConnection;
use bench::{
harness::TlsBenchConfig, CipherSuite, ConnectedBuffer, CryptoConfig, HandshakeType, KXGroup,
Mode, S2NConnection, SigType, TlsConnPair, TlsConnection, PROFILER_FREQUENCY,
harness::TlsBenchConfig, CipherSuite, CryptoConfig, HandshakeType, KXGroup, Mode,
S2NConnection, SigType, TlsConnPair, TlsConnection, PROFILER_FREQUENCY,
};
use criterion::{
criterion_group, criterion_main, measurement::WallTime, BatchSize, BenchmarkGroup, Criterion,
Throughput,
};
use pprof::criterion::{Output, PProfProfiler};
use std::error::Error;
use strum::IntoEnumIterator;

fn bench_throughput_for_library<T>(
Expand All @@ -31,18 +30,13 @@ fn bench_throughput_for_library<T>(

bench_group.bench_function(T::name(), |b| {
b.iter_batched_ref(
|| -> Result<TlsConnPair<T, T>, Box<dyn Error>> {
let connected_buffer = ConnectedBuffer::default();
let client = T::new_from_config(&client_config, connected_buffer.clone_inverse())?;
let server = T::new_from_config(&server_config, connected_buffer)?;
let mut conn_pair = TlsConnPair::wrap(client, server);
conn_pair.handshake()?;
Ok(conn_pair)
|| -> TlsConnPair<T, T> {
let mut conn_pair = TlsConnPair::from_configs(&client_config, &server_config);
conn_pair.handshake().unwrap();
conn_pair
},
|conn_pair| {
if let Ok(conn_pair) = conn_pair {
let _ = conn_pair.round_trip_transfer(shared_buf);
}
let _ = conn_pair.round_trip_transfer(shared_buf);
},
BatchSize::SmallInput,
)
Expand Down
67 changes: 67 additions & 0 deletions bindings/rust/bench/src/harness/io.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::{cell::RefCell, collections::VecDeque, io::ErrorKind, pin::Pin, rc::Rc};

pub type LocalDataBuffer = RefCell<VecDeque<u8>>;

#[derive(Debug)]
pub struct TestPairIO {
/// a data buffer that the server writes to and the client reads from
pub server_tx_stream: Pin<Rc<LocalDataBuffer>>,
/// a data buffer that the client writes to and the server reads from
pub client_tx_stream: Pin<Rc<LocalDataBuffer>>,
}

impl TestPairIO {
pub fn client_view(&self) -> ViewIO {
ViewIO {
send_ctx: self.client_tx_stream.clone(),
recv_ctx: self.server_tx_stream.clone(),
}
}

pub fn server_view(&self) -> ViewIO {
ViewIO {
send_ctx: self.server_tx_stream.clone(),
recv_ctx: self.client_tx_stream.clone(),
}
}
}

/// A "view" of the IO.
///
/// This view is client/server specific, and notably implements the read and write
/// traits.
///
// This struct is used by Openssl and Rustls which both rely on a "stream" abstraction
// which implements read and write. This is not used by s2n-tls, which relies on
// lower level callbacks.
pub struct ViewIO {
pub send_ctx: Pin<Rc<LocalDataBuffer>>,
pub recv_ctx: Pin<Rc<LocalDataBuffer>>,
}

impl std::io::Read for ViewIO {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
let res = self.recv_ctx.borrow_mut().read(buf);
if let Ok(0) = res {
// We are "faking" a TcpStream, where a read of length 0 indicates
// EoF. That is incorrect for this scenario. Instead we return WouldBlock
// to indicate that there is simply no more data to be read.
Err(std::io::Error::new(ErrorKind::WouldBlock, "blocking"))
} else {
res
}
}
}

impl std::io::Write for ViewIO {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
self.send_ctx.borrow_mut().write(buf)
}

fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,11 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

use std::{
cell::RefCell,
collections::VecDeque,
error::Error,
fmt::Debug,
fs::read_to_string,
io::{ErrorKind, Read, Write},
rc::Rc,
};
mod io;
pub use io::{LocalDataBuffer, ViewIO};

use io::TestPairIO;
use std::{error::Error, fmt::Debug, fs::read_to_string, rc::Rc};
use strum::EnumIter;

#[derive(Clone, Copy, EnumIter)]
Expand Down Expand Up @@ -161,10 +157,7 @@ pub trait TlsConnection: Sized {
fn name() -> String;

/// Make connection from existing config and buffer
fn new_from_config(
config: &Self::Config,
connected_buffer: ConnectedBuffer,
) -> Result<Self, Box<dyn Error>>;
fn new_from_config(config: &Self::Config, io: ViewIO) -> Result<Self, Box<dyn Error>>;

/// Run one handshake step: receive msgs from other connection, process, and send new msgs
fn handshake(&mut self) -> Result<(), Box<dyn Error>>;
Expand All @@ -184,29 +177,13 @@ pub trait TlsConnection: Sized {

/// Read application data from ConnectedBuffer
fn recv(&mut self, data: &mut [u8]) -> Result<(), Box<dyn Error>>;

/// Shrink buffers owned by the connection
fn shrink_connection_buffers(&mut self);

/// Clear and shrink buffers used for IO with another connection
fn shrink_connected_buffer(&mut self);

/// Get reference to internal connected buffer
fn connected_buffer(&self) -> &ConnectedBuffer;
}

/// A TlsConnPair owns the client and server tls connections along with the IO buffers.
pub struct TlsConnPair<C: TlsConnection, S: TlsConnection> {
client: C,
server: S,
}

impl<C: TlsConnection, S: TlsConnection> TlsConnPair<C, S> {
pub fn new(client_config: &C::Config, server_config: &S::Config) -> TlsConnPair<C, S> {
let connected_buffer = ConnectedBuffer::default();
let client = C::new_from_config(&client_config, connected_buffer.clone_inverse()).unwrap();
let server = S::new_from_config(&server_config, connected_buffer).unwrap();
Self { client, server }
}
pub client: C,
pub server: S,
pub io: TestPairIO,
}

impl<C, S> Default for TlsConnPair<C, S>
Expand Down Expand Up @@ -242,7 +219,7 @@ where

// handshake the client and server connections. This will result in
// session ticket getting stored in client_config
let mut pair = TlsConnPair::<C, S>::new(&client_config, &server_config);
let mut pair = TlsConnPair::<C, S>::from_configs(&client_config, &server_config);
pair.handshake()?;
// NewSessionTicket messages are part of the application data and sent
// after the handshake is complete, so we must trigger an additional
Expand All @@ -255,10 +232,13 @@ where
// on the connection. This results in the session ticket in
// client_config (from the previous handshake) getting set on the
// client connection.
return Ok(TlsConnPair::<C, S>::new(&client_config, &server_config));
return Ok(TlsConnPair::<C, S>::from_configs(
&client_config,
&server_config,
));
}

Ok(TlsConnPair::<C, S>::new(
Ok(TlsConnPair::<C, S>::from_configs(
&C::Config::make_config(Mode::Client, crypto_config, handshake_type).unwrap(),
&S::Config::make_config(Mode::Server, crypto_config, handshake_type).unwrap(),
))
Expand All @@ -270,13 +250,14 @@ where
C: TlsConnection,
S: TlsConnection,
{
/// Wrap two TlsConnections into a TlsConnPair
pub fn wrap(client: C, server: S) -> Self {
assert!(
client.connected_buffer() == &server.connected_buffer().clone_inverse(),
"connected buffers don't match"
);
Self { client, server }
pub fn from_configs(client_config: &C::Config, server_config: &S::Config) -> Self {
let io = TestPairIO {
server_tx_stream: Rc::pin(Default::default()),
client_tx_stream: Rc::pin(Default::default()),
};
let client = C::new_from_config(client_config, io.client_view()).unwrap();
let server = S::new_from_config(server_config, io.server_view()).unwrap();
Self { client, server, io }
}

/// Take back ownership of individual connections in the TlsConnPair
Expand Down Expand Up @@ -325,93 +306,6 @@ where

Ok(())
}

/// Shrink buffers owned by the connections
pub fn shrink_connection_buffers(&mut self) {
self.client.shrink_connection_buffers();
self.server.shrink_connection_buffers();
}

/// Clear and shrink buffers used for IO between the connections
pub fn shrink_connected_buffers(&mut self) {
self.client.shrink_connected_buffer();
self.server.shrink_connected_buffer();
}
}

/// Wrapper of two shared buffers to pass as stream
/// This wrapper `read()`s into one buffer and `write()`s to another
/// `Rc<RefCell<VecDeque<u8>>>` allows sharing of references to the buffers for two connections
#[derive(Clone, Eq)]
pub struct ConnectedBuffer {
recv: Rc<RefCell<VecDeque<u8>>>,
send: Rc<RefCell<VecDeque<u8>>>,
}

impl PartialEq for ConnectedBuffer {
/// ConnectedBuffers are equal if and only if they point to the same VecDeques
fn eq(&self, other: &ConnectedBuffer) -> bool {
Rc::ptr_eq(&self.recv, &other.recv) && Rc::ptr_eq(&self.send, &other.send)
}
}

impl ConnectedBuffer {
/// Make a new struct with new internal buffers
pub fn new() -> Self {
let recv = Rc::new(RefCell::new(VecDeque::new()));
let send = Rc::new(RefCell::new(VecDeque::new()));

// prevent (potentially slow) resizing of buffers for small data transfers,
// like with handshake
recv.borrow_mut().reserve(10000);
send.borrow_mut().reserve(10000);

Self { recv, send }
}

/// Makes a new ConnectedBuffer that shares internal buffers but swapped,
/// ex. `write()` writes to the buffer that the inverse `read()`s from
pub fn clone_inverse(&self) -> Self {
Self {
recv: self.send.clone(),
send: self.recv.clone(),
}
}

/// Clears and shrinks buffers
pub fn shrink(&mut self) {
self.recv.borrow_mut().clear();
self.recv.borrow_mut().shrink_to_fit();
self.send.borrow_mut().clear();
self.send.borrow_mut().shrink_to_fit();
}
}

impl Read for ConnectedBuffer {
fn read(&mut self, dest: &mut [u8]) -> Result<usize, std::io::Error> {
let res = self.recv.borrow_mut().read(dest);
match res {
// rustls expects WouldBlock on read of length 0
Ok(0) => Err(std::io::Error::new(ErrorKind::WouldBlock, "blocking")),
Ok(len) => Ok(len),
Err(err) => Err(err),
}
}
}

impl Write for ConnectedBuffer {
fn write(&mut self, src: &[u8]) -> Result<usize, std::io::Error> {
self.send.borrow_mut().write(src)
}
fn flush(&mut self) -> Result<(), std::io::Error> {
Ok(()) // data already available to destination
}
}

impl Default for ConnectedBuffer {
fn default() -> Self {
Self::new()
}
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions bindings/rust/bench/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ pub mod s2n_tls;

pub use crate::{
harness::{
get_cert_path, CipherSuite, ConnectedBuffer, CryptoConfig, HandshakeType, KXGroup, Mode,
PemType, SigType, TlsConnPair, TlsConnection,
get_cert_path, CipherSuite, CryptoConfig, HandshakeType, KXGroup, Mode, PemType, SigType,
TlsConnPair, TlsConnection,
},
openssl::OpenSslConnection,
rustls::RustlsConnection,
Expand Down
Loading

0 comments on commit e50f319

Please sign in to comment.