diff --git a/Cargo.lock b/Cargo.lock
index 9fea71108121bde744bc5a0aadec57de20031ff8..eabcf14a84ca727623983621240ff11966700f7f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -352,7 +352,6 @@ dependencies = [
  "nix",
  "pretty-hex",
  "rpassword",
- "signal-hook-tokio",
  "simplelog",
  "snafu",
  "tokio",
@@ -1225,16 +1224,6 @@ dependencies = [
  "digest 0.10.3",
 ]
 
-[[package]]
-name = "signal-hook"
-version = "0.3.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a253b5e89e2698464fc26b545c9edceb338e18a89effeeecfea192c3025be29d"
-dependencies = [
- "libc",
- "signal-hook-registry",
-]
-
 [[package]]
 name = "signal-hook-registry"
 version = "1.4.0"
@@ -1244,17 +1233,6 @@ dependencies = [
  "libc",
 ]
 
-[[package]]
-name = "signal-hook-tokio"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "213241f76fb1e37e27de3b6aa1b068a2c333233b59cca6634f634b80a27ecf1e"
-dependencies = [
- "libc",
- "signal-hook",
- "tokio",
-]
-
 [[package]]
 name = "signature"
 version = "1.5.0"
diff --git a/async/Cargo.toml b/async/Cargo.toml
index cbe0cdd7221c3ae692b9a758396af89f8a1a9608..eb07034dfd65766a7a2797f7330c55394f936430 100644
--- a/async/Cargo.toml
+++ b/async/Cargo.toml
@@ -29,8 +29,6 @@ moro = "0.4"
 libc = "0.2"
 nix = "0.24"
 
-signal-hook-tokio = "0.3"
-
 heapless = "0.7.10"
 
 # TODO
diff --git a/async/src/async_channel.rs b/async/src/async_channel.rs
index 952ec94f799f56be3bebb0fa2837b3b89373c3fc..0adc4c773bb59817153980dc885d21337cd27da0 100644
--- a/async/src/async_channel.rs
+++ b/async/src/async_channel.rs
@@ -17,6 +17,30 @@ use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
 use crate::*;
 use async_door::{Inner, poll_lock};
 
+pub struct Channel<'a> {
+    chan: u32,
+    door: AsyncDoor<'a>,
+}
+
+impl<'a> Channel<'a> {
+    /// Should be called by a SSH client when the local terminal changes size
+    /// (`SIGWINCH` is received). Only applicable to client session
+    /// channels with a pty.
+    pub async fn term_window_change(&self) {
+        let wc = match pty::win_size() {
+            Ok(wc) => wc,
+            Err(e) => {
+                warn!("Failed getting window size: {e}");
+                return;
+            }
+        };
+
+        // TODO: also need to wait for spare output buffer
+        self.door.inner.lock().await
+        .runner.term_window_change(self.chan, wc);
+    }
+}
+
 pub struct ChanInOut<'a> {
     chan: u32,
     door: AsyncDoor<'a>,
diff --git a/async/src/pty.rs b/async/src/pty.rs
index 9bb12b275f31df79793c5f684fc0325a892df6ee..ec2f413a9aca2567d6eb6f6f7f440c140d6653e5 100644
--- a/async/src/pty.rs
+++ b/async/src/pty.rs
@@ -8,29 +8,43 @@ use libc::{ioctl, winsize, termios, tcgetattr, tcsetattr };
 use door_sshproto as door;
 use door::{Behaviour, AsyncCliBehaviour, Runner, Result, Pty};
 use door::config::*;
+use door::packets::WinChange;
 
+/// Returns the size of the current terminal
+pub fn win_size() -> Result<WinChange, IoError> {
+    let mut ws = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
+    let r = unsafe { ioctl(libc::STDIN_FILENO, libc::TIOCGWINSZ, &mut ws) };
+    if r != 0 {
+        return Err(IoError::last_os_error())
+    }
 
+    Ok(WinChange {
+        rows: ws.ws_row as u32,
+        cols: ws.ws_col as u32,
+        width: ws.ws_xpixel as u32,
+        height: ws.ws_ypixel as u32,
+    })
+}
+
+/// Returns a `Pty` describing the current terminal.
 pub fn current_pty() -> Result<Pty, IoError> {
     let mut term = heapless::String::<MAX_TERM>::new();
     let t = std::env::var("TERM").unwrap_or(DEFAULT_TERM.into());
     // XXX error
     term.push_str(&t).expect("TERM fits buffer");
 
-    let mut ws = winsize { ws_row: 0, ws_col: 0, ws_xpixel: 0, ws_ypixel: 0 };
-    let r = unsafe { ioctl(libc::STDIN_FILENO, libc::TIOCGWINSZ, &mut ws) };
-    if r != 0 {
-        return Err(IoError::last_os_error())
-    }
+    let wc = win_size()?;
 
-    info!("rows {} cols {}", ws.ws_row, ws.ws_col);
+    // TODO modes
+    let modes = heapless::Vec::new();
 
     Ok(Pty {
         term,
-        rows: ws.ws_row as u32,
-        cols: ws.ws_col as u32,
-        width: ws.ws_xpixel as u32,
-        height: ws.ws_ypixel as u32,
-        modes: heapless::Vec::new(),
+        rows: wc.rows,
+        cols: wc.cols,
+        width: wc.width,
+        height: wc.height,
+        modes,
     })
 
 }
diff --git a/sshproto/src/conn.rs b/sshproto/src/conn.rs
index 69bcaceed97f66906ca94a61bde00e3650bcba4e..cf9fa13b6104d81fa3c6607bff075a5ff720234b 100644
--- a/sshproto/src/conn.rs
+++ b/sshproto/src/conn.rs
@@ -178,11 +178,22 @@ impl<'a> Conn<'a> {
     /// We queue response packets that can be sent (written into the same buffer)
     /// after `handle_payload()` runs.
     pub(crate) async fn handle_payload<'p>(
-        &mut self, payload: &'p [u8], keys: &mut KeyState, b: &mut Behaviour<'_>,
+        &mut self, payload: &'p [u8], seq: u32,
+        keys: &mut KeyState, b: &mut Behaviour<'_>,
     ) -> Result<Dispatched<'_>, Error> {
-        let p = sshwire::packet_from_bytes(payload, &self.parse_ctx)?;
-        let r = self.dispatch_packet(p, keys, b).await;
-        r
+        let r = sshwire::packet_from_bytes(payload, &self.parse_ctx);
+        match r {
+            Ok(p) => self.dispatch_packet(p, keys, b).await,
+            Err(Error::UnknownPacket { number }) => {
+                trace!("Unimplemented packet type {number}");
+                let p: Packet = packets::Unimplemented { seq }.into();
+                let mut resp = RespPackets::new();
+                // unwrap is OK, single packet has space
+                resp.push(p.into()).unwrap();
+                Ok(Dispatched { resp, event: None })
+            }
+            Err(e) => return Err(e),
+        }
     }
 
     async fn dispatch_packet<'p>(
diff --git a/sshproto/src/encrypt.rs b/sshproto/src/encrypt.rs
index 5806a221fa3601f1b6a2d9cdb25ebca4f6471b8a..0ef9c062695e74e214b05f5bdededfc34f13d3ca 100644
--- a/sshproto/src/encrypt.rs
+++ b/sshproto/src/encrypt.rs
@@ -62,6 +62,10 @@ impl KeyState {
         self.keys = keys
     }
 
+    pub fn recv_seq(&self) -> u32 {
+        self.seq_decrypt.0
+    }
+
     /// Decrypts the first block in the buffer, returning the length.
     pub fn decrypt_first_block(&mut self, buf: &mut [u8]) -> Result<u32, Error> {
         self.keys.decrypt_first_block(buf, self.seq_decrypt.0)
diff --git a/sshproto/src/error.rs b/sshproto/src/error.rs
index 51abae2bc2e67ed34d61abaa9a08e1468110a355..421761033f20aa3176705553d2d6daa73ae6f166 100644
--- a/sshproto/src/error.rs
+++ b/sshproto/src/error.rs
@@ -26,19 +26,20 @@ pub enum Error {
     /// Not a UTF8 string
     BadString,
 
-    /// Not a valid SSH ascii string
+    /// Not a valid SSH ASCII string
     BadName,
 
-    /// Decryption failure or integrity mismatch
+    /// Key exchange incorrect
+    BadKex,
+
+    /// Decryption failed
     BadDecrypt,
 
     /// Signature is incorrect
     BadSig,
 
-    /// Key exchange incorrect
-    BadKex,
-
-    // Signature doesn't match key type
+    // TODO: should this just be badsig? as long as we catch UnknownMethod first
+    /// Signature doesn't match key type
     SigMismatch,
 
     /// Error in received SSH protocol
@@ -96,6 +97,7 @@ pub enum Error {
     /// Program bug
     Bug,
 
+    // TODO remove this
     OtherBug { location: snafu::Location },
 }
 
diff --git a/sshproto/src/kex.rs b/sshproto/src/kex.rs
index da4146b3c1005fb485b5524107429fbc5dce4851..cee9f13e83ab6d8ea0fe4a1cd96c33ab12f0e6f6 100644
--- a/sshproto/src/kex.rs
+++ b/sshproto/src/kex.rs
@@ -128,10 +128,14 @@ impl KexHash {
     }
 
     /// Fill everything except K.
-    /// q_c and q_s need to be padded as mpint (extra 0x00 if high bit set)
-    /// for ecdsa and DH modes, but not for curve25519.
     fn prefinish(&mut self, host_key: &PubKey, q_c: &[u8], q_s: &[u8]) -> Result<()> {
         hash_ser_length(&mut self.hash_ctx, host_key)?;
+
+        // TODO: q_c and q_s need to be padded as mpint (extra 0x00 if high bit set)
+        // for ecdsa and DH modes, but not for curve25519.
+        // Hack test for ed25519 algo
+        assert_eq!(q_c.len(), 32);
+
         self.hash_slice(q_c);
         self.hash_slice(q_s);
         Ok(())
@@ -493,11 +497,10 @@ impl<'a> KexOutput {
     // server only
     pub fn make_kexdhreply(&'a self) -> Result<Packet<'a>> {
         let q_s = BinString(self.pubkey.as_ref().trap()?);
-        // TODO
+        // TODO real signature
         let k_s = Blob(PubKey::Ed25519(packets::Ed25519PubKey{ key: BinString(&[]) }));
         let sig = Blob(Signature::Ed25519(packets::Ed25519Sig{ sig: BinString(&[]) }));
         Ok(packets::KexDHReply { k_s, q_s, sig }.into())
-        // then sign it.
     }
 }
 
@@ -534,7 +537,7 @@ impl KexCurve25519 {
         algos: &mut Algos, theirs: &[u8], kex_hash: KexHash,
         sess_id: &Option<SessId>,
     ) -> Result<KexOutput> {
-        #[warn(irrefutable_let_patterns)] // until we have other algos
+        #[allow(irrefutable_let_patterns)] // until we have other algos
         let kex = if let SharedSecret::KexCurve25519(k) = &mut algos.kex {
             k
         } else {
diff --git a/sshproto/src/runner.rs b/sshproto/src/runner.rs
index 50a2993d11c4b4124e52d7a4f4107069ba940f39..1d952fc3ab330a5fd5781f29894e571a21c11f0c 100644
--- a/sshproto/src/runner.rs
+++ b/sshproto/src/runner.rs
@@ -74,7 +74,7 @@ impl<'a> Runner<'a> {
     /// event to the application.
     /// [`done_payload()`] must be called after any `Ok` result.
     pub async fn progress<'f>(&'f mut self) -> Result<Option<Event<'f>>, Error> {
-        let em = if let Some(payload) = self.traffic.payload() {
+        let em = if let Some((payload, seq)) = self.traffic.payload() {
             // Lifetimes here are a bit subtle.
             // `payload` has self.traffic lifetime, used until `handle_payload`
             // completes.
@@ -82,7 +82,7 @@ impl<'a> Runner<'a> {
             // by the send_packet().
             // After that progress() can perform more send_packet() itself.
 
-            let d = self.conn.handle_payload(payload, &mut self.keys, &mut self.behaviour).await?;
+            let d = self.conn.handle_payload(payload, seq, &mut self.keys, &mut self.behaviour).await?;
             self.traffic.handled_payload()?;
 
             if !d.resp.is_empty() || d.event.is_none() {
@@ -250,6 +250,11 @@ impl<'a> Runner<'a> {
         self.conn.channels.send_allowed(chan).map(|s| s.min(buf_space))
     }
 
+    pub fn term_window_change(&self, chan: u32, wc: packets::WinChange) -> Result<()> {
+        todo!();
+        // self.conn.channels.term_window_change(chan, wc)
+    }
+
     // pub fn chan_pending(&self) -> bool {
     //     self.conn.chan_pending()
     // }
diff --git a/sshproto/src/traffic.rs b/sshproto/src/traffic.rs
index 1cab016fafc1cb28777e62c59e6fb8ab060c21c9..73e05808fa6bba780f47e7eaa67a825fdc9f79ad 100644
--- a/sshproto/src/traffic.rs
+++ b/sshproto/src/traffic.rs
@@ -41,7 +41,7 @@ enum TrafState {
     /// Whole encrypted packet has been read
     ReadComplete { len: usize },
     /// Decrypted complete input payload
-    InPayload { len: usize },
+    InPayload { len: usize, seq: u32 },
     /// Decrypted complete input payload. It has been dispatched by handle_payload(),
     /// remains "borrowed" for use by a progress() Event.
     BorrowPayload { len: usize },
@@ -164,27 +164,21 @@ impl<'a> Traffic<'a> {
         Ok(inlen)
     }
 
-    /// Returns a reference to the decrypted payload buffer if ready.
-    /// For a given payload should be called once initially to pass to handle_payload(),
-    /// with borrow=false. Subsequent calls will only return the payload if borrow=false,
-    /// used for borrowing the payload for Event.
-    // TODO: get rid of the bool and make two functions. need to figure naming.
-    // "payload_reborrow()"?
-    pub(crate) fn payload(&mut self) -> Option<&[u8]> {
-        let p = match self.state {
-            | TrafState::InPayload { len, .. }
+    /// Returns a reference to the decrypted payload buffer if ready,
+    /// and the `seq` of that packet.
+    pub(crate) fn payload(&mut self) -> Option<(&[u8], u32)> {
+        match self.state {
+            | TrafState::InPayload { len, seq }
             => {
                 let payload = &self.buf[SSH_PAYLOAD_START..SSH_PAYLOAD_START + len];
-                Some(payload)
+                Some((payload, seq))
             }
             _ => None,
-        };
-        trace!("traf 2 {:?}", self.state);
-        p
+        }
     }
 
     pub(crate) fn payload_reborrow(&mut self) -> Option<&[u8]> {
-        let p = match self.state {
+        match self.state {
             | TrafState::InPayload { len, .. }
             | TrafState::BorrowPayload { len, .. }
             => {
@@ -192,16 +186,14 @@ impl<'a> Traffic<'a> {
                 Some(payload)
             }
             _ => None,
-        };
-        trace!("traf 2 {:?}", self.state);
-        p
+        }
     }
 
     /// Called when `payload()` has been handled once, can still be
     /// `payload_reborrow()`ed later.
     pub(crate) fn handled_payload(&mut self) -> Result<(), Error> {
         match self.state {
-            | TrafState::InPayload { len }
+            | TrafState::InPayload { len, .. }
             | TrafState::BorrowPayload { len }
             => {
                 self.state = TrafState::BorrowPayload { len };
@@ -348,8 +340,9 @@ impl<'a> Traffic<'a> {
 
         if let TrafState::ReadComplete { len } = self.state {
             let w = &mut self.buf[0..len];
+            let seq = keys.recv_seq();
             let payload_len = keys.decrypt(w)?;
-            self.state = TrafState::InPayload { len: payload_len }
+            self.state = TrafState::InPayload { len: payload_len, seq }
         }
 
         Ok(buf.len() - r.len())