From d7dde31f7a1982889d46417f0c8481eee0b58758 Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Fri, 14 Aug 2026 02:24:40 +0000 Subject: [PATCH 1/2] docs: io callback examples --- bindings/rust-examples/Cargo.toml | 2 +- .../rust-examples/io-callbacks/Cargo.toml | 15 + bindings/rust-examples/io-callbacks/README.md | 1 + .../rust-examples/io-callbacks/src/lib.rs | 264 ++++++++++++++++++ .../rust-examples/io-callbacks/src/raw_fd.rs | 188 +++++++++++++ .../io-callbacks/src/tls_stream.rs | 141 ++++++++++ 6 files changed, 610 insertions(+), 1 deletion(-) create mode 100644 bindings/rust-examples/io-callbacks/Cargo.toml create mode 100644 bindings/rust-examples/io-callbacks/README.md create mode 100644 bindings/rust-examples/io-callbacks/src/lib.rs create mode 100644 bindings/rust-examples/io-callbacks/src/raw_fd.rs create mode 100644 bindings/rust-examples/io-callbacks/src/tls_stream.rs diff --git a/bindings/rust-examples/Cargo.toml b/bindings/rust-examples/Cargo.toml index ee3b3d7beef..fccca7a1c03 100644 --- a/bindings/rust-examples/Cargo.toml +++ b/bindings/rust-examples/Cargo.toml @@ -1,7 +1,7 @@ [workspace] members = [ "client-hello-config-resolution", - "hyper-server-client", "key-logging", + "hyper-server-client", "io-callbacks", "key-logging", "tokio-server-client", ] resolver = "2" diff --git a/bindings/rust-examples/io-callbacks/Cargo.toml b/bindings/rust-examples/io-callbacks/Cargo.toml new file mode 100644 index 00000000000..fd01e445677 --- /dev/null +++ b/bindings/rust-examples/io-callbacks/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "io-callbacks" +version.workspace = true +authors.workspace = true +publish.workspace = true +license.workspace = true +edition.workspace = true + +[dependencies] +errno = "0.3.14" +libc = "0.2.189" +tracing = "0.1.44" +s2n-tls = { path = "../../rust/extended/s2n-tls" } + +[dev-dependencies] diff --git a/bindings/rust-examples/io-callbacks/README.md b/bindings/rust-examples/io-callbacks/README.md new file mode 100644 index 00000000000..43d00c131bd --- /dev/null +++ b/bindings/rust-examples/io-callbacks/README.md @@ -0,0 +1 @@ +This example shows how to work with lower-level s2n-tls IO. Most library consumers will not have to work with these methods, and should instead use `s2n-tls-tokio` for higher level IO interfaces. \ No newline at end of file diff --git a/bindings/rust-examples/io-callbacks/src/lib.rs b/bindings/rust-examples/io-callbacks/src/lib.rs new file mode 100644 index 00000000000..dd4d6c2faa9 --- /dev/null +++ b/bindings/rust-examples/io-callbacks/src/lib.rs @@ -0,0 +1,264 @@ +//! This example shows how to setup the unsafe send + recv callbacks for s2n-tls. +//! +//! For progress on offering safe bindings here, follow https://github.com/aws/s2n-tls/issues/6018 +//! + +pub mod raw_fd; +pub mod tls_stream; + +//////////////////////////////////////////////////////////////////////////////// +///////////////////// generic Read & Write C callbacks ///////////////////////// +//////////////////////////////////////////////////////////////////////////////// + +use std::ffi::{c_int, c_void}; + +/// An s2n-tls `send` callback. +/// +/// This callback assumes that the underlying IO object follows posix conventions. +/// E.g. a non-blocking send should set the errno to `EWOULDBLOCK` if the send would +/// block. +/// +/// Most abstractions, e.g. [`std::net::TcpStream`] already do this. +/// +/// This can be used where ctx is a stable pointer to a `T: Write`. For example. +/// ``` +/// use std::os::unix::net::UnixStream; +/// use std::pin::Pin; +/// use std::ffi::c_void; +/// use s2n_tls::connection::Connection; +/// use io_callbacks::generic_posix_send_cb; +/// +/// let (client_stream, server_stream) = UnixStream::pair().unwrap(); +/// // The IO context should be pinned, because s2n-tls holds the raw pointer for +/// // the duration of the connection. +/// let io_context: Pin> = Box::pin(client_stream); +/// let io_ctx_ptr: *mut c_void = &*io_context as *const UnixStream as *mut c_void; +/// +/// let mut conn = Connection::new_client(); +/// unsafe { conn.set_send_context(io_ctx_ptr) }.unwrap(); +/// conn.set_send_callback(Some(generic_posix_send_cb::)).unwrap(); +/// ``` +/// +/// # Safety +/// +/// * `context` must be a stable (`Pin`) pointer to a `T` that outlives the +/// connection. +/// * The callback forms a `&mut T` from `context`, so no other reference to that +/// `T` may be live while it runs. s2n-tls calls the send/receive callbacks +/// one at a time and never reentrantly per connection, so one context may back +/// both callbacks of the same connection. It must not be shared across +/// connections or driven concurrently. +pub unsafe extern "C" fn generic_posix_send_cb( + context: *mut c_void, + data: *const u8, + len: u32, +) -> c_int { + let context: &mut T = &mut *(context as *mut T); + let data = core::slice::from_raw_parts(data, len as _); + match context.write(data) { + Ok(bytes_written) => bytes_written as i32, + Err(err) => { + // On -1, s2n-tls reads `errno` to distinguish "would block" + // (EWOULDBLOCK/EAGAIN -> Poll::Pending) from a fatal error, so set it + // before returning. Types that hit the syscall directly (e.g. + // std::net::TcpStream) already leave errno set, making this redundant; + // but in general the OS error lives in the io::Error, and intervening + // work (like the log below) can clobber errno. So re-install it last. + let os_err = err.raw_os_error(); + tracing::trace!("generic send cb: write error: {err}"); + match os_err { + Some(os_err) => errno::set_errno(errno::Errno(os_err)), + None => tracing::warn!("Err {err} doesn't have a corresponding os err 😬"), + } + -1 + } + } +} + +/// This callback can be used where ctx is a stable pointer to a `T: Read`. +/// +/// The underlying transport stream is responsible for populating the errno appropriately. +/// +/// A read of `0` is assumed to mean a closed stream. In the case of no data available +/// and a non-blocking IO mode, the io stream should return an Err and set the errno +/// to EWOULDBLOCK. +/// +/// # Safety +/// +/// * `context` must be a stable (`Pin`) pointer to a `T` that outlives the +/// connection. +/// * The callback forms a `&mut T` from `context`, so no other reference to that +/// `T` may be live while it runs. s2n-tls calls the send/receive callbacks +/// one at a time and never reentrantly per connection, so one context may back +/// both callbacks of the same connection. It must not be shared across +/// connections or driven concurrently. +pub unsafe extern "C" fn generic_posix_recv_cb( + context: *mut c_void, + data: *mut u8, + len: u32, +) -> c_int { + let context: &mut T = &mut *(context as *mut T); + let data = core::slice::from_raw_parts_mut(data, len as _); + let read_result = context.read(data); + match read_result { + Ok(len) => { + // Note: an in-memory channel (e.g. VecDeque) returns Ok(0) when + // empty, but s2n-tls treats a read of 0 as EOF. Such transports must + // special-case 0 into an EWOULDBLOCK error instead. + len as c_int + } + Err(err) => { + // On -1, s2n-tls reads `errno` to distinguish "would block" + // (EWOULDBLOCK/EAGAIN -> Poll::Pending) from a fatal error, so set it + // before returning. Types that hit the syscall directly (e.g. + // std::net::TcpStream) already leave errno set, making this redundant; + // but in general the OS error lives in the io::Error, and intervening + // work (like the log below) can clobber errno. So re-install it last. + let os_err = err.raw_os_error(); + tracing::trace!("generic recv cb: read error: {err}"); + match os_err { + Some(os_err) => errno::set_errno(errno::Errno(os_err)), + None => tracing::warn!("Err {err} doesn't have a corresponding os err 😬"), + } + -1 + } + } +} + +#[cfg(test)] +pub(crate) mod test_utils { + use s2n_tls::{callbacks::VerifyHostNameCallback, config::Config, security::DEFAULT_TLS13}; + + // NOTE: these certificates are for demonstration/testing purposes only! + const CA_CERT: &[u8] = + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../certs/ca-cert.pem")); + const SERVER_CHAIN: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../certs/localhost-chain.pem" + )); + const SERVER_KEY: &[u8] = include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/../certs/localhost-key.pem" + )); + pub(crate) const SERVER_NAME: &str = "localhost"; + + /// A host verification callback that only trusts the expected server name. + struct VerifyLocalhost; + impl VerifyHostNameCallback for VerifyLocalhost { + fn verify_host_name(&self, host_name: &str) -> bool { + host_name == SERVER_NAME + } + } + + pub(crate) fn client_config() -> Config { + let mut builder = Config::builder(); + builder.set_security_policy(&DEFAULT_TLS13).unwrap(); + builder.trust_pem(CA_CERT).unwrap(); + builder.set_verify_host_callback(VerifyLocalhost).unwrap(); + builder.build().unwrap() + } + + pub(crate) fn server_config() -> Config { + let mut builder = Config::builder(); + builder.set_security_policy(&DEFAULT_TLS13).unwrap(); + builder.load_pem(SERVER_CHAIN, SERVER_KEY).unwrap(); + builder.build().unwrap() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{client_config, server_config, SERVER_NAME}; + use s2n_tls::{connection::Connection, enums::Mode, error::Error as S2NError}; + use std::{os::unix::net::UnixStream, task::Poll}; + + // s2n-tls handshake driven over a std::os::unix::net::UnixStream pair, using + // the generic send/recv callbacks defined in this crate. + #[test] + fn handshake_over_unix_domain_socket() -> Result<(), S2NError> { + let (client_stream, server_stream) = UnixStream::pair().unwrap(); + + // conceptually, we require a "Pin" because s2n-tls is holding the raw + // context pointer for the lifetime of the connection + let server_stream = Box::pin(server_stream); + let client_stream = Box::pin(client_stream); + + let mut server = { + let server_config = server_config(); + let mut conn = Connection::new(Mode::Server); + conn.set_config(server_config)?; + + let io_context = &*server_stream as *const UnixStream as *mut c_void; + + unsafe { conn.set_send_context(io_context) }?; + conn.set_send_callback(Some(generic_posix_send_cb::))?; + + unsafe { conn.set_receive_context(io_context) }?; + conn.set_receive_callback(Some(generic_posix_recv_cb::))?; + + conn + }; + + let mut client = { + let server_config = client_config(); + let mut conn = Connection::new(Mode::Client); + conn.set_config(server_config)?; + conn.set_server_name(SERVER_NAME)?; + + let io_context = &*client_stream as *const UnixStream as *mut c_void; + + unsafe { conn.set_send_context(io_context) }?; + conn.set_send_callback(Some(generic_posix_send_cb::))?; + + unsafe { conn.set_receive_context(io_context) }?; + conn.set_receive_callback(Some(generic_posix_recv_cb::))?; + + conn + }; + + // Drive each handshake on its own thread. + // + // These sockets are blocking, so a stalled callback blocks the thread + // instead of returning EWOULDBLOCK; poll_negotiate never yields Pending + // and the loops below don't spin. The peers need separate threads, or one + // would block waiting for bytes the other never gets to send. With + // non-blocking IO a single thread can drive both, but should wait on the + // fd (poll/select) or a waker rather than spinning on Pending. + + // drive the client handshake + let client_hs = std::thread::spawn(move || { + let res = loop { + match client.poll_negotiate() { + Poll::Ready(res) => break res, + Poll::Pending => { /* we need to poll again */ } + }; + }; + assert!(res.is_ok()); + client + }); + + // drive the server handshake + let server_hs = std::thread::spawn(move || { + let res = loop { + match server.poll_negotiate() { + Poll::Ready(res) => break res, + Poll::Pending => { /* we need to poll again */ } + }; + }; + assert!(res.is_ok()); + server + }); + + client_hs.join().unwrap(); + server_hs.join().unwrap(); + + // Note that because s2n-tls takes raw pointers to the underlying stream + // there is no automatic memory management. It is generally easier to + // implement a `TlsStream` abstraction that store the Connection alongside + // it's "owned" transport layer. + drop(client_stream); + drop(server_stream); + Ok(()) + } +} diff --git a/bindings/rust-examples/io-callbacks/src/raw_fd.rs b/bindings/rust-examples/io-callbacks/src/raw_fd.rs new file mode 100644 index 00000000000..90dd790fdb1 --- /dev/null +++ b/bindings/rust-examples/io-callbacks/src/raw_fd.rs @@ -0,0 +1,188 @@ +//! A minimal transport type built directly on a raw file descriptor. +//! +//! This models the kind of integration where you don't have a rich Rust type +//! like [`std::net::TcpStream`] to hand to s2n-tls. Across an FFI or JNI +//! boundary the foreign runtime frequently hands you a bare integer file +//! descriptor instead. By implementing [`std::io::Read`] and [`std::io::Write`] +//! directly over the fd with posix syscalls, the fd can be plugged straight +//! into the generic send/recv callbacks defined in this crate. + +use std::ffi::c_void; +use std::io::{Read, Write}; +use std::os::fd::RawFd; + +/// A newtype wrapper around a raw file descriptor that implements +/// [`Read`] and [`Write`] via posix `read`/`write` syscalls. +/// +/// `RawFdStream` takes ownership of the fd and closes it on drop. +pub struct RawFdStream { + fd: RawFd, +} + +impl RawFdStream { + /// Take *ownership* of `fd`; it is closed via `close(2)` on drop. + /// + /// Only pass an fd you own and nothing else will close. In FFI/JNI settings + /// the fd is often owned by the foreign runtime (e.g. the JVM); handing such + /// a borrowed fd here causes a double-close. For borrowed fds, use a wrapper + /// without `Drop` (or a [`std::os::fd::BorrowedFd`]). + pub fn from_owned(fd: RawFd) -> Self { + Self { fd } + } +} + +impl Read for RawFdStream { + fn read(&mut self, buf: &mut [u8]) -> std::io::Result { + // SAFETY: `buf` is a valid, writable slice of length `buf.len()`, and + // `self.fd` is a file descriptor we own. + let res = unsafe { libc::read(self.fd, buf.as_mut_ptr() as *mut c_void, buf.len()) }; + if res < 0 { + // Surface the OS error so the generic recv callback can propagate + // the errno (e.g. EWOULDBLOCK) back to s2n-tls. + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } +} + +impl Write for RawFdStream { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + // SAFETY: `buf` is a valid, readable slice of length `buf.len()`, and + // `self.fd` is a file descriptor we own. + let res = unsafe { libc::write(self.fd, buf.as_ptr() as *const c_void, buf.len()) }; + if res < 0 { + Err(std::io::Error::last_os_error()) + } else { + Ok(res as usize) + } + } + + fn flush(&mut self) -> std::io::Result<()> { + // Raw writes go straight to the kernel, so there is nothing to flush. + Ok(()) + } +} + +impl Drop for RawFdStream { + fn drop(&mut self) { + // SAFETY: we own `self.fd` (it was handed to us via `from_owned`), so it + // is valid to close it here. This prevents the fd from leaking. + unsafe { + libc::close(self.fd); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + generic_posix_recv_cb, generic_posix_send_cb, + test_utils::{client_config, server_config, SERVER_NAME}, + }; + use s2n_tls::{connection::Connection, enums::Mode, error::Error as S2NError}; + use std::{ + net::{TcpListener, TcpStream}, + os::fd::IntoRawFd, + task::Poll, + }; + + // s2n-tls handshake driven over raw file descriptors, using the generic + // send/recv callbacks defined in this crate. + // + // This models an FFI/JNI style integration: rather than handing s2n-tls a + // rich Rust type, we only have bare file descriptors. We wrap each fd in the + // `RawFdStream` newtype (which implements Read/Write via posix syscalls) and + // use that as the IO context. + #[test] + fn handshake_over_raw_fd() -> Result<(), S2NError> { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let addr = listener.local_addr().unwrap(); + + // Accept the server side of the connection on a background thread while the + // main thread connects as the client. + let accept = std::thread::spawn(move || listener.accept().unwrap().0); + let client_tcp = TcpStream::connect(addr).unwrap(); + let server_tcp = accept.join().unwrap(); + + // Drop down to raw file descriptors. `into_raw_fd` consumes the TcpStream + // *without* closing the fd, transferring ownership of the fd to us. This is + // the point where a real integrator would instead receive an fd from their + // foreign runtime. + let client_fd = client_tcp.into_raw_fd(); + let server_fd = server_tcp.into_raw_fd(); + + // Wrap the raw fds. `RawFdStream` now owns each fd and will close it on drop. + // + // conceptually, we require a "Pin" because s2n-tls is holding the raw + // context pointer for the lifetime of the connection. + let server_stream = Box::pin(RawFdStream::from_owned(server_fd)); + let client_stream = Box::pin(RawFdStream::from_owned(client_fd)); + + let mut server = { + let mut conn = Connection::new(Mode::Server); + conn.set_config(server_config())?; + + let io_context = &*server_stream as *const RawFdStream as *mut c_void; + + unsafe { conn.set_send_context(io_context) }?; + conn.set_send_callback(Some(generic_posix_send_cb::))?; + + unsafe { conn.set_receive_context(io_context) }?; + conn.set_receive_callback(Some(generic_posix_recv_cb::))?; + + conn + }; + + let mut client = { + let mut conn = Connection::new(Mode::Client); + conn.set_config(client_config())?; + conn.set_server_name(SERVER_NAME)?; + + let io_context = &*client_stream as *const RawFdStream as *mut c_void; + + unsafe { conn.set_send_context(io_context) }?; + conn.set_send_callback(Some(generic_posix_send_cb::))?; + + unsafe { conn.set_receive_context(io_context) }?; + conn.set_receive_callback(Some(generic_posix_recv_cb::))?; + + conn + }; + + // drive the client handshake + let client_hs = std::thread::spawn(move || { + let res = loop { + match client.poll_negotiate() { + Poll::Ready(res) => break res, + Poll::Pending => { /* we need to poll again */ } + }; + }; + assert!(res.is_ok()); + }); + + // drive the server handshake + let server_hs = std::thread::spawn(move || { + let res = loop { + match server.poll_negotiate() { + Poll::Ready(res) => break res, + Poll::Pending => { /* we need to poll again */ } + }; + }; + assert!(res.is_ok()); + }); + + client_hs.join().unwrap(); + server_hs.join().unwrap(); + + // Note that because s2n-tls takes raw pointers to the underlying stream + // there is no automatic memory management. It is generally easier to + // implement a `TlsStream` abstraction that stores the Connection alongside + // its "owned" transport layer. Dropping the streams here closes the + // underlying file descriptors. + drop(client_stream); + drop(server_stream); + Ok(()) + } +} diff --git a/bindings/rust-examples/io-callbacks/src/tls_stream.rs b/bindings/rust-examples/io-callbacks/src/tls_stream.rs new file mode 100644 index 00000000000..971903cb554 --- /dev/null +++ b/bindings/rust-examples/io-callbacks/src/tls_stream.rs @@ -0,0 +1,141 @@ +//! A minimal owning wrapper that ties an s2n-tls [`Connection`] to its transport. +//! +//! s2n-tls holds a raw pointer to the IO context for the life of the connection, +//! so the transport must have a stable address and must outlive the connection's +//! use of it. `TlsStream` enforces both by pinning the transport on the heap and +//! owning it alongside the connection. + +use std::ffi::c_void; +use std::io::{self, ErrorKind, Read, Write}; +use std::ops::Deref; +use std::pin::Pin; +use std::task::Poll; + +use s2n_tls::connection::Connection; +use s2n_tls::error::Error; + +use crate::{generic_posix_recv_cb, generic_posix_send_cb}; + +pub struct TlsStream { + /// The TLS Connection. + /// + /// Internally, this holds references (raw pointers) to `transport`. + connection: Connection, + // `Pin>` gives the transport a stable heap address to hand to s2n-tls + // as the IO context, and guarantees it won't move for the life of the stream. + // It is never read directly (the callbacks reach it via the raw context + // pointer); the field exists to own and keep the allocation alive. + #[allow(dead_code)] + transport: Pin>, +} + +impl TlsStream { + /// Wire `transport` into `connection` as the send/receive IO context and take + /// ownership of both. + pub fn new(mut connection: Connection, transport: T) -> Result { + let transport = Box::pin(transport); + + // The context is a stable pointer to the pinned transport. It is only ever + // dereferenced inside the callbacks, which s2n-tls invokes one at a time, + // so no two `&mut T` are ever live at once. + let io_context = &*transport as *const T as *mut c_void; + + connection.set_send_callback(Some(generic_posix_send_cb::))?; + unsafe { connection.set_send_context(io_context) }?; + + connection.set_receive_callback(Some(generic_posix_recv_cb::))?; + unsafe { connection.set_receive_context(io_context) }?; + + Ok(Self { + connection, + transport, + }) + } + + /// Drive the TLS handshake. + pub fn poll_negotiate(&mut self) -> Poll> { + self.connection.poll_negotiate().map(|res| res.map(|_| ())) + } + + pub fn connection(&self) -> &Connection { + &self.connection + } +} + +impl Read for TlsStream { + fn read(&mut self, buf: &mut [u8]) -> io::Result { + match self.connection.poll_recv(buf) { + Poll::Ready(Ok(n)) => Ok(n), + Poll::Ready(Err(e)) => Err(io::Error::new(ErrorKind::Other, e)), + Poll::Pending => Err(io::Error::new(ErrorKind::WouldBlock, "s2n-tls blocked")), + } + } +} + +impl Write for TlsStream { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self.connection.poll_send(buf) { + Poll::Ready(Ok(n)) => Ok(n), + Poll::Ready(Err(e)) => Err(io::Error::new(ErrorKind::Other, e)), + Poll::Pending => Err(io::Error::new(ErrorKind::WouldBlock, "s2n-tls blocked")), + } + } + + fn flush(&mut self) -> io::Result<()> { + // no-op poll_send already invoke the transport methods + Ok(()) + } +} + +// implementing deref makes it easy to use getters on the tls stream. +impl Deref for TlsStream { + type Target = Connection; + + fn deref(&self) -> &Self::Target { + &self.connection + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::test_utils::{client_config, server_config, SERVER_NAME}; + use s2n_tls::enums::Mode; + use std::os::unix::net::UnixStream; + + // Handshake and exchange application data using `TlsStream`, which owns each + // transport and wires up the IO callbacks in its constructor. + #[test] + fn tls_stream_roundtrip() -> Result<(), Error> { + const MESSAGE: &[u8] = b"hello from the client"; + + let (client_transport, server_transport) = UnixStream::pair().unwrap(); + + let mut server_conn = Connection::new(Mode::Server); + server_conn.set_config(server_config())?; + let mut server = TlsStream::new(server_conn, server_transport)?; + + let mut client_conn = Connection::new(Mode::Client); + client_conn.set_config(client_config())?; + client_conn.set_server_name(SERVER_NAME)?; + let mut client = TlsStream::new(client_conn, client_transport)?; + + // Blocking sockets, so drive each peer on its own thread (see the note in + // lib.rs's handshake test). + let server_hs = std::thread::spawn(move || { + while server.poll_negotiate().is_pending() {} + server + }); + while client.poll_negotiate().is_pending() {} + let mut server = server_hs.join().unwrap(); + + // Application data flows through the standard Read/Write impls. + client.write_all(MESSAGE).unwrap(); + + let mut buf = vec![0u8; MESSAGE.len()]; + server.read_exact(&mut buf).unwrap(); + assert_eq!(buf, MESSAGE); + Ok(()) + } +} + From bc7d386b7b4c0026b6bbdea98cf6e5cef61c305f Mon Sep 17 00:00:00 2001 From: James Mayclin Date: Mon, 17 Aug 2026 23:52:18 +0000 Subject: [PATCH 2/2] add copyright header --- bindings/rust-examples/io-callbacks/src/lib.rs | 4 +++- bindings/rust-examples/io-callbacks/src/raw_fd.rs | 3 +++ bindings/rust-examples/io-callbacks/src/tls_stream.rs | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/bindings/rust-examples/io-callbacks/src/lib.rs b/bindings/rust-examples/io-callbacks/src/lib.rs index dd4d6c2faa9..1d21d80b3a3 100644 --- a/bindings/rust-examples/io-callbacks/src/lib.rs +++ b/bindings/rust-examples/io-callbacks/src/lib.rs @@ -1,7 +1,9 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + //! This example shows how to setup the unsafe send + recv callbacks for s2n-tls. //! //! For progress on offering safe bindings here, follow https://github.com/aws/s2n-tls/issues/6018 -//! pub mod raw_fd; pub mod tls_stream; diff --git a/bindings/rust-examples/io-callbacks/src/raw_fd.rs b/bindings/rust-examples/io-callbacks/src/raw_fd.rs index 90dd790fdb1..8d575257fcf 100644 --- a/bindings/rust-examples/io-callbacks/src/raw_fd.rs +++ b/bindings/rust-examples/io-callbacks/src/raw_fd.rs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + //! A minimal transport type built directly on a raw file descriptor. //! //! This models the kind of integration where you don't have a rich Rust type diff --git a/bindings/rust-examples/io-callbacks/src/tls_stream.rs b/bindings/rust-examples/io-callbacks/src/tls_stream.rs index 971903cb554..efbd1259ac1 100644 --- a/bindings/rust-examples/io-callbacks/src/tls_stream.rs +++ b/bindings/rust-examples/io-callbacks/src/tls_stream.rs @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + //! A minimal owning wrapper that ties an s2n-tls [`Connection`] to its transport. //! //! s2n-tls holds a raw pointer to the IO context for the life of the connection,