summary history branches tags files
commit:ecc128ec2ac0484a7b0c0bf13b59beaded624a55
author:Trevor Bentley
committer:Trevor Bentley
date:Sun Apr 28 15:50:41 2019 +0200
parents:5ad6e4d9b3498745025b57ed39a414cdb2d54c6e
Split into modules
diff --git a/src/comm.rs b/src/comm.rs
line changes: +253/-0
index 0000000..5b1bc67
--- /dev/null
+++ b/src/comm.rs
@@ -0,0 +1,253 @@
+use crate::*;
+
+use std::convert::TryInto;
+
+/// Read a complete network packet from the input stream.
+///
+/// On success, returns a NetworkPacket struct containing the header and data,
+/// and a `usize` indicating how many bytes were consumed from the input buffer.
+pub(crate) fn read_packet<T,U>(conn: &mut OssuaryConnection,
+                    mut stream: T) ->Result<(NetworkPacket, usize), OssuaryError>
+where T: std::ops::DerefMut<Target = U>,
+      U: std::io::Read {
+    let header_size = ::std::mem::size_of::<PacketHeader>();
+    let bytes_read: usize;
+    match stream.read(&mut conn.read_buf[conn.read_buf_used..]) {
+        Ok(b) => bytes_read = b,
+        Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
+            return Err(OssuaryError::WouldBlock(0))
+        },
+        Err(e) => return Err(e.into()),
+    }
+    conn.read_buf_used += bytes_read;
+    let buf: &[u8] = &conn.read_buf;
+    let hdr = PacketHeader {
+        len: u16::from_be_bytes(buf[0..2].try_into()?),
+        msg_id: u16::from_be_bytes(buf[2..4].try_into()?),
+        packet_type: PacketType::from_u16(u16::from_be_bytes(buf[4..6].try_into()?)),
+        _reserved: u16::from_be_bytes(buf[6..8].try_into()?),
+    };
+    let packet_len = hdr.len as usize;
+    if conn.read_buf_used < header_size + packet_len {
+        if header_size + packet_len > PACKET_BUF_SIZE {
+            panic!("oversized packet");
+        }
+        return Err(OssuaryError::WouldBlock(bytes_read));
+    }
+    let buf: Box<[u8]> = (&conn.read_buf[header_size..header_size+packet_len])
+        .to_vec().into_boxed_slice();
+    let excess = conn.read_buf_used - header_size - packet_len;
+    unsafe {
+        // no safe way to memmove() in Rust?
+        std::ptr::copy::<u8>(
+            conn.read_buf.as_ptr().offset((header_size + packet_len) as isize),
+            conn.read_buf.as_mut_ptr(),
+            excess);
+    }
+    conn.read_buf_used = excess;
+    Ok((NetworkPacket {
+        header: hdr,
+        data: buf,
+    },
+    header_size + packet_len))
+}
+
+/// Write a packet from OssuaryConnection's internal storage to the out buffer.
+///
+/// All packets are buffered to internal storage before writing, so this is
+/// the function responsible for putting all packets "on the wire".
+///
+/// On success, returns the number of bytes written to the output buffer
+pub(crate) fn write_stored_packet<T,U>(conn: &mut OssuaryConnection,
+                            stream: &mut T) -> Result<usize, OssuaryError>
+where T: std::ops::DerefMut<Target = U>,
+      U: std::io::Write {
+    let mut written = 0;
+    while written < conn.write_buf_used {
+        match stream.write(&conn.write_buf[written..conn.write_buf_used]) {
+            Ok(w) => {
+                written += w;
+            },
+            Err(e) => {
+                if written > 0 && written < conn.write_buf_used {
+                    unsafe {
+                        // no safe way to memmove() in Rust?
+                        std::ptr::copy::<u8>(
+                            conn.write_buf.as_ptr().offset(written as isize),
+                            conn.write_buf.as_mut_ptr(),
+                            conn.write_buf_used - written);
+                    }
+                }
+                conn.write_buf_used -= written;
+                return Err(e.into());
+            },
+        }
+    }
+    conn.write_buf_used = 0;
+    Ok(written)
+}
+
+/// Write a packet to the OssuaryConnection's internal packet buffer
+///
+/// All packets are buffered internally because there is no guarantee that a
+/// complete packet can be written without blocking, and Ossuary is a non-
+/// blocking library.
+///
+/// On success, returns the number of bytes written to the output buffer.
+pub(crate) fn write_packet<T,U>(conn: &mut OssuaryConnection,
+                     stream: &mut T, data: &[u8],
+                     kind: PacketType) -> Result<usize, OssuaryError>
+where T: std::ops::DerefMut<Target = U>,
+      U: std::io::Write {
+    let msg_id = conn.local_msg_id as u16;
+    conn.write_buf[0..2].copy_from_slice(&(data.len() as u16).to_be_bytes());
+    conn.write_buf[2..4].copy_from_slice(&msg_id.to_be_bytes());
+    conn.write_buf[4..6].copy_from_slice(&(kind as u16).to_be_bytes());
+    conn.write_buf[6..8].copy_from_slice(&(0u16).to_be_bytes());
+    conn.write_buf[8..8+data.len()].copy_from_slice(&data);
+    conn.write_buf_used = 8 + data.len();
+    conn.local_msg_id += 1;
+    let written = write_stored_packet(conn, stream)?;
+    Ok(written)
+}
+
+impl OssuaryConnection {
+    pub fn send_data<T,U>(&mut self,
+                          in_buf: &[u8],
+                          mut out_buf: T) -> Result<usize, OssuaryError>
+    where T: std::ops::DerefMut<Target = U>,
+          U: std::io::Write {
+        // Try to send any unsent buffered data
+        match write_stored_packet(self, &mut out_buf) {
+            Ok(w) if w == 0 => {},
+            Ok(w) => return Err(OssuaryError::WouldBlock(w)),
+            Err(e) => return Err(e),
+        }
+        match self.state {
+            ConnectionState::Encrypted => {},
+            _ => {
+                return Err(OssuaryError::InvalidPacket(
+                    "Encrypted channel not established.".into()));
+            }
+        }
+        let aad = [];
+        let mut ciphertext = Vec::with_capacity(in_buf.len());
+        let session_key = match self.local_key.session {
+            Some(ref k) => k,
+            None => {
+                self.reset_state(None);
+                return Err(OssuaryError::InvalidKey);;
+            }
+        };
+        let tag = match encrypt(session_key.as_bytes(),
+                                &self.local_key.nonce, &aad, in_buf, &mut ciphertext) {
+            Ok(t) => t,
+            Err(_) => {
+                self.reset_state(None);
+                return Err(OssuaryError::InvalidKey);;
+            }
+        };
+
+        let pkt: EncryptedPacket = EncryptedPacket {
+            tag_len: tag.len() as u16,
+            data_len: ciphertext.len() as u16,
+        };
+        let mut buf: Vec<u8>= vec![];
+        buf.extend(struct_as_slice(&pkt));
+        buf.extend(&ciphertext);
+        buf.extend(&tag);
+        let written = write_packet(self, &mut out_buf, &buf,
+                                   PacketType::EncryptedData)?;
+        Ok(written)
+    }
+
+    pub fn recv_data<T,U,R,V>(&mut self,
+                              in_buf: T,
+                              mut out_buf: R) -> Result<(usize, usize), OssuaryError>
+    where T: std::ops::DerefMut<Target = U>,
+          U: std::io::Read,
+          R: std::ops::DerefMut<Target = V>,
+          V: std::io::Write {
+        let bytes_written: usize;
+        let mut bytes_read: usize = 0;
+        match self.state {
+            ConnectionState::Encrypted => {},
+            _ => {
+                return Err(OssuaryError::InvalidPacket(
+                    "Encrypted channel not established.".into()));
+            }
+        }
+
+        match read_packet(self, in_buf) {
+            Ok((pkt, bytes)) => {
+                bytes_read += bytes;
+                if pkt.header.msg_id != self.remote_msg_id {
+                    let msg_id = pkt.header.msg_id;
+                    println!("Message gap detected.  Restarting connection. ({} != {})",
+                             msg_id, self.remote_msg_id);
+                    println!("Server: {}", self.is_server());
+                    self.reset_state(None);
+                    return Err(OssuaryError::InvalidPacket("Message ID mismatch".into()))
+                }
+                self.remote_msg_id = pkt.header.msg_id + 1;
+
+                match pkt.kind() {
+                    PacketType::EncryptedData => {
+                        match interpret_packet_extra::<EncryptedPacket>(&pkt) {
+                            Ok((data_pkt, rest)) => {
+                                let ciphertext = &rest[..data_pkt.data_len as usize];
+                                let tag = &rest[data_pkt.data_len as usize..];
+                                let aad = [];
+                                let mut plaintext = Vec::with_capacity(ciphertext.len());
+                                let session_key = match self.local_key.session {
+                                    Some(ref k) => k,
+                                    None => {
+                                        self.reset_state(None);
+                                        return Err(OssuaryError::InvalidKey);
+                                    }
+                                };
+                                let remote_nonce = match self.remote_key {
+                                    Some(ref rem) => rem.nonce,
+                                    None => {
+                                        self.reset_state(None);
+                                        return Err(OssuaryError::InvalidKey);
+                                    }
+                                };
+                                decrypt(session_key.as_bytes(),
+                                        &remote_nonce,
+                                        &aad, &ciphertext, &tag, &mut plaintext)?;
+                                bytes_written = match out_buf.write(&plaintext) {
+                                    Ok(w) => w,
+                                    Err(e) => return Err(e.into()),
+                                };
+                            },
+                            Err(_) => {
+                                self.reset_state(None);
+                                return Err(OssuaryError::InvalidKey);
+                            },
+                        }
+                    },
+                    _ => {
+                        return Err(OssuaryError::InvalidPacket(
+                            "Received non-encrypted data on encrypted channel.".into()));
+                    },
+                }
+            },
+            Err(OssuaryError::WouldBlock(b)) => {
+                return Err(OssuaryError::WouldBlock(b));
+            },
+            Err(_e) => {
+                self.reset_state(None);
+                return Err(OssuaryError::InvalidPacket("Packet header did not parse.".into()));
+            },
+        }
+        Ok((bytes_read, bytes_written))
+    }
+
+    pub fn flush<R,V>(&mut self,
+                      mut out_buf: R) -> Result<usize, OssuaryError>
+    where R: std::ops::DerefMut<Target = V>,
+          V: std::io::Write {
+        return write_stored_packet(self, &mut out_buf);
+    }
+}

diff --git a/src/connection.rs b/src/connection.rs
line changes: +161/-0
index 0000000..210de68
--- /dev/null
+++ b/src/connection.rs
@@ -0,0 +1,161 @@
+use crate::*;
+
+//use rand::thread_rng;
+use rand::RngCore;
+use rand::rngs::OsRng;
+
+impl OssuaryConnection {
+    /// Allocate a new OssuaryConnection.
+    ///
+    /// `conn_type` is a [`ConnectionType`] indicating whether this instance
+    /// is for a client or server.
+    pub fn new(conn_type: ConnectionType) -> OssuaryConnection {
+        //let mut rng = thread_rng();
+        let mut rng = OsRng::new().expect("RNG not available.");
+        let sec_key = EphemeralSecret::new(&mut rng);
+        let pub_key = EphemeralPublic::from(&sec_key);
+
+        let mut challenge: [u8; CHALLENGE_LEN] = [0; CHALLENGE_LEN];
+        let mut nonce: [u8; NONCE_LEN] = [0; NONCE_LEN];
+        rng.fill_bytes(&mut challenge);
+        rng.fill_bytes(&mut nonce);
+
+        let key = SessionKeyMaterial {
+            secret: Some(sec_key),
+            public: *pub_key.as_bytes(),
+            nonce: nonce,
+            session: None,
+        };
+        let auth = AuthKeyMaterial {
+            challenge: Some(challenge),
+            signature: None,
+            public_key: None,
+            secret_key: None,
+        };
+        OssuaryConnection {
+            state: match conn_type {
+                ConnectionType::Client => ConnectionState::ClientSendHandshake,
+                _ => ConnectionState::ServerWaitHandshake(std::time::SystemTime::now()),
+            },
+            conn_type: conn_type,
+            local_key: key,
+            local_auth: auth,
+            ..Default::default()
+        }
+    }
+
+    /// Reset the context back to its default state.
+    ///
+    /// If `permanent_err` is None, this connection can be re-established by
+    /// calling the connection handshake functions.  This indicates that
+    /// something unexpected went wrong with the connection, such as an invalid
+    /// state or corrupt data, such that reestablishing the connection may fix
+    /// it.
+    ///
+    /// If `permanent_err` is set to some error, it indicates a permanent
+    /// failure on this connection, and attempting to reestablish it will likely
+    /// not work.  This includes situations where the server has rejected the
+    /// connection, such as when the client's key is not authorized.
+    ///
+    pub(crate) fn reset_state(&mut self, permanent_err: Option<OssuaryError>) {
+        let default = OssuaryConnection::new(self.conn_type.clone());
+        *self = default;
+        self.state = match permanent_err {
+            None => {
+                match self.conn_type {
+                    ConnectionType::Client => ConnectionState::ClientSendHandshake,
+                    _ => ConnectionState::ServerWaitHandshake(std::time::SystemTime::now()),
+                }
+            },
+            Some(e) => {
+                ConnectionState::Failed(e)
+            }
+        };
+    }
+    /// Whether this context represents a server (as opposed to a client).
+    pub(crate) fn is_server(&self) -> bool {
+        match self.conn_type {
+            ConnectionType::Client => false,
+            _ => true,
+        }
+    }
+    /// Add key received from a remote connection and generate session key
+    pub(crate) fn add_remote_key(&mut self, public: &[u8; 32], nonce: &[u8; 12]) {
+        let key = SessionKeyMaterial {
+            secret: None,
+            public: public.to_owned(),
+            nonce: nonce.to_owned(),
+            session: None,
+        };
+        self.remote_key = Some(key);
+        let secret = self.local_key.secret.take();
+        if let Some(secret) = secret {
+            self.local_key.session = Some(secret.diffie_hellman(&EphemeralPublic::from(*public)));
+        }
+    }
+    /// Add public keys of clients permitted to connect to this server.
+    ///
+    /// `keys` must be an iterable of `&[u8]` slices containing valid 32-byte
+    /// ed25519 public keys.  During the handshake, a client will be required
+    /// to sign a challenge with its secret signing key.  The client sends the
+    /// public key it signed with and the resulting signature, and the server
+    /// validates that the public key is in this provided list of keys prior
+    /// to validating the signature.
+    ///
+    /// If a client attempts to connect with a key not matching one of these
+    /// provided keys, a permanent connection failure is raised on both ends.
+    ///
+    /// **NOTE:** keys are only checked if the context was created with
+    /// [`ConnectionType::AuthenticatedServer`].
+    pub fn set_authorized_keys<'a,T>(&mut self, keys: T) -> Result<usize, OssuaryError>
+    where T: std::iter::IntoIterator<Item = &'a [u8]> {
+        let mut count: usize = 0;
+        for key in keys {
+            if key.len() != 32 {
+                return Err(OssuaryError::KeySize(32, key.len()));
+            }
+            let mut key_owned = [0u8; 32];
+            key_owned.copy_from_slice(key);
+            self.authorized_keys.push(key_owned);
+            count += 1;
+        }
+        Ok(count)
+    }
+    /// Add authentication secret signing key
+    ///
+    /// ´key` must be a `&[u8]` slice containing a valid 32-byte ed25519
+    /// signing key.  Signing keys should be kept secret and should be stored
+    /// securely.
+    ///
+    /// This key is used to authenticate during the handshake if the remote
+    /// server requires authentication.  During the handshake, the server will
+    /// send a challenge (a buffer of random bytes) which the client signs
+    /// with this secret key.  The client returns its public key and the
+    /// signature of the challenge data to identify which key it is using for
+    /// authentication, and to prove possession of the secret key.
+    ///
+    pub fn set_secret_key(&mut self, key: &[u8]) -> Result<(), OssuaryError> {
+        if key.len() != 32 {
+            return Err(OssuaryError::KeySize(32, key.len()));
+        }
+        let secret = SecretKey::from_bytes(key)?;
+        let public = PublicKey::from(&secret);
+        self.local_auth.secret_key = Some(secret);
+        self.local_auth.public_key = Some(public);
+        Ok(())
+    }
+    /// Get the client's authentication public verification key
+    ///
+    /// When a secret key is set with [`OssuaryConnection::set_secret_key`], the
+    /// matching public key is calculated.  This function returns that public
+    /// key, which can be shared with a remote server for future authentication.
+    ///
+    pub fn public_key(&self) -> Result<&[u8], OssuaryError> {
+        match self.local_auth.public_key {
+            None => Err(OssuaryError::InvalidKey),
+            Some(ref p) => {
+                Ok(p.as_bytes())
+            }
+        }
+    }
+}

diff --git a/src/error.rs b/src/error.rs
line changes: +138/-0
index 0000000..bfc014e
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,138 @@
+use crate::*;
+
+/// Error produced by Ossuary or one of its dependencies
+pub enum OssuaryError {
+    /// A problem with I/O read or writes.
+    ///
+    /// An Io error is most likely raised when using an input or output buffer
+    /// that is more complex than a simple in-memory buffer, such as a
+    /// [`std::net::TcpStream`]
+    Io(std::io::Error),
+
+    /// A buffer cannot complete a read/write without blocking.
+    ///
+    /// Ossuary is inherently a non-blocking library, and returns this error any
+    /// time it is unable to read or write more data.
+    ///
+    /// When using a buffer configured for non-blocking operation, such as a
+    /// [`std::net::TcpStream`], any non-blocking errors
+    /// ([`std::io::ErrorKind::WouldBlock`]) encounted by the buffer are raised
+    /// as this error.
+    ///
+    /// The error has a paired parameter indicating whether any data WAS read
+    /// or written (depending on the function called).  This can be non-zero
+    /// on operations that require multiple consecutive read/write operations
+    /// to the buffer if some but not all operations succeeded.
+    ///
+    /// When using an input or output buffer in a manner that requires manually
+    /// sending or clearing data from the buffer, such as when passing the data
+    /// from Ossuary through an in-memory buffer prior to handing it to a TCP
+    /// connection, the amount of bytes indicated by the paired parameter should
+    /// be processed immediately.
+    WouldBlock(usize), // bytes consumed
+
+    /// Error casting received bytes to a primitive type.
+    ///
+    /// This error likely indicates a sync or corruption error in the data
+    /// stream, and will trigger a connection reset.
+    Unpack(core::array::TryFromSliceError),
+
+    /// An invalid sized encryption key was encountered.
+    ///
+    /// This error is most likely caused by an attempt to register an invalid
+    /// secret or public key in [`OssuaryConnection::set_authorized_keys`] or
+    /// [`OssuaryConnection::set_secret_key`].  Both should be 32 bytes.
+    KeySize(usize, usize), // (expected, actual)
+
+    /// An error occurred when parsing or using an encryption key.
+    ///
+    /// This error indicates a problem when using an encryption key.  This could
+    /// be because an expected key is missing, the format is incorrect, it was
+    /// corrupted in memory or in transit, or the wrong key was used.
+    ///
+    /// This typically indicates an internal error, and will cause the
+    /// connection to reset.
+    InvalidKey,
+
+    /// The channel received an unexpected or malformed packet
+    ///
+    /// The associated string may describe the problem that went wrong.  This
+    /// might be encountered if packets are duplicated, dropped, or corrupted.
+    /// It typically indicates an internal error, and the connection will reset.
+    InvalidPacket(String),
+
+    /// Error casting a received packet to an internal struct format.
+    ///
+    /// This means a packet header was not found or corrupted, and will trigger
+    /// a connection reset.
+    InvalidStruct,
+
+    /// The signature received from a client failed to verify.
+    ///
+    /// This either indicates a key mismatch (public and secret keys are not
+    /// a valid pair), corruption in the stream, or a problem during the
+    /// handshake.  The connection will reset.
+    InvalidSignature,
+
+    /// The connection has reset, and reconnection may be possible.
+    ///
+    /// Ossuary does not attempt to recover from errors encountered on the data
+    /// stream.  If anything has gone wrong, it resets the connection.  When one
+    /// side resets, it always tells the other side to reset as well.
+    ///
+    /// This error indicates that whatever went wrong may have been a temporal
+    /// fluke, such as momentary corruption or a sync error.  Reconnection with
+    /// the same context may be possible.  This must be handled by returning to
+    /// the handshake loop.
+    ConnectionReset,
+
+    /// The connection has reset, and reconnection is not suggested.
+    ///
+    /// This indicates that an error has occurred that Ossuary suspects is
+    /// permanent, and that a reconnect will not succeed.  Errors include
+    /// failed authorization, such as a connection attempt fro a client whose
+    /// public key is not authorized.
+    ///
+    /// When one side fails, it attempts to trigger a failure on the other side
+    /// as well.
+    ConnectionFailed,
+}
+impl std::fmt::Debug for OssuaryError {
+    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match self {
+            OssuaryError::Io(e) => write!(f, "OssuaryError::Io {}", e),
+            OssuaryError::WouldBlock(_) => write!(f, "OssuaryError::WouldBlock"),
+            OssuaryError::Unpack(_) => write!(f, "OssuaryError::Unpack"),
+            OssuaryError::KeySize(_,_) => write!(f, "OssuaryError::KeySize"),
+            OssuaryError::InvalidKey => write!(f, "OssuaryError::InvalidKey"),
+            OssuaryError::InvalidPacket(_) => write!(f, "OssuaryError::InvalidPacket"),
+            OssuaryError::InvalidStruct => write!(f, "OssuaryError::InvalidStruct"),
+            OssuaryError::InvalidSignature => write!(f, "OssuaryError::InvalidSignature"),
+            OssuaryError::ConnectionReset => write!(f, "OssuaryError::ConnectionReset"),
+            OssuaryError::ConnectionFailed => write!(f, "OssuaryError::ConnectionFailed"),
+        }
+    }
+}
+impl From<std::io::Error> for OssuaryError {
+    fn from(error: std::io::Error) -> Self {
+        match error.kind() {
+            std::io::ErrorKind::WouldBlock => OssuaryError::WouldBlock(0),
+            _ => OssuaryError::Io(error),
+        }
+    }
+}
+impl From<core::array::TryFromSliceError> for OssuaryError {
+    fn from(error: core::array::TryFromSliceError) -> Self {
+        OssuaryError::Unpack(error)
+    }
+}
+impl From<ed25519_dalek::SignatureError> for OssuaryError {
+    fn from(_error: ed25519_dalek::SignatureError) -> Self {
+        OssuaryError::InvalidKey
+    }
+}
+impl From<chacha20_poly1305_aead::DecryptError> for OssuaryError {
+    fn from(_error: chacha20_poly1305_aead::DecryptError) -> Self {
+        OssuaryError::InvalidKey
+    }
+}

diff --git a/src/handshake.rs b/src/handshake.rs
line changes: +654/-0
index 0000000..a4cf738
--- /dev/null
+++ b/src/handshake.rs
@@ -0,0 +1,654 @@
+use crate::*;
+
+use comm::{read_packet, write_packet, write_stored_packet};
+
+const SERVER_HANDSHAKE_SUBPACKET_LEN: usize = ::std::mem::size_of::<ServerEncryptedHandshakePacket>() +
+    ::std::mem::size_of::<EncryptedPacket>() + TAG_LEN;
+const CLIENT_AUTH_SUBPACKET_LEN: usize = ::std::mem::size_of::<ClientEncryptedAuthenticationPacket>() +
+    ::std::mem::size_of::<EncryptedPacket>() + TAG_LEN;
+
+#[repr(C,packed)]
+struct ResetPacket {
+    len: u16,
+    _reserved: u16,
+}
+impl Default for ResetPacket {
+    fn default() -> ResetPacket {
+        ResetPacket {
+            len: ::std::mem::size_of::<ResetPacket> as u16,
+            _reserved: 0u16,
+        }
+    }
+}
+
+#[repr(C,packed)]
+struct ClientHandshakePacket {
+    len: u16,
+    version: u8,
+    _reserved: [u8; 5],
+    public_key: [u8; KEY_LEN],
+    nonce: [u8; NONCE_LEN],
+    challenge: [u8; CHALLENGE_LEN],
+}
+impl Default for ClientHandshakePacket {
+    fn default() -> ClientHandshakePacket {
+        ClientHandshakePacket {
+            len: (CHALLENGE_LEN + NONCE_LEN + KEY_LEN + 8) as u16,
+            version: PROTOCOL_VERSION,
+            _reserved: [0u8; 5],
+            public_key: [0u8; KEY_LEN],
+            nonce: [0u8; NONCE_LEN],
+            challenge: [0u8; CHALLENGE_LEN],
+        }
+    }
+}
+impl ClientHandshakePacket {
+    fn new(pubkey: &[u8], nonce: &[u8], challenge: &[u8]) -> ClientHandshakePacket {
+        let mut pkt: ClientHandshakePacket = Default::default();
+        pkt.public_key.copy_from_slice(pubkey);
+        pkt.nonce.copy_from_slice(nonce);
+        pkt.challenge.copy_from_slice(challenge);
+        pkt
+    }
+    fn from_packet(pkt: &NetworkPacket) -> Result<&ClientHandshakePacket, OssuaryError> {
+        let hs_pkt = interpret_packet::<ClientHandshakePacket>(&pkt);
+        // TODO: validate len/version fields
+        hs_pkt
+    }
+}
+
+#[repr(C,packed)]
+struct ServerEncryptedHandshakePacket {
+    public_key: [u8; KEY_LEN],
+    challenge: [u8; CHALLENGE_LEN],
+    signature: [u8; SIGNATURE_LEN],
+}
+impl Default for ServerEncryptedHandshakePacket {
+    fn default() -> ServerEncryptedHandshakePacket {
+        ServerEncryptedHandshakePacket {
+            public_key: [0u8; KEY_LEN],
+            challenge: [0u8; CHALLENGE_LEN],
+            signature: [0u8; SIGNATURE_LEN],
+        }
+    }
+}
+impl ServerEncryptedHandshakePacket {
+    fn from_bytes(data: &[u8]) -> Result<&ServerEncryptedHandshakePacket, OssuaryError> {
+        let s: &ServerEncryptedHandshakePacket = slice_as_struct(&data)?;
+        Ok(s)
+    }
+}
+
+#[repr(C,packed)]
+struct ServerHandshakePacket {
+    len: u16,
+    version: u8,
+    _reserved: [u8; 5],
+    public_key: [u8; KEY_LEN],
+    nonce: [u8; NONCE_LEN],
+    subpacket: [u8; SERVER_HANDSHAKE_SUBPACKET_LEN],
+}
+impl Default for ServerHandshakePacket {
+    fn default() -> ServerHandshakePacket {
+        ServerHandshakePacket {
+            len: (NONCE_LEN + KEY_LEN + SERVER_HANDSHAKE_SUBPACKET_LEN + 8) as u16,
+            version: PROTOCOL_VERSION,
+            _reserved: [0u8; 5],
+            public_key: [0u8; KEY_LEN],
+            nonce: [0u8; NONCE_LEN],
+            subpacket: [0; SERVER_HANDSHAKE_SUBPACKET_LEN],
+        }
+    }
+}
+impl ServerHandshakePacket {
+    fn new(session_pubkey: &[u8], nonce: &[u8], session_privkey: &[u8],
+           server_pubkey: &[u8], challenge: &[u8], signature: &[u8]) -> Result<ServerHandshakePacket, OssuaryError> {
+        let mut pkt: ServerHandshakePacket = Default::default();
+        let mut enc_pkt: ServerEncryptedHandshakePacket = Default::default();
+        pkt.public_key.copy_from_slice(session_pubkey);
+        pkt.nonce.copy_from_slice(nonce);
+        enc_pkt.public_key.copy_from_slice(server_pubkey);
+        enc_pkt.challenge.copy_from_slice(challenge);
+        enc_pkt.signature.copy_from_slice(signature);
+        let mut subpkt: &mut [u8] = &mut pkt.subpacket;
+        encrypt_to_bytes(session_privkey, nonce, struct_as_slice(&enc_pkt), &mut subpkt)?;
+        Ok(pkt)
+    }
+    fn from_packet(pkt: &NetworkPacket) -> Result<&ServerHandshakePacket, OssuaryError> {
+        let hs_pkt = interpret_packet::<ServerHandshakePacket>(&pkt);
+        // TODO: validate len/version fields
+        hs_pkt
+    }
+}
+
+#[repr(C,packed)]
+struct ClientEncryptedAuthenticationPacket {
+    public_key: [u8; KEY_LEN],
+    signature: [u8; SIGNATURE_LEN],
+}
+impl Default for ClientEncryptedAuthenticationPacket {
+    fn default() -> ClientEncryptedAuthenticationPacket {
+        ClientEncryptedAuthenticationPacket {
+            public_key: [0u8; KEY_LEN],
+            signature: [0u8; SIGNATURE_LEN],
+        }
+    }
+}
+impl ClientEncryptedAuthenticationPacket {
+    fn from_bytes(data: &[u8]) -> Result<&ClientEncryptedAuthenticationPacket, OssuaryError> {
+        let s: &ClientEncryptedAuthenticationPacket = slice_as_struct(&data)?;
+        Ok(s)
+    }
+}
+
+#[repr(C,packed)]
+struct ClientAuthenticationPacket {
+    len: u16,
+    version: u8,
+    _reserved: [u8; 5],
+    subpacket: [u8; CLIENT_AUTH_SUBPACKET_LEN],
+}
+impl Default for ClientAuthenticationPacket {
+    fn default() -> ClientAuthenticationPacket {
+        ClientAuthenticationPacket {
+            len: (CLIENT_AUTH_SUBPACKET_LEN + 8) as u16,
+            version: PROTOCOL_VERSION,
+            _reserved: [0u8; 5],
+            subpacket: [0u8; CLIENT_AUTH_SUBPACKET_LEN],
+        }
+    }
+}
+impl ClientAuthenticationPacket {
+    fn new(nonce: &[u8], session_privkey: &[u8],
+           client_pubkey: &[u8], signature: &[u8]) -> Result<ClientAuthenticationPacket, OssuaryError> {
+        let mut pkt: ClientAuthenticationPacket = Default::default();
+        let mut enc_pkt: ClientEncryptedAuthenticationPacket = Default::default();
+        enc_pkt.public_key.copy_from_slice(client_pubkey);
+        enc_pkt.signature.copy_from_slice(signature);
+        let mut subpkt: &mut [u8] = &mut pkt.subpacket;
+        encrypt_to_bytes(session_privkey, nonce, struct_as_slice(&enc_pkt), &mut subpkt)?;
+        Ok(pkt)
+    }
+    fn from_packet(pkt: &NetworkPacket) -> Result<&ClientAuthenticationPacket, OssuaryError> {
+        let hs_pkt = interpret_packet::<ClientAuthenticationPacket>(&pkt);
+        // TODO: validate len/version fields
+        hs_pkt
+    }
+}
+
+impl OssuaryConnection {
+    pub fn send_handshake<T,U>(&mut self, mut buf: T) -> Result<usize, OssuaryError>
+    where T: std::ops::DerefMut<Target = U>,
+          U: std::io::Write {
+        // Try to send any unsent buffered data
+        match write_stored_packet(self, &mut buf) {
+            Ok(w) if w == 0 => {},
+            Ok(w) => return Err(OssuaryError::WouldBlock(w)),
+            Err(e) => return Err(e),
+        }
+        let written = match self.state {
+            // No-op states
+            ConnectionState::Encrypted => {0},
+
+            // Timeout wait states
+            ConnectionState::ServerWaitHandshake(t) |
+            ConnectionState::ServerWaitAuthentication(t) |
+            ConnectionState::ClientWaitHandshake(t)  => {
+                let mut w: usize = 0;
+                // Wait for response, with timeout
+                if let Ok(dur) = t.elapsed() {
+                    if dur.as_secs() > MAX_HANDSHAKE_WAIT_TIME {
+                        let pkt: ResetPacket = Default::default();
+                        w = write_packet(self, &mut buf, struct_as_slice(&pkt),
+                                         PacketType::Reset)?;
+                        self.reset_state(None);
+                    }
+                }
+                w
+            },
+
+            // <client> --> [session x25519 public key,
+            //               session nonce,
+            //               client random challenge]      --> <server>
+            ConnectionState::ClientSendHandshake => {
+                // Send session public key and nonce to initiate connection
+                let chal = self.local_auth.challenge.unwrap_or([0u8; CHALLENGE_LEN]);
+                let pkt = ClientHandshakePacket::new(&self.local_key.public,
+                                                     &self.local_key.nonce,
+                                                     &chal);
+                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
+                                     PacketType::ClientHandshake)?;
+                self.state = ConnectionState::ClientWaitHandshake(std::time::SystemTime::now());
+                w
+            },
+
+            // <client> <-- [session x25519 public key,
+            //               session nonce],
+            //              [[server x25519 public key,
+            //                server random challenge,
+            //                client challenge signature]] <-- <server>
+            ConnectionState::ServerSendHandshake => {
+                // Get a local copy of server's secret auth key, if it has one.
+                // Default to 0s.
+                let server_secret = match self.local_auth.secret_key {
+                    Some(ref s) => match SecretKey::from_bytes(s.as_bytes()) {
+                        Ok(s) => Some(s),
+                        Err(_) => None,
+                    },
+                    _ => None,
+                };
+                // Sign the client's challenge if we have a key,
+                // default to 0s.
+                let sig: [u8; SIGNATURE_LEN] = match server_secret {
+                    Some(s) => {
+                        let server_public = PublicKey::from(&s);
+                        let keypair = Keypair { secret: s, public: server_public };
+                        match self.remote_auth.challenge {
+                            Some(ref c) => keypair.sign(c).to_bytes(),
+                            None => {
+                                self.reset_state(None);
+                                return Err(OssuaryError::InvalidSignature);
+                            }
+                        }
+                    },
+                    None => [0; SIGNATURE_LEN],
+                };
+                // Get server's public auth key, if it has one.
+                // Default to 0s.
+                let server_public = match self.local_auth.public_key {
+                    Some(ref p) => p.as_bytes(),
+                    None => &[0; KEY_LEN],
+                };
+                // Get session encryption key, which must be known by now.
+                let session = match self.local_key.session {
+                    Some(ref s) => s.as_bytes(),
+                    None => {
+                        self.reset_state(None);
+                        return Err(OssuaryError::InvalidKey);
+                    }
+                };
+                let chal = self.local_auth.challenge.unwrap_or([0u8; CHALLENGE_LEN]);
+                let pkt = ServerHandshakePacket::new(&self.local_key.public,
+                                                     &self.local_key.nonce,
+                                                     session,
+                                                     server_public,
+                                                     &chal,
+                                                     &sig)?;
+                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
+                                     PacketType::ServerHandshake)?;
+                self.state = ConnectionState::ServerWaitAuthentication(std::time::SystemTime::now());
+                w
+            },
+
+            // <client> --> [[client x25519 public key,
+            //                server challenge signature]] --> <server>
+            ConnectionState::ClientSendAuthentication => {
+                // Get a local copy of client's secret auth key, if it has one.
+                // Default to 0s.
+                let client_secret = match self.local_auth.secret_key {
+                    Some(ref s) => match SecretKey::from_bytes(s.as_bytes()) {
+                        Ok(s) => Some(s),
+                        Err(_) => None,
+                    },
+                    _ => None,
+                };
+                // Sign the client's challenge if we have a key,
+                // default to 0s.
+                let sig: [u8; SIGNATURE_LEN] = match client_secret {
+                    Some(s) => {
+                        let client_public = PublicKey::from(&s);
+                        let keypair = Keypair { secret: s, public: client_public };
+                        match self.remote_auth.challenge {
+                            Some(ref c) => keypair.sign(c).to_bytes(),
+                            None => {
+                                self.reset_state(None);
+                                return Err(OssuaryError::InvalidSignature);
+                            }
+                        }
+                    },
+                    None => [0; SIGNATURE_LEN],
+                };
+                // Get server's public auth key, if it has one.
+                // Default to 0s.
+                let client_public = match self.local_auth.public_key {
+                    Some(ref p) => p.as_bytes(),
+                    None => &[0; KEY_LEN],
+                };
+                // Get session encryption key, which must be known by now.
+                let session = match self.local_key.session {
+                    Some(ref s) => s.as_bytes(),
+                    None => {
+                        self.reset_state(None);
+                        return Err(OssuaryError::InvalidKey);
+                    }
+                };
+                let pkt = ClientAuthenticationPacket::new(&self.local_key.nonce,
+                                                          session,
+                                                          client_public,
+                                                          &sig)?;
+                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
+                                     PacketType::ClientAuthentication)?;
+                self.state = ConnectionState::Encrypted;
+                w
+            },
+
+            ConnectionState::Failed(ref _e) => {
+                // TODO: fail
+                unimplemented!();
+            },
+        };
+        Ok(written)
+    }
+    pub fn recv_handshake<T,U>(&mut self, buf: T) -> Result<usize, OssuaryError>
+    where T: std::ops::DerefMut<Target = U>,
+          U: std::io::Read {
+        match self.state {
+            ConnectionState::Encrypted => return Ok(0),
+            // Timeout wait states
+            ConnectionState::ServerWaitHandshake(t) |
+            ConnectionState::ServerWaitAuthentication(t) |
+            ConnectionState::ClientWaitHandshake(t)  => {
+                // Wait for response, with timeout
+                if let Ok(dur) = t.elapsed() {
+                    if dur.as_secs() > MAX_HANDSHAKE_WAIT_TIME {
+                        return Err(OssuaryError::ConnectionReset);
+                    }
+                }
+            },
+            _ => {},
+        }
+
+        let (pkt, bytes_read) = match read_packet(self, buf) {
+            Ok(t) => { t },
+            Err(OssuaryError::WouldBlock(b)) => {
+                return Err(OssuaryError::WouldBlock(b));
+            }
+            Err(e) => {
+                self.reset_state(None);
+                return Err(e);
+            }
+        };
+
+        match pkt.kind() {
+            PacketType::Reset => {
+                self.reset_state(None);
+                return Err(OssuaryError::ConnectionReset);
+            },
+            PacketType::Disconnect => {
+                self.reset_state(Some(OssuaryError::ConnectionFailed));
+                return Err(OssuaryError::ConnectionFailed);
+            },
+            _ => {},
+        }
+
+        if pkt.header.msg_id != self.remote_msg_id {
+            println!("Message gap detected.  Restarting connection.");
+            println!("Server: {}", self.is_server());
+            self.reset_state(None);
+            return Err(OssuaryError::InvalidPacket("Message ID does not match".into()));
+        }
+        self.remote_msg_id = pkt.header.msg_id + 1;
+
+        println!("Recv packet: ({}) {:?} <- {:?}", self.is_server(), self.state, pkt.kind());
+        match self.state {
+            // Non-receive states.  Receiving handshake data is an error.
+            ConnectionState::ClientSendHandshake |
+            ConnectionState::ClientSendAuthentication |
+            ConnectionState::ServerSendHandshake |
+            ConnectionState::Encrypted => {
+                self.reset_state(None);
+                return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+            },
+
+            // <client> --> [session x25519 public key,
+            //               session nonce,
+            //               client random challenge]      --> <server>
+            ConnectionState::ServerWaitHandshake(_) => {
+                match pkt.kind() {
+                    PacketType::ClientHandshake => {
+                        if let Ok(inner_pkt) = ClientHandshakePacket::from_packet(&pkt) {
+                            let mut chal: [u8; CHALLENGE_LEN] = Default::default();
+                            chal.copy_from_slice(&inner_pkt.challenge);
+                            self.add_remote_key(&inner_pkt.public_key, &inner_pkt.nonce);
+                            self.remote_auth = AuthKeyMaterial {
+                                challenge: Some(chal),
+                                public_key: None,
+                                signature: None,
+                                secret_key: None,
+                            };
+                            self.state = ConnectionState::ServerSendHandshake;
+                        }
+                    },
+                    _ => {
+                        self.reset_state(None);
+                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                    },
+                }
+            },
+
+            // <client> <-- [session x25519 public key,
+            //               session nonce],
+            //              [[server x25519 public key,
+            //                server random challenge,
+            //                client challenge signature]] <-- <server>
+            ConnectionState::ClientWaitHandshake(_t) => {
+                match pkt.kind() {
+                    PacketType::ServerHandshake => {
+                        // TODO: handle error, reset state
+                        if let Ok(inner_pkt) = ServerHandshakePacket::from_packet(&pkt) {
+                            self.add_remote_key(&inner_pkt.public_key, &inner_pkt.nonce);
+                            let mut plaintext: [u8; SERVER_HANDSHAKE_SUBPACKET_LEN] = [0u8; SERVER_HANDSHAKE_SUBPACKET_LEN];
+                            let session = match self.local_key.session {
+                                Some(ref s) => s.as_bytes(),
+                                _ => {
+                                    self.reset_state(None);
+                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                                }
+                            };
+                            let nonce = match self.remote_key {
+                                Some(ref k) => k.nonce,
+                                _ => {
+                                    self.reset_state(None);
+                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                                }
+                            };
+                            let mut pt: &mut [u8] = &mut plaintext;
+                            // note: pt is consumed by decrypt_to_bytes
+                            let _ = decrypt_to_bytes(session, &nonce, &inner_pkt.subpacket, &mut pt)?;
+                            if let Ok(enc_pkt) = ServerEncryptedHandshakePacket::from_bytes(&plaintext) {
+                                let mut chal: [u8; CHALLENGE_LEN] = [0u8; CHALLENGE_LEN];
+                                let mut sig: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
+                                chal.copy_from_slice(&enc_pkt.challenge);
+                                sig.copy_from_slice(&enc_pkt.signature);
+                                let pubkey = match PublicKey::from_bytes(&enc_pkt.public_key) {
+                                    Ok(p) => p,
+                                    _ => {
+                                        self.reset_state(None);
+                                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                                    }
+                                };
+                                let signature = match Signature::from_bytes(&sig) {
+                                    Ok(s) => s,
+                                    Err(_) => {
+                                        self.reset_state(None);
+                                        return Err(OssuaryError::InvalidSignature);
+                                    }
+                                };
+                                // TODO: support trust on first use
+                                if self.authorized_keys.len() > 0 {
+                                    if chal.iter().all(|x| *x == 0) ||
+                                        sig.iter().all(|x| *x == 0) ||
+                                        enc_pkt.public_key.iter().all(|x| *x == 0) {
+                                            // Parameters must be non-zero
+                                            self.reset_state(None);
+                                            return Err(OssuaryError::InvalidSignature);
+                                        }
+                                    match pubkey.verify(&chal, &signature) {
+                                        Ok(_) => {},
+                                        Err(_) => {
+                                            self.reset_state(None);
+                                            return Err(OssuaryError::InvalidSignature);
+                                        },
+                                    }
+                                }
+                                self.remote_auth = AuthKeyMaterial {
+                                    challenge: Some(chal),
+                                    public_key: Some(pubkey),
+                                    signature: Some(sig),
+                                    secret_key: None,
+                                };
+                                self.state = ConnectionState::ClientSendAuthentication;
+                            }
+                        }
+                    },
+                    _ => {
+                        self.reset_state(None);
+                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                    },
+                }
+            },
+
+            // <client> --> [[client x25519 public key,
+            //                server challenge signature]] --> <server>
+            ConnectionState::ServerWaitAuthentication(_t) => {
+                match pkt.kind() {
+                    PacketType::ClientAuthentication => {
+                        // TODO: handle error, reset state
+                        if let Ok(inner_pkt) = ClientAuthenticationPacket::from_packet(&pkt) {
+                            let mut plaintext: [u8; CLIENT_AUTH_SUBPACKET_LEN] = [0u8; CLIENT_AUTH_SUBPACKET_LEN];
+                            let session = match self.local_key.session {
+                                Some(ref s) => s.as_bytes(),
+                                _ => {
+                                    self.reset_state(None);
+                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                                }
+                            };
+                            let nonce = match self.remote_key {
+                                Some(ref k) => k.nonce,
+                                _ => {
+                                    self.reset_state(None);
+                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                                }
+                            };
+                            let mut pt: &mut [u8] = &mut plaintext;
+                            // note: pt is consumed by decrypt_to_bytes
+                            let _ = decrypt_to_bytes(session, &nonce, &inner_pkt.subpacket, &mut pt)?;
+                            if let Ok(enc_pkt) = ClientEncryptedAuthenticationPacket::from_bytes(&plaintext) {
+                                let mut sig: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
+                                sig.copy_from_slice(&enc_pkt.signature);
+                                let pubkey = match PublicKey::from_bytes(&enc_pkt.public_key) {
+                                    Ok(p) => p,
+                                    _ => {
+                                        self.reset_state(None);
+                                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                                    }
+                                };
+                                let challenge = self.local_auth.challenge.unwrap_or([0u8; CHALLENGE_LEN]);
+                                let signature = match Signature::from_bytes(&sig) {
+                                    Ok(s) => s,
+                                    Err(_) => {
+                                        self.reset_state(None);
+                                        return Err(OssuaryError::InvalidSignature);
+                                    }
+                                };
+                                match self.conn_type {
+                                    // TODO: only permit known pubkeys
+                                    ConnectionType::AuthenticatedServer => {
+                                        if challenge.iter().all(|x| *x == 0) ||
+                                            sig.iter().all(|x| *x == 0) ||
+                                            enc_pkt.public_key.iter().all(|x| *x == 0) {
+                                                // Parameters must be non-zero
+                                                self.reset_state(None);
+                                                return Err(OssuaryError::InvalidSignature);
+                                        }
+                                        match pubkey.verify(&challenge, &signature) {
+                                            Ok(_) => {},
+                                            Err(_) => {
+                                                self.reset_state(None);
+                                                return Err(OssuaryError::InvalidSignature);
+                                            },
+                                        }
+                                    }
+                                    _ => {},
+                                }
+                                self.remote_auth.signature = Some(sig);
+                                self.remote_auth.public_key = Some(pubkey);
+                                self.state = ConnectionState::Encrypted;
+                            }
+                        }
+                    },
+                    _ => {
+                        self.reset_state(None);
+                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
+                    },
+                }
+            },
+
+            ConnectionState::Failed(ref _e) => {
+                // TODO: fail
+                unimplemented!();
+            },
+
+        };
+        Ok(bytes_read)
+    }
+
+
+    /// Returns whether the handshake process is complete.
+    ///
+    ///
+    pub fn handshake_done(&self) -> Result<bool, &OssuaryError> {
+        match self.state {
+            ConnectionState::Encrypted => Ok(true),
+            ConnectionState::Failed(ref e) => Err(e),
+            _ => Ok(false),
+        }
+    }
+}
+
+fn encrypt_to_bytes<T,U>(session_key: &[u8], nonce: &[u8],
+                         data: &[u8], mut out: T) -> Result<usize, OssuaryError>
+where T: std::ops::DerefMut<Target = U>,
+      U: std::io::Write {
+    let aad = [];
+    let mut ciphertext = Vec::with_capacity(data.len());
+    let tag = match encrypt(session_key,
+                            nonce,
+                            &aad,
+                            data,
+                            &mut ciphertext) {
+        Ok(t) => t,
+        Err(_) => {
+            return Err(OssuaryError::InvalidKey);
+        }
+    };
+    let pkt: EncryptedPacket = EncryptedPacket {
+        tag_len: tag.len() as u16,
+        data_len: ciphertext.len() as u16,
+    };
+    let mut size = 0;
+    size += out.write(struct_as_slice(&pkt))?;
+    size += out.write(&ciphertext)?;
+    size += out.write(&tag)?;
+    Ok(size)
+}
+
+fn decrypt_to_bytes<T,U>(session_key: &[u8], nonce: &[u8],
+                         data: &[u8], mut out: T) -> Result<usize, OssuaryError>
+where T: std::ops::DerefMut<Target = U>,
+      U: std::io::Write {
+    let s: &EncryptedPacket = slice_as_struct(data)?;
+    if s.tag_len != 16 {
+        return Err(OssuaryError::InvalidPacket("Invalid packet length".into()));
+    }
+    let data_pkt = s;
+    let rest = &data[::std::mem::size_of::<EncryptedPacket>()..];
+    let ciphertext = &rest[..data_pkt.data_len as usize];
+    let tag = &rest[data_pkt.data_len as usize..];
+    let aad = [];
+    decrypt(session_key,
+            &nonce,
+            &aad, &ciphertext, &tag,
+            out.deref_mut())?;
+    Ok(ciphertext.len())
+}

diff --git a/src/lib.rs b/src/lib.rs
line changes: +27/-1227
index 18c88f7..1212046
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -133,22 +133,22 @@
 // TODO: raise OssuaryError::UntrustedServer() when trust-on-first-use
 
 pub mod clib;
+mod connection;
+mod handshake;
+mod comm;
+mod error;
+
+pub use error::OssuaryError;
 
 extern crate x25519_dalek;
 extern crate ed25519_dalek;
 extern crate rand;
 extern crate chacha20_poly1305_aead;
 
-use std::convert::TryInto;
-
 use chacha20_poly1305_aead::{encrypt,decrypt};
 use x25519_dalek::{EphemeralSecret, EphemeralPublic, SharedSecret};
 use ed25519_dalek::{Signature, Keypair, SecretKey, PublicKey};
 
-//use rand::thread_rng;
-use rand::RngCore;
-use rand::rngs::OsRng;
-
 const PROTOCOL_VERSION: u8 = 1u8;
 
 // Maximum time to wait (in seconds) for a handshake response
@@ -168,391 +168,22 @@ const PACKET_BUF_SIZE: usize = 16384
     + ::std::mem::size_of::<EncryptedPacket>()
     + TAG_LEN;
 
-fn struct_as_slice<T: Sized>(p: &T) -> &[u8] {
-    unsafe {
-        ::std::slice::from_raw_parts(
-            (p as *const T) as *const u8,
-            ::std::mem::size_of::<T>(),
-        )
-    }
-}
-fn slice_as_struct<T>(p: &[u8]) -> Result<&T, OssuaryError> {
-    unsafe {
-        if p.len() < ::std::mem::size_of::<T>() {
-            return Err(OssuaryError::InvalidStruct);
-        }
-        Ok(&*(&p[..::std::mem::size_of::<T>()] as *const [u8] as *const T))
-    }
-}
-
-/// Error produced by Ossuary or one of its dependencies
-pub enum OssuaryError {
-    /// A problem with I/O read or writes.
-    ///
-    /// An Io error is most likely raised when using an input or output buffer
-    /// that is more complex than a simple in-memory buffer, such as a
-    /// [`std::net::TcpStream`]
-    Io(std::io::Error),
-
-    /// A buffer cannot complete a read/write without blocking.
-    ///
-    /// Ossuary is inherently a non-blocking library, and returns this error any
-    /// time it is unable to read or write more data.
-    ///
-    /// When using a buffer configured for non-blocking operation, such as a
-    /// [`std::net::TcpStream`], any non-blocking errors
-    /// ([`std::io::ErrorKind::WouldBlock`]) encounted by the buffer are raised
-    /// as this error.
-    ///
-    /// The error has a paired parameter indicating whether any data WAS read
-    /// or written (depending on the function called).  This can be non-zero
-    /// on operations that require multiple consecutive read/write operations
-    /// to the buffer if some but not all operations succeeded.
-    ///
-    /// When using an input or output buffer in a manner that requires manually
-    /// sending or clearing data from the buffer, such as when passing the data
-    /// from Ossuary through an in-memory buffer prior to handing it to a TCP
-    /// connection, the amount of bytes indicated by the paired parameter should
-    /// be processed immediately.
-    WouldBlock(usize), // bytes consumed
-
-    /// Error casting received bytes to a primitive type.
-    ///
-    /// This error likely indicates a sync or corruption error in the data
-    /// stream, and will trigger a connection reset.
-    Unpack(core::array::TryFromSliceError),
-
-    /// An invalid sized encryption key was encountered.
-    ///
-    /// This error is most likely caused by an attempt to register an invalid
-    /// secret or public key in [`OssuaryConnection::set_authorized_keys`] or
-    /// [`OssuaryConnection::set_secret_key`].  Both should be 32 bytes.
-    KeySize(usize, usize), // (expected, actual)
-
-    /// An error occurred when parsing or using an encryption key.
-    ///
-    /// This error indicates a problem when using an encryption key.  This could
-    /// be because an expected key is missing, the format is incorrect, it was
-    /// corrupted in memory or in transit, or the wrong key was used.
-    ///
-    /// This typically indicates an internal error, and will cause the
-    /// connection to reset.
-    InvalidKey,
-
-    /// The channel received an unexpected or malformed packet
-    ///
-    /// The associated string may describe the problem that went wrong.  This
-    /// might be encountered if packets are duplicated, dropped, or corrupted.
-    /// It typically indicates an internal error, and the connection will reset.
-    InvalidPacket(String),
-
-    /// Error casting a received packet to an internal struct format.
-    ///
-    /// This means a packet header was not found or corrupted, and will trigger
-    /// a connection reset.
-    InvalidStruct,
-
-    /// The signature received from a client failed to verify.
-    ///
-    /// This either indicates a key mismatch (public and secret keys are not
-    /// a valid pair), corruption in the stream, or a problem during the
-    /// handshake.  The connection will reset.
-    InvalidSignature,
-
-    /// The connection has reset, and reconnection may be possible.
-    ///
-    /// Ossuary does not attempt to recover from errors encountered on the data
-    /// stream.  If anything has gone wrong, it resets the connection.  When one
-    /// side resets, it always tells the other side to reset as well.
-    ///
-    /// This error indicates that whatever went wrong may have been a temporal
-    /// fluke, such as momentary corruption or a sync error.  Reconnection with
-    /// the same context may be possible.  This must be handled by returning to
-    /// the handshake loop.
-    ConnectionReset,
-
-    /// The connection has reset, and reconnection is not suggested.
-    ///
-    /// This indicates that an error has occurred that Ossuary suspects is
-    /// permanent, and that a reconnect will not succeed.  Errors include
-    /// failed authorization, such as a connection attempt fro a client whose
-    /// public key is not authorized.
-    ///
-    /// When one side fails, it attempts to trigger a failure on the other side
-    /// as well.
-    ConnectionFailed,
-}
-impl std::fmt::Debug for OssuaryError {
-    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
-        match self {
-            OssuaryError::Io(e) => write!(f, "OssuaryError::Io {}", e),
-            OssuaryError::WouldBlock(_) => write!(f, "OssuaryError::WouldBlock"),
-            OssuaryError::Unpack(_) => write!(f, "OssuaryError::Unpack"),
-            OssuaryError::KeySize(_,_) => write!(f, "OssuaryError::KeySize"),
-            OssuaryError::InvalidKey => write!(f, "OssuaryError::InvalidKey"),
-            OssuaryError::InvalidPacket(_) => write!(f, "OssuaryError::InvalidPacket"),
-            OssuaryError::InvalidStruct => write!(f, "OssuaryError::InvalidStruct"),
-            OssuaryError::InvalidSignature => write!(f, "OssuaryError::InvalidSignature"),
-            OssuaryError::ConnectionReset => write!(f, "OssuaryError::ConnectionReset"),
-            OssuaryError::ConnectionFailed => write!(f, "OssuaryError::ConnectionFailed"),
-        }
-    }
-}
-impl From<std::io::Error> for OssuaryError {
-    fn from(error: std::io::Error) -> Self {
-        match error.kind() {
-            std::io::ErrorKind::WouldBlock => OssuaryError::WouldBlock(0),
-            _ => OssuaryError::Io(error),
-        }
-    }
-}
-impl From<core::array::TryFromSliceError> for OssuaryError {
-    fn from(error: core::array::TryFromSliceError) -> Self {
-        OssuaryError::Unpack(error)
-    }
-}
-impl From<ed25519_dalek::SignatureError> for OssuaryError {
-    fn from(_error: ed25519_dalek::SignatureError) -> Self {
-        OssuaryError::InvalidKey
-    }
-}
-impl From<chacha20_poly1305_aead::DecryptError> for OssuaryError {
-    fn from(_error: chacha20_poly1305_aead::DecryptError) -> Self {
-        OssuaryError::InvalidKey
-    }
-}
-
-/// Represents the packet sent during client/server handshaking to exchange
-/// ephemeral session public keys and random nonces.
-#[repr(C,packed)]
-struct HandshakePacket {
-    len: u16,
-    _reserved: u16,
-    public_key: [u8; 32],
-    nonce: [u8; 12],
-}
-impl Default for HandshakePacket {
-    fn default() -> HandshakePacket {
-        HandshakePacket {
-        len: 48,
-        _reserved: 0u16,
-        public_key: [0u8; 32],
-        nonce: [0u8; 12],
-        }
-    }
-}
-
-#[repr(C,packed)]
-struct ClientHandshakePacket {
-    len: u16,
-    version: u8,
-    _reserved: [u8; 5],
-    public_key: [u8; KEY_LEN],
-    nonce: [u8; NONCE_LEN],
-    challenge: [u8; CHALLENGE_LEN],
-}
-impl Default for ClientHandshakePacket {
-    fn default() -> ClientHandshakePacket {
-        ClientHandshakePacket {
-            len: (CHALLENGE_LEN + NONCE_LEN + KEY_LEN + 8) as u16,
-            version: PROTOCOL_VERSION,
-            _reserved: [0u8; 5],
-            public_key: [0u8; KEY_LEN],
-            nonce: [0u8; NONCE_LEN],
-            challenge: [0u8; CHALLENGE_LEN],
-        }
-    }
-}
-impl ClientHandshakePacket {
-    fn new(pubkey: &[u8], nonce: &[u8], challenge: &[u8]) -> ClientHandshakePacket {
-        let mut pkt: ClientHandshakePacket = Default::default();
-        pkt.public_key.copy_from_slice(pubkey);
-        pkt.nonce.copy_from_slice(nonce);
-        pkt.challenge.copy_from_slice(challenge);
-        pkt
-    }
-    fn from_packet(pkt: &NetworkPacket) -> Result<&ClientHandshakePacket, OssuaryError> {
-        let hs_pkt = interpret_packet::<ClientHandshakePacket>(&pkt);
-        // TODO: validate len/version fields
-        hs_pkt
-    }
-}
-
-const SERVER_HANDSHAKE_SUBPACKET_LEN: usize = ::std::mem::size_of::<ServerEncryptedHandshakePacket>() +
-    ::std::mem::size_of::<EncryptedPacket>() + TAG_LEN;
-
-#[repr(C,packed)]
-struct ServerHandshakePacket {
-    len: u16,
-    version: u8,
-    _reserved: [u8; 5],
-    public_key: [u8; KEY_LEN],
-    nonce: [u8; NONCE_LEN],
-    subpacket: [u8; SERVER_HANDSHAKE_SUBPACKET_LEN],
-}
-#[repr(C,packed)]
-struct ServerEncryptedHandshakePacket {
-    public_key: [u8; KEY_LEN],
-    challenge: [u8; CHALLENGE_LEN],
-    signature: [u8; SIGNATURE_LEN],
-}
-impl Default for ServerEncryptedHandshakePacket {
-    fn default() -> ServerEncryptedHandshakePacket {
-        ServerEncryptedHandshakePacket {
-            public_key: [0u8; KEY_LEN],
-            challenge: [0u8; CHALLENGE_LEN],
-            signature: [0u8; SIGNATURE_LEN],
-        }
-    }
-}
-impl ServerEncryptedHandshakePacket {
-    fn from_bytes(data: &[u8]) -> Result<&ServerEncryptedHandshakePacket, OssuaryError> {
-        let s: &ServerEncryptedHandshakePacket = slice_as_struct(&data)?;
-        Ok(s)
-    }
-}
-fn encrypt_to_bytes<T,U>(session_key: &[u8], nonce: &[u8],
-                         data: &[u8], mut out: T) -> Result<usize, OssuaryError>
-where T: std::ops::DerefMut<Target = U>,
-      U: std::io::Write {
-    let aad = [];
-    let mut ciphertext = Vec::with_capacity(data.len());
-    let tag = match encrypt(session_key,
-                            nonce,
-                            &aad,
-                            data,
-                            &mut ciphertext) {
-        Ok(t) => t,
-        Err(_) => {
-            return Err(OssuaryError::InvalidKey);
-        }
-    };
-    let pkt: EncryptedPacket = EncryptedPacket {
-        tag_len: tag.len() as u16,
-        data_len: ciphertext.len() as u16,
-    };
-    let mut size = 0;
-    size += out.write(struct_as_slice(&pkt))?;
-    size += out.write(&ciphertext)?;
-    size += out.write(&tag)?;
-    Ok(size)
-}
-
-fn decrypt_to_bytes<T,U>(session_key: &[u8], nonce: &[u8],
-                         data: &[u8], mut out: T) -> Result<usize, OssuaryError>
-where T: std::ops::DerefMut<Target = U>,
-      U: std::io::Write {
-    let s: &EncryptedPacket = slice_as_struct(data)?;
-    if s.tag_len != 16 {
-        return Err(OssuaryError::InvalidPacket("Invalid packet length".into()));
-    }
-    let data_pkt = s;
-    let rest = &data[::std::mem::size_of::<EncryptedPacket>()..];
-    let ciphertext = &rest[..data_pkt.data_len as usize];
-    let tag = &rest[data_pkt.data_len as usize..];
-    let aad = [];
-    decrypt(session_key,
-            &nonce,
-            &aad, &ciphertext, &tag,
-            out.deref_mut())?;
-    Ok(ciphertext.len())
-}
-
-impl Default for ServerHandshakePacket {
-    fn default() -> ServerHandshakePacket {
-        ServerHandshakePacket {
-            len: (NONCE_LEN + KEY_LEN + SERVER_HANDSHAKE_SUBPACKET_LEN + 8) as u16,
-            version: PROTOCOL_VERSION,
-            _reserved: [0u8; 5],
-            public_key: [0u8; KEY_LEN],
-            nonce: [0u8; NONCE_LEN],
-            subpacket: [0; SERVER_HANDSHAKE_SUBPACKET_LEN],
-        }
-    }
-}
-impl ServerHandshakePacket {
-    fn new(session_pubkey: &[u8], nonce: &[u8], session_privkey: &[u8],
-           server_pubkey: &[u8], challenge: &[u8], signature: &[u8]) -> Result<ServerHandshakePacket, OssuaryError> {
-        let mut pkt: ServerHandshakePacket = Default::default();
-        let mut enc_pkt: ServerEncryptedHandshakePacket = Default::default();
-        pkt.public_key.copy_from_slice(session_pubkey);
-        pkt.nonce.copy_from_slice(nonce);
-        enc_pkt.public_key.copy_from_slice(server_pubkey);
-        enc_pkt.challenge.copy_from_slice(challenge);
-        enc_pkt.signature.copy_from_slice(signature);
-        let mut subpkt: &mut [u8] = &mut pkt.subpacket;
-        encrypt_to_bytes(session_privkey, nonce, struct_as_slice(&enc_pkt), &mut subpkt)?;
-        Ok(pkt)
-    }
-    fn from_packet(pkt: &NetworkPacket) -> Result<&ServerHandshakePacket, OssuaryError> {
-        let hs_pkt = interpret_packet::<ServerHandshakePacket>(&pkt);
-        // TODO: validate len/version fields
-        hs_pkt
-    }
-}
-
-#[repr(C,packed)]
-struct ClientEncryptedAuthenticationPacket {
-    public_key: [u8; KEY_LEN],
-    signature: [u8; SIGNATURE_LEN],
-}
-impl Default for ClientEncryptedAuthenticationPacket {
-    fn default() -> ClientEncryptedAuthenticationPacket {
-        ClientEncryptedAuthenticationPacket {
-            public_key: [0u8; KEY_LEN],
-            signature: [0u8; SIGNATURE_LEN],
-        }
-    }
-}
-impl ClientEncryptedAuthenticationPacket {
-    fn from_bytes(data: &[u8]) -> Result<&ClientEncryptedAuthenticationPacket, OssuaryError> {
-        let s: &ClientEncryptedAuthenticationPacket = slice_as_struct(&data)?;
-        Ok(s)
-    }
-}
-
-const CLIENT_AUTH_SUBPACKET_LEN: usize = ::std::mem::size_of::<ClientEncryptedAuthenticationPacket>() +
-    ::std::mem::size_of::<EncryptedPacket>() + TAG_LEN;
-
-#[repr(C,packed)]
-struct ClientAuthenticationPacket {
-    len: u16,
-    version: u8,
-    _reserved: [u8; 5],
-    subpacket: [u8; CLIENT_AUTH_SUBPACKET_LEN],
-}
-impl Default for ClientAuthenticationPacket {
-    fn default() -> ClientAuthenticationPacket {
-        ClientAuthenticationPacket {
-            len: (CLIENT_AUTH_SUBPACKET_LEN + 8) as u16,
-            version: PROTOCOL_VERSION,
-            _reserved: [0u8; 5],
-            subpacket: [0u8; CLIENT_AUTH_SUBPACKET_LEN],
-        }
-    }
+/// Internal struct for holding a complete network packet
+struct NetworkPacket {
+    header: PacketHeader,
+    /// Data.  If encrypted, also EncryptedPacket header and HMAC tag.
+    data: Box<[u8]>,
 }
-impl ClientAuthenticationPacket {
-    fn new(nonce: &[u8], session_privkey: &[u8],
-           client_pubkey: &[u8], signature: &[u8]) -> Result<ClientAuthenticationPacket, OssuaryError> {
-        let mut pkt: ClientAuthenticationPacket = Default::default();
-        let mut enc_pkt: ClientEncryptedAuthenticationPacket = Default::default();
-        enc_pkt.public_key.copy_from_slice(client_pubkey);
-        enc_pkt.signature.copy_from_slice(signature);
-        let mut subpkt: &mut [u8] = &mut pkt.subpacket;
-        encrypt_to_bytes(session_privkey, nonce, struct_as_slice(&enc_pkt), &mut subpkt)?;
-        Ok(pkt)
-    }
-    fn from_packet(pkt: &NetworkPacket) -> Result<&ClientAuthenticationPacket, OssuaryError> {
-        let hs_pkt = interpret_packet::<ClientAuthenticationPacket>(&pkt);
-        // TODO: validate len/version fields
-        hs_pkt
+impl NetworkPacket {
+    fn kind(&self) -> PacketType {
+        self.header.packet_type
     }
 }
 
 /// The packet types used by the Ossuary protocol.
 #[repr(u16)]
 #[derive(Clone, Copy, Debug)]
-enum PacketType {
+pub(crate) enum PacketType {
     /// Should never be encountered.
     Unknown = 0x00,
 
@@ -592,7 +223,7 @@ enum PacketType {
 }
 impl PacketType {
     /// Convert u16 integer to a PacketType enum
-    fn from_u16(i: u16) -> PacketType {
+    pub fn from_u16(i: u16) -> PacketType {
         match i {
             0x01 => PacketType::ClientHandshake,
             0x02 => PacketType::ServerHandshake,
@@ -627,18 +258,6 @@ struct PacketHeader {
     _reserved: u16,
 }
 
-/// Internal struct for holding a complete network packet
-struct NetworkPacket {
-    header: PacketHeader,
-    /// Data.  If encrypted, also EncryptedPacket header and HMAC tag.
-    data: Box<[u8]>,
-}
-impl NetworkPacket {
-    fn kind(&self) -> PacketType {
-        self.header.packet_type
-    }
-}
-
 /// Internal state of OssuaryConnection state machine.
 #[derive(Debug)]
 enum ConnectionState {
@@ -693,22 +312,13 @@ struct AuthKeyMaterial {
     signature: Option<[u8; SIGNATURE_LEN]>,
 }
 
+#[derive(Default)]
 struct SessionKeyMaterial {
     secret: Option<EphemeralSecret>,
     public: [u8; 32],
     session: Option<SharedSecret>,
     nonce: [u8; 12],
 }
-impl Default for SessionKeyMaterial {
-    fn default() -> Self {
-        SessionKeyMaterial {
-            secret: None,
-            session: None,
-            public: [0u8; KEY_LEN],
-            nonce: [0u8; NONCE_LEN],
-        }
-    }
-}
 
 /// Enum specifying the client or server role of a [`OssuaryConnection`]
 #[derive(Clone)]
@@ -791,724 +401,6 @@ impl Default for OssuaryConnection {
         }
     }
 }
-impl OssuaryConnection {
-    /// Allocate a new OssuaryConnection.
-    ///
-    /// `conn_type` is a [`ConnectionType`] indicating whether this instance
-    /// is for a client or server.
-    pub fn new(conn_type: ConnectionType) -> OssuaryConnection {
-        //let mut rng = thread_rng();
-        let mut rng = OsRng::new().expect("RNG not available.");
-        let sec_key = EphemeralSecret::new(&mut rng);
-        let pub_key = EphemeralPublic::from(&sec_key);
-
-        let mut challenge: [u8; CHALLENGE_LEN] = [0; CHALLENGE_LEN];
-        let mut nonce: [u8; NONCE_LEN] = [0; NONCE_LEN];
-        rng.fill_bytes(&mut challenge);
-        rng.fill_bytes(&mut nonce);
-
-        let key = SessionKeyMaterial {
-            secret: Some(sec_key),
-            public: *pub_key.as_bytes(),
-            nonce: nonce,
-            session: None,
-        };
-        let auth = AuthKeyMaterial {
-            challenge: Some(challenge),
-            signature: None,
-            public_key: None,
-            secret_key: None,
-        };
-        OssuaryConnection {
-            state: match conn_type {
-                ConnectionType::Client => ConnectionState::ClientSendHandshake,
-                _ => ConnectionState::ServerWaitHandshake(std::time::SystemTime::now()),
-            },
-            conn_type: conn_type,
-            local_key: key,
-            local_auth: auth,
-            ..Default::default()
-        }
-    }
-
-    /// Reset the context back to its default state.
-    ///
-    /// If `permanent_err` is None, this connection can be re-established by
-    /// calling the connection handshake functions.  This indicates that
-    /// something unexpected went wrong with the connection, such as an invalid
-    /// state or corrupt data, such that reestablishing the connection may fix
-    /// it.
-    ///
-    /// If `permanent_err` is set to some error, it indicates a permanent
-    /// failure on this connection, and attempting to reestablish it will likely
-    /// not work.  This includes situations where the server has rejected the
-    /// connection, such as when the client's key is not authorized.
-    ///
-    fn reset_state(&mut self, permanent_err: Option<OssuaryError>) {
-        let default = OssuaryConnection::new(self.conn_type.clone());
-        *self = default;
-        self.state = match permanent_err {
-            None => {
-                match self.conn_type {
-                    ConnectionType::Client => ConnectionState::ClientSendHandshake,
-                    _ => ConnectionState::ServerWaitHandshake(std::time::SystemTime::now()),
-                }
-            },
-            Some(e) => {
-                ConnectionState::Failed(e)
-            }
-        };
-    }
-    /// Whether this context represents a server (as opposed to a client).
-    fn is_server(&self) -> bool {
-        match self.conn_type {
-            ConnectionType::Client => false,
-            _ => true,
-        }
-    }
-    /// Add key received from a remote connection and generate session key
-    fn add_remote_key(&mut self, public: &[u8; 32], nonce: &[u8; 12]) {
-        let key = SessionKeyMaterial {
-            secret: None,
-            public: public.to_owned(),
-            nonce: nonce.to_owned(),
-            session: None,
-        };
-        self.remote_key = Some(key);
-        let secret = self.local_key.secret.take();
-        if let Some(secret) = secret {
-            self.local_key.session = Some(secret.diffie_hellman(&EphemeralPublic::from(*public)));
-        }
-    }
-    /// Add public keys of clients permitted to connect to this server.
-    ///
-    /// `keys` must be an iterable of `&[u8]` slices containing valid 32-byte
-    /// ed25519 public keys.  During the handshake, a client will be required
-    /// to sign a challenge with its secret signing key.  The client sends the
-    /// public key it signed with and the resulting signature, and the server
-    /// validates that the public key is in this provided list of keys prior
-    /// to validating the signature.
-    ///
-    /// If a client attempts to connect with a key not matching one of these
-    /// provided keys, a permanent connection failure is raised on both ends.
-    ///
-    /// **NOTE:** keys are only checked if the context was created with
-    /// [`ConnectionType::AuthenticatedServer`].
-    pub fn set_authorized_keys<'a,T>(&mut self, keys: T) -> Result<usize, OssuaryError>
-    where T: std::iter::IntoIterator<Item = &'a [u8]> {
-        let mut count: usize = 0;
-        for key in keys {
-            if key.len() != 32 {
-                return Err(OssuaryError::KeySize(32, key.len()));
-            }
-            let mut key_owned = [0u8; 32];
-            key_owned.copy_from_slice(key);
-            self.authorized_keys.push(key_owned);
-            count += 1;
-        }
-        Ok(count)
-    }
-    /// Add authentication secret signing key
-    ///
-    /// ´key` must be a `&[u8]` slice containing a valid 32-byte ed25519
-    /// signing key.  Signing keys should be kept secret and should be stored
-    /// securely.
-    ///
-    /// This key is used to authenticate during the handshake if the remote
-    /// server requires authentication.  During the handshake, the server will
-    /// send a challenge (a buffer of random bytes) which the client signs
-    /// with this secret key.  The client returns its public key and the
-    /// signature of the challenge data to identify which key it is using for
-    /// authentication, and to prove possession of the secret key.
-    ///
-    pub fn set_secret_key(&mut self, key: &[u8]) -> Result<(), OssuaryError> {
-        if key.len() != 32 {
-            return Err(OssuaryError::KeySize(32, key.len()));
-        }
-        let secret = SecretKey::from_bytes(key)?;
-        let public = PublicKey::from(&secret);
-        self.local_auth.secret_key = Some(secret);
-        self.local_auth.public_key = Some(public);
-        Ok(())
-    }
-    /// Get the client's authentication public verification key
-    ///
-    /// When a secret key is set with [`OssuaryConnection::set_secret_key`], the
-    /// matching public key is calculated.  This function returns that public
-    /// key, which can be shared with a remote server for future authentication.
-    ///
-    pub fn public_key(&self) -> Result<&[u8], OssuaryError> {
-        match self.local_auth.public_key {
-            None => Err(OssuaryError::InvalidKey),
-            Some(ref p) => {
-                Ok(p.as_bytes())
-            }
-        }
-    }
-
-    pub fn send_handshake<T,U>(&mut self, mut buf: T) -> Result<usize, OssuaryError>
-    where T: std::ops::DerefMut<Target = U>,
-          U: std::io::Write {
-        // Try to send any unsent buffered data
-        match write_stored_packet(self, &mut buf) {
-            Ok(w) if w == 0 => {},
-            Ok(w) => return Err(OssuaryError::WouldBlock(w)),
-            Err(e) => return Err(e),
-        }
-        let written = match self.state {
-            // No-op states
-            ConnectionState::Encrypted => {0},
-
-            // Timeout wait states
-            ConnectionState::ServerWaitHandshake(t) |
-            ConnectionState::ServerWaitAuthentication(t) |
-            ConnectionState::ClientWaitHandshake(t)  => {
-                let mut w: usize = 0;
-                // Wait for response, with timeout
-                if let Ok(dur) = t.elapsed() {
-                    if dur.as_secs() > MAX_HANDSHAKE_WAIT_TIME {
-                        let pkt: HandshakePacket = Default::default();
-                        w = write_packet(self, &mut buf, struct_as_slice(&pkt),
-                                         PacketType::Reset)?;
-                        self.reset_state(None);
-                    }
-                }
-                w
-            },
-
-            // <client> --> [session x25519 public key,
-            //               session nonce,
-            //               client random challenge]      --> <server>
-            ConnectionState::ClientSendHandshake => {
-                // Send session public key and nonce to initiate connection
-                let chal = self.local_auth.challenge.unwrap_or([0u8; CHALLENGE_LEN]);
-                let pkt = ClientHandshakePacket::new(&self.local_key.public,
-                                                     &self.local_key.nonce,
-                                                     &chal);
-                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
-                                     PacketType::ClientHandshake)?;
-                self.state = ConnectionState::ClientWaitHandshake(std::time::SystemTime::now());
-                w
-            },
-
-            // <client> <-- [session x25519 public key,
-            //               session nonce],
-            //              [[server x25519 public key,
-            //                server random challenge,
-            //                client challenge signature]] <-- <server>
-            ConnectionState::ServerSendHandshake => {
-                // Get a local copy of server's secret auth key, if it has one.
-                // Default to 0s.
-                let server_secret = match self.local_auth.secret_key {
-                    Some(ref s) => match SecretKey::from_bytes(s.as_bytes()) {
-                        Ok(s) => Some(s),
-                        Err(_) => None,
-                    },
-                    _ => None,
-                };
-                // Sign the client's challenge if we have a key,
-                // default to 0s.
-                let sig: [u8; SIGNATURE_LEN] = match server_secret {
-                    Some(s) => {
-                        let server_public = PublicKey::from(&s);
-                        let keypair = Keypair { secret: s, public: server_public };
-                        match self.remote_auth.challenge {
-                            Some(ref c) => keypair.sign(c).to_bytes(),
-                            None => {
-                                self.reset_state(None);
-                                return Err(OssuaryError::InvalidSignature);
-                            }
-                        }
-                    },
-                    None => [0; SIGNATURE_LEN],
-                };
-                // Get server's public auth key, if it has one.
-                // Default to 0s.
-                let server_public = match self.local_auth.public_key {
-                    Some(ref p) => p.as_bytes(),
-                    None => &[0; KEY_LEN],
-                };
-                // Get session encryption key, which must be known by now.
-                let session = match self.local_key.session {
-                    Some(ref s) => s.as_bytes(),
-                    None => {
-                        self.reset_state(None);
-                        return Err(OssuaryError::InvalidKey);
-                    }
-                };
-                let chal = self.local_auth.challenge.unwrap_or([0u8; CHALLENGE_LEN]);
-                let pkt = ServerHandshakePacket::new(&self.local_key.public,
-                                                     &self.local_key.nonce,
-                                                     session,
-                                                     server_public,
-                                                     &chal,
-                                                     &sig)?;
-                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
-                                     PacketType::ServerHandshake)?;
-                self.state = ConnectionState::ServerWaitAuthentication(std::time::SystemTime::now());
-                w
-            },
-
-            // <client> --> [[client x25519 public key,
-            //                server challenge signature]] --> <server>
-            ConnectionState::ClientSendAuthentication => {
-                // Get a local copy of client's secret auth key, if it has one.
-                // Default to 0s.
-                let client_secret = match self.local_auth.secret_key {
-                    Some(ref s) => match SecretKey::from_bytes(s.as_bytes()) {
-                        Ok(s) => Some(s),
-                        Err(_) => None,
-                    },
-                    _ => None,
-                };
-                // Sign the client's challenge if we have a key,
-                // default to 0s.
-                let sig: [u8; SIGNATURE_LEN] = match client_secret {
-                    Some(s) => {
-                        let client_public = PublicKey::from(&s);
-                        let keypair = Keypair { secret: s, public: client_public };
-                        match self.remote_auth.challenge {
-                            Some(ref c) => keypair.sign(c).to_bytes(),
-                            None => {
-                                self.reset_state(None);
-                                return Err(OssuaryError::InvalidSignature);
-                            }
-                        }
-                    },
-                    None => [0; SIGNATURE_LEN],
-                };
-                // Get server's public auth key, if it has one.
-                // Default to 0s.
-                let client_public = match self.local_auth.public_key {
-                    Some(ref p) => p.as_bytes(),
-                    None => &[0; KEY_LEN],
-                };
-                // Get session encryption key, which must be known by now.
-                let session = match self.local_key.session {
-                    Some(ref s) => s.as_bytes(),
-                    None => {
-                        self.reset_state(None);
-                        return Err(OssuaryError::InvalidKey);
-                    }
-                };
-                let pkt = ClientAuthenticationPacket::new(&self.local_key.nonce,
-                                                          session,
-                                                          client_public,
-                                                          &sig)?;
-                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
-                                     PacketType::ClientAuthentication)?;
-                self.state = ConnectionState::Encrypted;
-                w
-            },
-
-            ConnectionState::Failed(ref e) => {0},
-        };
-        Ok(written)
-    }
-    pub fn recv_handshake<T,U>(&mut self, buf: T) -> Result<usize, OssuaryError>
-    where T: std::ops::DerefMut<Target = U>,
-          U: std::io::Read {
-        match self.state {
-            ConnectionState::Encrypted => return Ok(0),
-            // Timeout wait states
-            ConnectionState::ServerWaitHandshake(t) |
-            ConnectionState::ServerWaitAuthentication(t) |
-            ConnectionState::ClientWaitHandshake(t)  => {
-                // Wait for response, with timeout
-                if let Ok(dur) = t.elapsed() {
-                    if dur.as_secs() > MAX_HANDSHAKE_WAIT_TIME {
-                        return Err(OssuaryError::ConnectionReset);
-                    }
-                }
-            },
-            _ => {},
-        }
-
-        let (pkt, bytes_read) = match read_packet(self, buf) {
-            Ok(t) => { t },
-            Err(OssuaryError::WouldBlock(b)) => {
-                return Err(OssuaryError::WouldBlock(b));
-            }
-            Err(e) => {
-                self.reset_state(None);
-                return Err(e);
-            }
-        };
-
-        match pkt.kind() {
-            PacketType::Reset => {
-                self.reset_state(None);
-                return Err(OssuaryError::ConnectionReset);
-            },
-            PacketType::Disconnect => {
-                self.reset_state(Some(OssuaryError::ConnectionFailed));
-                return Err(OssuaryError::ConnectionFailed);
-            },
-            _ => {},
-        }
-
-        if pkt.header.msg_id != self.remote_msg_id {
-            println!("Message gap detected.  Restarting connection.");
-            println!("Server: {}", self.is_server());
-            self.reset_state(None);
-            return Err(OssuaryError::InvalidPacket("Message ID does not match".into()));
-        }
-        self.remote_msg_id = pkt.header.msg_id + 1;
-
-        println!("Recv packet: ({}) {:?} <- {:?}", self.is_server(), self.state, pkt.kind());
-        match self.state {
-            // Non-receive states.  Receiving handshake data is an error.
-            ConnectionState::ClientSendHandshake |
-            ConnectionState::ClientSendAuthentication |
-            ConnectionState::ServerSendHandshake |
-            ConnectionState::Encrypted => {
-                self.reset_state(None);
-                return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-            },
-
-            // <client> --> [session x25519 public key,
-            //               session nonce,
-            //               client random challenge]      --> <server>
-            ConnectionState::ServerWaitHandshake(_) => {
-                match pkt.kind() {
-                    PacketType::ClientHandshake => {
-                        if let Ok(inner_pkt) = ClientHandshakePacket::from_packet(&pkt) {
-                            let mut chal: [u8; CHALLENGE_LEN] = Default::default();
-                            chal.copy_from_slice(&inner_pkt.challenge);
-                            self.add_remote_key(&inner_pkt.public_key, &inner_pkt.nonce);
-                            self.remote_auth = AuthKeyMaterial {
-                                challenge: Some(chal),
-                                public_key: None,
-                                signature: None,
-                                secret_key: None,
-                            };
-                            self.state = ConnectionState::ServerSendHandshake;
-                        }
-                    },
-                    _ => {
-                        self.reset_state(None);
-                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                    },
-                }
-            },
-
-            // <client> <-- [session x25519 public key,
-            //               session nonce],
-            //              [[server x25519 public key,
-            //                server random challenge,
-            //                client challenge signature]] <-- <server>
-            ConnectionState::ClientWaitHandshake(_t) => {
-                match pkt.kind() {
-                    PacketType::ServerHandshake => {
-                        // TODO: handle error, reset state
-                        if let Ok(inner_pkt) = ServerHandshakePacket::from_packet(&pkt) {
-                            self.add_remote_key(&inner_pkt.public_key, &inner_pkt.nonce);
-                            let mut plaintext: [u8; SERVER_HANDSHAKE_SUBPACKET_LEN] = [0u8; SERVER_HANDSHAKE_SUBPACKET_LEN];
-                            let session = match self.local_key.session {
-                                Some(ref s) => s.as_bytes(),
-                                _ => {
-                                    self.reset_state(None);
-                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                                }
-                            };
-                            let nonce = match self.remote_key {
-                                Some(ref k) => k.nonce,
-                                _ => {
-                                    self.reset_state(None);
-                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                                }
-                            };
-                            let mut pt: &mut [u8] = &mut plaintext;
-                            // note: pt is consumed by decrypt_to_bytes
-                            let _ = decrypt_to_bytes(session, &nonce, &inner_pkt.subpacket, &mut pt)?;
-                            if let Ok(enc_pkt) = ServerEncryptedHandshakePacket::from_bytes(&plaintext) {
-                                let mut chal: [u8; CHALLENGE_LEN] = [0u8; CHALLENGE_LEN];
-                                let mut sig: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
-                                chal.copy_from_slice(&enc_pkt.challenge);
-                                sig.copy_from_slice(&enc_pkt.signature);
-                                let pubkey = match PublicKey::from_bytes(&enc_pkt.public_key) {
-                                    Ok(p) => p,
-                                    _ => {
-                                        self.reset_state(None);
-                                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                                    }
-                                };
-                                let signature = match Signature::from_bytes(&sig) {
-                                    Ok(s) => s,
-                                    Err(_) => {
-                                        self.reset_state(None);
-                                        return Err(OssuaryError::InvalidSignature);
-                                    }
-                                };
-                                // TODO: support trust on first use
-                                if self.authorized_keys.len() > 0 {
-                                    if chal.iter().all(|x| *x == 0) ||
-                                        sig.iter().all(|x| *x == 0) ||
-                                        enc_pkt.public_key.iter().all(|x| *x == 0) {
-                                            // Parameters must be non-zero
-                                            self.reset_state(None);
-                                            return Err(OssuaryError::InvalidSignature);
-                                        }
-                                    match pubkey.verify(&chal, &signature) {
-                                        Ok(_) => {},
-                                        Err(_) => {
-                                            self.reset_state(None);
-                                            return Err(OssuaryError::InvalidSignature);
-                                        },
-                                    }
-                                }
-                                self.remote_auth = AuthKeyMaterial {
-                                    challenge: Some(chal),
-                                    public_key: Some(pubkey),
-                                    signature: Some(sig),
-                                    secret_key: None,
-                                };
-                                self.state = ConnectionState::ClientSendAuthentication;
-                            }
-                        }
-                    },
-                    _ => {
-                        self.reset_state(None);
-                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                    },
-                }
-            },
-
-            // <client> --> [[client x25519 public key,
-            //                server challenge signature]] --> <server>
-            ConnectionState::ServerWaitAuthentication(_t) => {
-                match pkt.kind() {
-                    PacketType::ClientAuthentication => {
-                        // TODO: handle error, reset state
-                        if let Ok(inner_pkt) = ClientAuthenticationPacket::from_packet(&pkt) {
-                            let mut plaintext: [u8; CLIENT_AUTH_SUBPACKET_LEN] = [0u8; CLIENT_AUTH_SUBPACKET_LEN];
-                            let session = match self.local_key.session {
-                                Some(ref s) => s.as_bytes(),
-                                _ => {
-                                    self.reset_state(None);
-                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                                }
-                            };
-                            let nonce = match self.remote_key {
-                                Some(ref k) => k.nonce,
-                                _ => {
-                                    self.reset_state(None);
-                                    return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                                }
-                            };
-                            let mut pt: &mut [u8] = &mut plaintext;
-                            // note: pt is consumed by decrypt_to_bytes
-                            let _ = decrypt_to_bytes(session, &nonce, &inner_pkt.subpacket, &mut pt)?;
-                            if let Ok(enc_pkt) = ClientEncryptedAuthenticationPacket::from_bytes(&plaintext) {
-                                let mut sig: [u8; SIGNATURE_LEN] = [0u8; SIGNATURE_LEN];
-                                sig.copy_from_slice(&enc_pkt.signature);
-                                let pubkey = match PublicKey::from_bytes(&enc_pkt.public_key) {
-                                    Ok(p) => p,
-                                    _ => {
-                                        self.reset_state(None);
-                                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                                    }
-                                };
-                                let challenge = self.local_auth.challenge.unwrap_or([0u8; CHALLENGE_LEN]);
-                                let signature = match Signature::from_bytes(&sig) {
-                                    Ok(s) => s,
-                                    Err(_) => {
-                                        self.reset_state(None);
-                                        return Err(OssuaryError::InvalidSignature);
-                                    }
-                                };
-                                match self.conn_type {
-                                    // TODO: only permit known pubkeys
-                                    ConnectionType::AuthenticatedServer => {
-                                        if challenge.iter().all(|x| *x == 0) ||
-                                            sig.iter().all(|x| *x == 0) ||
-                                            enc_pkt.public_key.iter().all(|x| *x == 0) {
-                                                // Parameters must be non-zero
-                                                self.reset_state(None);
-                                                return Err(OssuaryError::InvalidSignature);
-                                        }
-                                        match pubkey.verify(&challenge, &signature) {
-                                            Ok(_) => {},
-                                            Err(_) => {
-                                                self.reset_state(None);
-                                                return Err(OssuaryError::InvalidSignature);
-                                            },
-                                        }
-                                    }
-                                    _ => {},
-                                }
-                                self.remote_auth.signature = Some(sig);
-                                self.remote_auth.public_key = Some(pubkey);
-                                self.state = ConnectionState::Encrypted;
-                            }
-                        }
-                    },
-                    _ => {
-                        self.reset_state(None);
-                        return Err(OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()));
-                    },
-                }
-            },
-
-            ConnectionState::Failed(ref e) => {
-                // TODO: fail
-            },
-
-        };
-        Ok(bytes_read)
-    }
-
-
-    /// Returns whether the handshake process is complete.
-    ///
-    ///
-    pub fn handshake_done(&self) -> Result<bool, &OssuaryError> {
-        match self.state {
-            ConnectionState::Encrypted => Ok(true),
-            ConnectionState::Failed(ref e) => Err(e),
-            _ => Ok(false),
-        }
-    }
-
-    pub fn send_data<T,U>(&mut self,
-                          in_buf: &[u8],
-                          mut out_buf: T) -> Result<usize, OssuaryError>
-    where T: std::ops::DerefMut<Target = U>,
-          U: std::io::Write {
-        // Try to send any unsent buffered data
-        match write_stored_packet(self, &mut out_buf) {
-            Ok(w) if w == 0 => {},
-            Ok(w) => return Err(OssuaryError::WouldBlock(w)),
-            Err(e) => return Err(e),
-        }
-        match self.state {
-            ConnectionState::Encrypted => {},
-            _ => {
-                return Err(OssuaryError::InvalidPacket(
-                    "Encrypted channel not established.".into()));
-            }
-        }
-        let aad = [];
-        let mut ciphertext = Vec::with_capacity(in_buf.len());
-        let session_key = match self.local_key.session {
-            Some(ref k) => k,
-            None => {
-                self.reset_state(None);
-                return Err(OssuaryError::InvalidKey);;
-            }
-        };
-        let tag = match encrypt(session_key.as_bytes(),
-                                &self.local_key.nonce, &aad, in_buf, &mut ciphertext) {
-            Ok(t) => t,
-            Err(_) => {
-                self.reset_state(None);
-                return Err(OssuaryError::InvalidKey);;
-            }
-        };
-
-        let pkt: EncryptedPacket = EncryptedPacket {
-            tag_len: tag.len() as u16,
-            data_len: ciphertext.len() as u16,
-        };
-        let mut buf: Vec<u8>= vec![];
-        buf.extend(struct_as_slice(&pkt));
-        buf.extend(&ciphertext);
-        buf.extend(&tag);
-        let written = write_packet(self, &mut out_buf, &buf,
-                                   PacketType::EncryptedData)?;
-        Ok(written)
-    }
-
-    pub fn recv_data<T,U,R,V>(&mut self,
-                              in_buf: T,
-                              mut out_buf: R) -> Result<(usize, usize), OssuaryError>
-    where T: std::ops::DerefMut<Target = U>,
-          U: std::io::Read,
-          R: std::ops::DerefMut<Target = V>,
-          V: std::io::Write {
-        let bytes_written: usize;
-        let mut bytes_read: usize = 0;
-        match self.state {
-            ConnectionState::Encrypted => {},
-            _ => {
-                return Err(OssuaryError::InvalidPacket(
-                    "Encrypted channel not established.".into()));
-            }
-        }
-
-        match read_packet(self, in_buf) {
-            Ok((pkt, bytes)) => {
-                bytes_read += bytes;
-                if pkt.header.msg_id != self.remote_msg_id {
-                    let msg_id = pkt.header.msg_id;
-                    println!("Message gap detected.  Restarting connection. ({} != {})",
-                             msg_id, self.remote_msg_id);
-                    println!("Server: {}", self.is_server());
-                    self.reset_state(None);
-                    return Err(OssuaryError::InvalidPacket("Message ID mismatch".into()))
-                }
-                self.remote_msg_id = pkt.header.msg_id + 1;
-
-                match pkt.kind() {
-                    PacketType::EncryptedData => {
-                        match interpret_packet_extra::<EncryptedPacket>(&pkt) {
-                            Ok((data_pkt, rest)) => {
-                                let ciphertext = &rest[..data_pkt.data_len as usize];
-                                let tag = &rest[data_pkt.data_len as usize..];
-                                let aad = [];
-                                let mut plaintext = Vec::with_capacity(ciphertext.len());
-                                let session_key = match self.local_key.session {
-                                    Some(ref k) => k,
-                                    None => {
-                                        self.reset_state(None);
-                                        return Err(OssuaryError::InvalidKey);
-                                    }
-                                };
-                                let remote_nonce = match self.remote_key {
-                                    Some(ref rem) => rem.nonce,
-                                    None => {
-                                        self.reset_state(None);
-                                        return Err(OssuaryError::InvalidKey);
-                                    }
-                                };
-                                decrypt(session_key.as_bytes(),
-                                        &remote_nonce,
-                                        &aad, &ciphertext, &tag, &mut plaintext)?;
-                                bytes_written = match out_buf.write(&plaintext) {
-                                    Ok(w) => w,
-                                    Err(e) => return Err(e.into()),
-                                };
-                            },
-                            Err(_) => {
-                                self.reset_state(None);
-                                return Err(OssuaryError::InvalidKey);
-                            },
-                        }
-                    },
-                    _ => {
-                        return Err(OssuaryError::InvalidPacket(
-                            "Received non-encrypted data on encrypted channel.".into()));
-                    },
-                }
-            },
-            Err(OssuaryError::WouldBlock(b)) => {
-                return Err(OssuaryError::WouldBlock(b));
-            },
-            Err(_e) => {
-                self.reset_state(None);
-                return Err(OssuaryError::InvalidPacket("Packet header did not parse.".into()));
-            },
-        }
-        Ok((bytes_read, bytes_written))
-    }
-
-    pub fn flush<R,V>(&mut self,
-                      mut out_buf: R) -> Result<usize, OssuaryError>
-    where R: std::ops::DerefMut<Target = V>,
-          V: std::io::Write {
-        return write_stored_packet(self, &mut out_buf);
-    }
-}
 
 /// Cast the data bytes in a NetworkPacket into a struct
 fn interpret_packet<'a, T>(pkt: &'a NetworkPacket) -> Result<&'a T, OssuaryError> {
@@ -1524,113 +416,21 @@ fn interpret_packet_extra<'a, T>(pkt: &'a NetworkPacket)
     Ok((s, &pkt.data[::std::mem::size_of::<T>()..]))
 }
 
-/// Read a complete network packet from the input stream.
-///
-/// On success, returns a NetworkPacket struct containing the header and data,
-/// and a `usize` indicating how many bytes were consumed from the input buffer.
-fn read_packet<T,U>(conn: &mut OssuaryConnection,
-                    mut stream: T) ->Result<(NetworkPacket, usize), OssuaryError>
-where T: std::ops::DerefMut<Target = U>,
-      U: std::io::Read {
-    let header_size = ::std::mem::size_of::<PacketHeader>();
-    let bytes_read: usize;
-    match stream.read(&mut conn.read_buf[conn.read_buf_used..]) {
-        Ok(b) => bytes_read = b,
-        Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
-            return Err(OssuaryError::WouldBlock(0))
-        },
-        Err(e) => return Err(e.into()),
-    }
-    conn.read_buf_used += bytes_read;
-    let buf: &[u8] = &conn.read_buf;
-    let hdr = PacketHeader {
-        len: u16::from_be_bytes(buf[0..2].try_into()?),
-        msg_id: u16::from_be_bytes(buf[2..4].try_into()?),
-        packet_type: PacketType::from_u16(u16::from_be_bytes(buf[4..6].try_into()?)),
-        _reserved: u16::from_be_bytes(buf[6..8].try_into()?),
-    };
-    let packet_len = hdr.len as usize;
-    if conn.read_buf_used < header_size + packet_len {
-        if header_size + packet_len > PACKET_BUF_SIZE {
-            panic!("oversized packet");
-        }
-        return Err(OssuaryError::WouldBlock(bytes_read));
-    }
-    let buf: Box<[u8]> = (&conn.read_buf[header_size..header_size+packet_len])
-        .to_vec().into_boxed_slice();
-    let excess = conn.read_buf_used - header_size - packet_len;
+fn struct_as_slice<T: Sized>(p: &T) -> &[u8] {
     unsafe {
-        // no safe way to memmove() in Rust?
-        std::ptr::copy::<u8>(
-            conn.read_buf.as_ptr().offset((header_size + packet_len) as isize),
-            conn.read_buf.as_mut_ptr(),
-            excess);
+        ::std::slice::from_raw_parts(
+            (p as *const T) as *const u8,
+            ::std::mem::size_of::<T>(),
+        )
     }
-    conn.read_buf_used = excess;
-    Ok((NetworkPacket {
-        header: hdr,
-        data: buf,
-    },
-    header_size + packet_len))
 }
-
-/// Write a packet from OssuaryConnection's internal storage to the out buffer.
-///
-/// All packets are buffered to internal storage before writing, so this is
-/// the function responsible for putting all packets "on the wire".
-///
-/// On success, returns the number of bytes written to the output buffer
-fn write_stored_packet<T,U>(conn: &mut OssuaryConnection,
-                            stream: &mut T) -> Result<usize, OssuaryError>
-where T: std::ops::DerefMut<Target = U>,
-      U: std::io::Write {
-    let mut written = 0;
-    while written < conn.write_buf_used {
-        match stream.write(&conn.write_buf[written..conn.write_buf_used]) {
-            Ok(w) => {
-                written += w;
-            },
-            Err(e) => {
-                if written > 0 && written < conn.write_buf_used {
-                    unsafe {
-                        // no safe way to memmove() in Rust?
-                        std::ptr::copy::<u8>(
-                            conn.write_buf.as_ptr().offset(written as isize),
-                            conn.write_buf.as_mut_ptr(),
-                            conn.write_buf_used - written);
-                    }
-                }
-                conn.write_buf_used -= written;
-                return Err(e.into());
-            },
+fn slice_as_struct<T>(p: &[u8]) -> Result<&T, OssuaryError> {
+    unsafe {
+        if p.len() < ::std::mem::size_of::<T>() {
+            return Err(OssuaryError::InvalidStruct);
         }
+        Ok(&*(&p[..::std::mem::size_of::<T>()] as *const [u8] as *const T))
     }
-    conn.write_buf_used = 0;
-    Ok(written)
-}
-
-/// Write a packet to the OssuaryConnection's internal packet buffer
-///
-/// All packets are buffered internally because there is no guarantee that a
-/// complete packet can be written without blocking, and Ossuary is a non-
-/// blocking library.
-///
-/// On success, returns the number of bytes written to the output buffer.
-fn write_packet<T,U>(conn: &mut OssuaryConnection,
-                     stream: &mut T, data: &[u8],
-                     kind: PacketType) -> Result<usize, OssuaryError>
-where T: std::ops::DerefMut<Target = U>,
-      U: std::io::Write {
-    let msg_id = conn.local_msg_id as u16;
-    conn.write_buf[0..2].copy_from_slice(&(data.len() as u16).to_be_bytes());
-    conn.write_buf[2..4].copy_from_slice(&msg_id.to_be_bytes());
-    conn.write_buf[4..6].copy_from_slice(&(kind as u16).to_be_bytes());
-    conn.write_buf[6..8].copy_from_slice(&(0u16).to_be_bytes());
-    conn.write_buf[8..8+data.len()].copy_from_slice(&data);
-    conn.write_buf_used = 8 + data.len();
-    conn.local_msg_id += 1;
-    let written = write_stored_packet(conn, stream)?;
-    Ok(written)
 }
 
 #[cfg(test)]