summary history branches tags files
commit:2f3b59f9022c973a34603ce6a6cdac8a43f8ec61
author:Trevor Bentley
committer:Trevor Bentley
date:Thu May 23 17:40:38 2019 +0200
parents:ff034bf66c0afe8dc4eda96f08e201237142dd0e
Finished implementing and added tests for connection resets
diff --git a/src/comm.rs b/src/comm.rs
line changes: +20/-7
index ec8186f..cf8c472
--- a/src/comm.rs
+++ b/src/comm.rs
@@ -30,7 +30,7 @@ where T: std::ops::DerefMut<Target = U>,
     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::InvalidPacket("Oversized packet".into()));
         }
         return Err(OssuaryError::WouldBlock(bytes_read));
     }
@@ -183,16 +183,29 @@ impl OssuaryConnection {
             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()))
+                    match pkt.kind() {
+                        PacketType::Reset => {},
+                        _ => {
+                            let msg_id = pkt.header.msg_id;
+                            println!("Message gap detected.  Restarting connection. ({} != {})",
+                                     msg_id, self.remote_msg_id);
+                            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::Reset => {
+                        self.reset_state(None);
+                        self.state = ConnectionState::Resetting(false);
+                        return Err(OssuaryError::ConnectionReset(bytes_read));
+                    },
+                    PacketType::Disconnect => {
+                        self.reset_state(Some(OssuaryError::ConnectionFailed));
+                        return Err(OssuaryError::ConnectionFailed);
+                    },
                     PacketType::EncryptedData => {
                         match interpret_packet_extra::<EncryptedPacket>(&pkt) {
                             Ok((data_pkt, rest)) => {

diff --git a/src/connection.rs b/src/connection.rs
line changes: +35/-23
index 7e4b929..4dec8b5
--- a/src/connection.rs
+++ b/src/connection.rs
@@ -5,6 +5,21 @@ use rand::RngCore;
 use rand::rngs::OsRng;
 
 impl OssuaryConnection {
+    fn generate_session_material() -> Result<SessionKeyMaterial, OssuaryError> {
+        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 nonce: [u8; NONCE_LEN] = [0; NONCE_LEN];
+        rng.fill_bytes(&mut nonce);
+
+        Ok(SessionKeyMaterial {
+            secret: Some(sec_key),
+            public: *pub_key.as_bytes(),
+            nonce: nonce,
+            session: None,
+        })
+    }
+
     /// Allocate a new OssuaryConnection.
     ///
     /// `conn_type` is a [`ConnectionType`] indicating whether this instance
@@ -14,22 +29,13 @@ impl OssuaryConnection {
     /// used for host authentication.  If `None` is provided, a keypair will
     /// be generated for the lifetime of this connection object.
     pub fn new(conn_type: ConnectionType, auth_secret_key: Option<&[u8]>) -> Result<OssuaryConnection, OssuaryError> {
-        //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 key = OssuaryConnection::generate_session_material()?;
+
         let (auth_sec, auth_pub) = match auth_secret_key {
             Some(s) => {
                 // Use the given secret key
@@ -73,6 +79,14 @@ impl OssuaryConnection {
         })
     }
 
+    /// Get the initial state machine state of this connection
+    pub(crate) fn initial_state(&self) -> ConnectionState {
+        match self.conn_type {
+            ConnectionType::Client => ConnectionState::ClientSendHandshake,
+            _ => ConnectionState::ServerWaitHandshake(std::time::SystemTime::now()),
+        }
+    }
+
     /// Reset the context back to its default state.
     ///
     /// If `permanent_err` is None, this connection can be re-established by
@@ -87,7 +101,7 @@ impl OssuaryConnection {
     /// connection, such as when the client's key is not authorized.
     ///
     pub(crate) fn reset_state(&mut self, permanent_err: Option<OssuaryError>) {
-        self.local_key = Default::default();
+        self.local_key = OssuaryConnection::generate_session_material().unwrap_or_default();
         self.remote_key = None;
         self.local_msg_id = 0;
         self.remote_msg_id = 0;
@@ -96,20 +110,18 @@ impl OssuaryConnection {
         self.read_buf_used = 0;
         self.write_buf = [0u8; PACKET_BUF_SIZE];
         self.write_buf_used = 0;
-        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)
-            }
+        self.reset_count += 1;
+        let perm_error = match self.reset_count {
+            c if c < MAX_RESET_COUNT => permanent_err,
+            _ => Some(OssuaryError::ConnectionFailed),
+        };
+        self.state = match perm_error {
+            None => ConnectionState::Resetting(true),
+            Some(e) => ConnectionState::Failing(e),
         };
     }
     /// Whether this context represents a server (as opposed to a client).
-    pub(crate) fn is_server(&self) -> bool {
+    pub fn is_server(&self) -> bool {
         match self.conn_type {
             ConnectionType::Client => false,
             _ => true,

diff --git a/src/error.rs b/src/error.rs
line changes: +8/-7
index 5646ad8..29ab99b
--- a/src/error.rs
+++ b/src/error.rs
@@ -1,5 +1,6 @@
 use crate::*;
 
+#[derive(Clone, PartialEq)]
 /// Error produced by Ossuary or one of its dependencies
 pub enum OssuaryError {
     /// A problem with I/O read or writes.
@@ -7,7 +8,7 @@ pub enum OssuaryError {
     /// 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),
+    Io(String),
 
     /// A buffer cannot complete a read/write without blocking.
     ///
@@ -54,7 +55,7 @@ pub enum OssuaryError {
     ///
     /// This error likely indicates a sync or corruption error in the data
     /// stream, and will trigger a connection reset.
-    Unpack(core::array::TryFromSliceError),
+    Unpack(String),
 
     /// Error reading from random number generator
     NoRandomSource,
@@ -109,7 +110,7 @@ pub enum OssuaryError {
     /// 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,
+    ConnectionReset(usize),
 
     /// The connection has reset, and reconnection is not suggested.
     ///
@@ -131,10 +132,10 @@ impl std::fmt::Debug for OssuaryError {
             OssuaryError::NoRandomSource => write!(f, "OssuaryError::NoRandomSource"),
             OssuaryError::KeySize(_,_) => write!(f, "OssuaryError::KeySize"),
             OssuaryError::InvalidKey => write!(f, "OssuaryError::InvalidKey"),
-            OssuaryError::InvalidPacket(_) => write!(f, "OssuaryError::InvalidPacket"),
+            OssuaryError::InvalidPacket(m) => write!(f, "OssuaryError::InvalidPacket: {}", m),
             OssuaryError::InvalidStruct => write!(f, "OssuaryError::InvalidStruct"),
             OssuaryError::InvalidSignature => write!(f, "OssuaryError::InvalidSignature"),
-            OssuaryError::ConnectionReset => write!(f, "OssuaryError::ConnectionReset"),
+            OssuaryError::ConnectionReset(_) => write!(f, "OssuaryError::ConnectionReset"),
             OssuaryError::ConnectionFailed => write!(f, "OssuaryError::ConnectionFailed"),
             OssuaryError::UntrustedServer(_) => write!(f, "OssuaryError::UntrustedServer"),
             OssuaryError::DecryptionFailed => write!(f, "OssuaryError::DecryptionFailed"),
@@ -145,13 +146,13 @@ 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),
+            _ => OssuaryError::Io(error.to_string()),
         }
     }
 }
 impl From<core::array::TryFromSliceError> for OssuaryError {
     fn from(error: core::array::TryFromSliceError) -> Self {
-        OssuaryError::Unpack(error)
+        OssuaryError::Unpack(error.to_string())
     }
 }
 impl From<ed25519_dalek::SignatureError> for OssuaryError {

diff --git a/src/handshake.rs b/src/handshake.rs
line changes: +84/-31
index e2eb470..1b68725
--- a/src/handshake.rs
+++ b/src/handshake.rs
@@ -188,6 +188,8 @@ impl OssuaryConnection {
         }
         let written = match self.state {
             // No-op states
+            ConnectionState::Failed(_) |
+            ConnectionState::ResetWait |
             ConnectionState::Encrypted |
             ConnectionState::ClientRaiseUntrustedServer |
             ConnectionState::ClientWaitServerApproval => {0},
@@ -203,6 +205,7 @@ impl OssuaryConnection {
                         let pkt: ResetPacket = Default::default();
                         w = write_packet(self, &mut buf, struct_as_slice(&pkt),
                                          PacketType::Reset)?;
+                        self.local_msg_id = 0;
                         self.reset_state(None);
                     }
                 }
@@ -332,11 +335,35 @@ impl OssuaryConnection {
                 w
             },
 
-            ConnectionState::Failed(ref _e) => {
-                // TODO: fail
-                unimplemented!();
+            ConnectionState::Failing(_) => {
+                // Tell remote host to disconnect
+                let pkt: ResetPacket = Default::default();
+                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
+                                         PacketType::Disconnect)?;
+                w
             },
+
+            ConnectionState::Resetting(initial) => {
+                // Tell remote host to reset
+                let pkt: ResetPacket = Default::default();
+                let w = write_packet(self, &mut buf, struct_as_slice(&pkt),
+                                     PacketType::Reset)?;
+                self.local_msg_id = 0;
+                self.state = match initial {
+                    true => ConnectionState::ResetWait,
+                    false => self.initial_state(),
+                };
+                w
+            }
         };
+
+        // Finalize failure state if failing
+        match self.state {
+            ConnectionState::Failing(ref e) => {
+                self.state = ConnectionState::Failed(e.clone());
+            },
+            _ => {},
+        }
         Ok(written)
     }
     pub fn recv_handshake<T,U>(&mut self, buf: T) -> Result<usize, OssuaryError>
@@ -351,7 +378,8 @@ impl OssuaryConnection {
                 // Wait for response, with timeout
                 if let Ok(dur) = t.elapsed() {
                     if dur.as_secs() > MAX_HANDSHAKE_WAIT_TIME {
-                        return Err(OssuaryError::ConnectionReset);
+                        self.reset_state(None);
+                        return Err(OssuaryError::ConnectionReset(0));
                     }
                 }
             },
@@ -364,15 +392,21 @@ impl OssuaryConnection {
                 return Err(OssuaryError::WouldBlock(b));
             }
             Err(e) => {
-                self.reset_state(None);
+                self.reset_state(Some(e.clone()));
                 return Err(e);
             }
         };
 
         match pkt.kind() {
             PacketType::Reset => {
-                self.reset_state(None);
-                return Err(OssuaryError::ConnectionReset);
+                match self.state {
+                    ConnectionState::ResetWait => {},
+                    _ => {
+                        self.reset_state(None);
+                        self.state = ConnectionState::Resetting(false);
+                        return Err(OssuaryError::ConnectionReset(bytes_read));
+                    },
+                }
             },
             PacketType::Disconnect => {
                 self.reset_state(Some(OssuaryError::ConnectionFailed));
@@ -382,15 +416,27 @@ impl OssuaryConnection {
         }
 
         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()));
+            match pkt.kind() {
+                PacketType::Reset => {},
+                _ => {
+                    match self.state {
+                        ConnectionState::ResetWait => {},
+                        _ => {
+                            println!("Message gap detected.  Restarting connection.");
+                            self.reset_state(None);
+                            return Err(OssuaryError::InvalidPacket("Message ID does not match".into()));
+                        },
+                    }
+                },
+            }
         }
         self.remote_msg_id = pkt.header.msg_id + 1;
 
         match self.state {
             // no-op states
+            ConnectionState::Failing(_) |
+            ConnectionState::Failed(_) |
+            ConnectionState::Resetting(_) |
             ConnectionState::ClientRaiseUntrustedServer |
             ConnectionState::ClientWaitServerApproval => {},
 
@@ -457,7 +503,13 @@ impl OssuaryConnection {
                             };
                             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)?;
+                            match decrypt_to_bytes(session, &nonce, &inner_pkt.subpacket, &mut pt) {
+                                Ok(_) => {},
+                                Err(e) => {
+                                    self.reset_state(None);
+                                    return Err(e);
+                                }
+                            }
                             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];
@@ -548,7 +600,13 @@ impl OssuaryConnection {
                             };
                             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)?;
+                            match decrypt_to_bytes(session, &nonce, &inner_pkt.subpacket, &mut pt) {
+                                Ok(_) => {},
+                                Err(e) => {
+                                    self.reset_state(None);
+                                    return Err(e);
+                                }
+                            }
                             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);
@@ -615,11 +673,18 @@ impl OssuaryConnection {
                 }
             },
 
-            ConnectionState::Failed(ref _e) => {
-                // TODO: fail
-                unimplemented!();
-            },
-
+            ConnectionState::ResetWait => {
+                match pkt.kind() {
+                    PacketType::Reset => {
+                        self.remote_msg_id = 0;
+                        self.state = match self.conn_type {
+                            ConnectionType::Client => ConnectionState::ClientSendHandshake,
+                            _ => ConnectionState::ServerWaitHandshake(std::time::SystemTime::now()),
+                        }
+                    },
+                    _ => {},
+                }
+            }
         };
         Ok(bytes_read)
     }
@@ -629,21 +694,9 @@ impl OssuaryConnection {
     ///
     ///
     pub fn handshake_done(&mut self) -> Result<bool, OssuaryError> {
-        let failed = match self.state {
-            ConnectionState::Failed(_) => true,
-            _ => false,
-        };
-        if failed {
-            let mut new_state = ConnectionState::Failed(OssuaryError::ConnectionFailed);
-            std::mem::swap(&mut self.state, &mut new_state);
-            match new_state {
-                ConnectionState::Failed(e) => return Err(e),
-                _ => return Err(OssuaryError::ConnectionFailed),
-            }
-        }
         match self.state {
             ConnectionState::Encrypted => Ok(true),
-            ConnectionState::Failed(ref _e) => Err(OssuaryError::ConnectionFailed),
+            ConnectionState::Failed(ref e) => Err(e.clone()),
             ConnectionState::ClientRaiseUntrustedServer => {
                 self.state = ConnectionState::ClientWaitServerApproval;
                 let mut key: Vec<u8> = Vec::new();

diff --git a/src/lib.rs b/src/lib.rs
line changes: +33/-3
index 26e37fe..bd098d8
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -125,8 +125,6 @@
 //
 // TODO
 //  - consider all unexpected packet types to be errors
-//  - ensure that a reset on one end always sends a reset to the other
-//  - limit connection retries
 //  - tests should check their received strings
 //  - rustdoc everything
 //
@@ -155,6 +153,9 @@ const PROTOCOL_VERSION: u8 = 1u8;
 // Maximum time to wait (in seconds) for a handshake response
 const MAX_HANDSHAKE_WAIT_TIME: u64 = 3u64;
 
+// Maximum number of times a connection can reset before a permanent failure.
+const MAX_RESET_COUNT: usize = 5;
+
 // Size of the random data to be signed by client
 const CHALLENGE_LEN: usize = 32;
 
@@ -318,7 +319,34 @@ enum ConnectionState {
     /// Connection is established, encrypted, and optionally authenticated.
     Encrypted,
 
-    /// Connection has failed because of the associated error.
+    /// Connection has temporarily failed and will be reset
+    ///
+    /// An error occurred that might be recoverable.  A reset packet will be
+    /// sent to the remote host to inform it to reset as well.
+    ///
+    /// Parameter is true if this side is initiating the reset, false if this
+    /// side is responding to a received reset.
+    Resetting(bool),
+
+    /// Waiting for other side of connection to confirm reset
+    ///
+    /// The local host has sent a reset packet to the remote host, and is
+    /// waiting for the remote host to confirm that it has reset its state.
+    /// This is a temporary holding state to ensure that all packets that
+    /// were on the wire at the time of the error are received before a
+    /// new connection attempt is made.
+    ResetWait,
+
+    /// Connection has failed permanently because of the associated error
+    ///
+    /// The connection is known to have failed on the local side, but the
+    /// failure has not yet been communicated to the remote host.
+    Failing(OssuaryError),
+
+    /// Connection has failed permanently
+    ///
+    /// Both hosts are informed of the failure, and the connection will not be
+    /// recovered.
     Failed(OssuaryError),
 }
 
@@ -399,6 +427,7 @@ pub struct OssuaryConnection {
     read_buf_used: usize,
     write_buf: [u8; PACKET_BUF_SIZE],
     write_buf_used: usize,
+    reset_count: usize,
 }
 impl Default for OssuaryConnection {
     fn default() -> Self {
@@ -416,6 +445,7 @@ impl Default for OssuaryConnection {
             read_buf_used: 0,
             write_buf: [0u8; PACKET_BUF_SIZE],
             write_buf_used: 0,
+            reset_count: 0,
         }
     }
 }

diff --git a/tests/basic.rs b/tests/basic.rs
line changes: +1/-1
index ac26f63..9b87ca7
--- a/tests/basic.rs
+++ b/tests/basic.rs
@@ -26,7 +26,7 @@ where T: std::io::Read + std::io::Write {
                 match conn.recv_handshake(&mut stream) {
                     Ok(_) => break,
                     Err(OssuaryError::WouldBlock(_)) => {},
-                    _ => panic!("Handshake failed."),
+                    Err(e) => panic!("Handshake failed: {:?}", e),
                 }
             }
         }

diff --git a/tests/corruption.rs b/tests/corruption.rs
line changes: +160/-0
index 0000000..8d9dffe
--- /dev/null
+++ b/tests/corruption.rs
@@ -0,0 +1,160 @@
+use ossuary::{OssuaryConnection, ConnectionType};
+use ossuary::OssuaryError;
+
+#[test]
+fn corruption() {
+    #[derive(Debug)]
+    enum Corruption {
+        ClientKey,
+        ClientNonce,
+        ClientChal,
+        ClientAuth,
+        ClientInvalidPkt,
+        ServerKey,
+        ServerNonce,
+        ServerAuth,
+        ServerInvalidPkt,
+    };
+
+    // Corruption test tuple format:
+    // (test type, loop iteration, byte offset, byte value, expected recv error, permanent)
+    let corruptions = [
+        // loop iteration 0: Client -> Server
+        // 8 bytes network header, 8 bytes packet header, 32 bytes key, 12 bytes nonce, 32 bytes challenge
+        (Corruption::ClientInvalidPkt, 0, 0, 0xaa, OssuaryError::InvalidPacket("Oversized packet".into()), true),
+        (Corruption::ClientInvalidPkt, 0, 2, 0xaa, OssuaryError::InvalidPacket("Message ID does not match".into()), true),
+        (Corruption::ClientInvalidPkt, 0, 3, 0xaa, OssuaryError::InvalidPacket("Message ID does not match".into()), true),
+        (Corruption::ClientInvalidPkt, 0, 4, 0xaa, OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()), true),
+        (Corruption::ClientKey, 0, 18, 0xaa, OssuaryError::DecryptionFailed, false),
+        (Corruption::ClientNonce, 0, 50, 0xaa, OssuaryError::DecryptionFailed, false),
+        (Corruption::ClientChal, 0, 64, 0xaa, OssuaryError::InvalidSignature, false),
+
+        // loop iteration 3: Server -> Client
+        // 8 bytes net header, 8 bytes packet header, 32 bytes key, 12 bytes nonce, ~150 bytes encrypted auth
+        (Corruption::ServerInvalidPkt, 3, 0, 0xaa, OssuaryError::InvalidPacket("Oversized packet".into()), true),
+        (Corruption::ServerInvalidPkt, 3, 2, 0xaa, OssuaryError::InvalidPacket("Message ID does not match".into()), true),
+        (Corruption::ServerInvalidPkt, 3, 4, 0xaa, OssuaryError::InvalidPacket("Received unexpected handshake packet.".into()), true),
+        (Corruption::ServerKey, 3, 18, 0xaa, OssuaryError::DecryptionFailed, false),
+        (Corruption::ServerNonce, 3, 50, 0xaa, OssuaryError::DecryptionFailed, false),
+        (Corruption::ServerAuth, 3, 64, 0xaa, OssuaryError::DecryptionFailed, false),
+        (Corruption::ServerAuth, 3, 84, 0xaa, OssuaryError::DecryptionFailed, false),
+
+        // loop iteration 2: Client -> Server
+        // 8 bytes net header, 8 bytes packet header, 4 byte encryption packet, ~120 bytes encrypted auth
+        (Corruption::ClientInvalidPkt, 6, 18, 0xaa, OssuaryError::InvalidPacket("Invalid packet length".into()), false),
+        (Corruption::ClientAuth, 6, 24, 0xaa, OssuaryError::DecryptionFailed, false),
+        (Corruption::ClientAuth, 6, 96, 0xaa, OssuaryError::DecryptionFailed, false),
+    ];
+
+    #[derive(Debug)]
+    enum LoopConn {
+        LoopClient,
+        LoopServer,
+    };
+    for corruption in &corruptions {
+        println!("Corruption test: {:?}", corruption.0);
+        let server_secret_key = &[
+            0x50, 0x29, 0x04, 0x97, 0x62, 0xbd, 0xa6, 0x07,
+            0x71, 0xca, 0x29, 0x14, 0xe3, 0x83, 0x19, 0x0e,
+            0xa0, 0x9e, 0xd4, 0xb7, 0x1a, 0xf9, 0xc9, 0x59,
+            0x3e, 0xa3, 0x1c, 0x85, 0x0f, 0xc4, 0xfa, 0xa2,
+        ];
+        let server_public_key = &[
+            0x20, 0x88, 0x55, 0x8e, 0xbd, 0x9b, 0x46, 0x1d,
+            0xd0, 0x9d, 0xf0, 0x00, 0xda, 0xf4, 0x0f, 0x87,
+            0xf7, 0x38, 0x40, 0xc5, 0x54, 0x18, 0x57, 0x60,
+            0x74, 0x39, 0x3b, 0xb9, 0x70, 0xe1, 0x46, 0x98,
+        ];
+        let pubkeys: Vec<&[u8]> = vec![server_public_key];
+        let mut server_conn = OssuaryConnection::new(ConnectionType::UnauthenticatedServer, Some(server_secret_key)).unwrap();
+        let mut client_conn = OssuaryConnection::new(ConnectionType::Client, None).unwrap();
+        let _ = client_conn.add_authorized_keys(pubkeys).unwrap();
+
+        let mut loop_conn = LoopConn::LoopClient;
+        let mut client_buf: Vec<u8> = vec!();
+        let mut server_buf: Vec<u8> = vec!();
+        let mut loop_count = 0;
+
+        loop {
+            let mut done = 0;
+            let (send_conn, recv_conn, mut send_buf, recv_buf) = match loop_conn {
+                LoopConn::LoopClient => (&mut client_conn, &mut server_conn, &mut client_buf, &mut server_buf),
+                _ => (&mut server_conn, &mut client_conn, &mut server_buf, &mut client_buf),
+            };
+            match send_conn.handshake_done() {
+                Ok(true) => done += 1,
+                Ok(false) => {},
+                Err(OssuaryError::ConnectionFailed) => {
+                    match corruption.5 {
+                        true => break,
+                        false => panic!("Unexpected connection failure."),
+                    }
+                }
+                Err(e) => panic!("Handshake failed with error: {:?}", e),
+            }
+            match recv_conn.handshake_done() {
+                Ok(true) => done += 1,
+                Ok(false) => {},
+                Err(OssuaryError::ConnectionFailed) => {
+                    match corruption.5 {
+                        true => break,
+                        false => panic!("Unexpected connection failure."),
+                    }
+                }
+                Err(e) => panic!("Handshake failed with error: {:?}", e),
+            }
+            if done == 2 {
+                break;
+            }
+            send_conn.send_handshake(&mut send_buf).unwrap();
+            if send_buf.len() > 0 {
+                //println!("{:?}({}) {:?}", loop_conn, loop_count, send_buf);
+            }
+            if loop_count == corruption.1 {
+                send_buf[corruption.2] = corruption.3;
+            }
+            match send_conn.recv_handshake(&mut recv_buf.as_slice()) {
+                Ok(b) => { recv_buf.drain(0..b); },
+                Err(OssuaryError::WouldBlock(b)) => { recv_buf.drain(0..b); },
+                Err(e) => match e {
+                    ref e if e == &corruption.4 => {}, // expected error
+                    OssuaryError::ConnectionFailed => {
+                        match corruption.5 {
+                            true => break,
+                            false => panic!("Unexpected connection failure."),
+                        }
+                    },
+                    OssuaryError::ConnectionReset(b) => { recv_buf.drain(0..b); },
+                    _ => panic!("Handshake failed: {:?}", e),
+                },
+            }
+            match send_conn.handshake_done() {
+                Ok(true) => {
+                    let mut plaintext = Vec::<u8>::new();
+                    match send_conn.recv_data(&mut recv_buf.as_slice(), &mut plaintext) {
+                        Ok((b,_)) => { recv_buf.drain(0..b); },
+                        Err(OssuaryError::WouldBlock(b)) => { recv_buf.drain(0..b); },
+                        Err(e) => match e {
+                            ref e if e == &corruption.4 => {}, // expected error
+                            OssuaryError::ConnectionFailed => {
+                                match corruption.5 {
+                                    true => break,
+                                    false => panic!("Unexpected connection failure."),
+                                }
+                            },
+                            OssuaryError::ConnectionReset(b) => { recv_buf.drain(0..b); },
+                            _ => panic!("Handshake failed: {:?}", e),
+                        },
+                    }
+                },
+                Err(e) => panic!("ERROR: {:?}", e),
+                _ => {},
+            }
+            loop_conn = match loop_conn {
+                LoopConn::LoopClient => LoopConn::LoopServer,
+                _ => LoopConn::LoopClient,
+            };
+            loop_count += 1;
+        }
+    }
+}