From 5475c7913569fa3e7fd609e7a6854107d80d6fee Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Thu, 09 Sep 2021 22:09:02 +0100 Subject: [PATCH] Fix hanging on cargo update Fixes #6, makes a start on #5 git always expects us to close the connection, they won't do it themselves even if they've got everything they need --- chartered-git/src/main.rs | 31 ++++++++++++++++++++++++------- chartered-git/src/git/codec.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++----------------------- 2 files changed, 108 insertions(+), 59 deletions(-) diff --git a/chartered-git/src/main.rs b/chartered-git/src/main.rs index 2dec1b5..1f3419b 100644 --- a/chartered-git/src/main.rs +++ a/chartered-git/src/main.rs @@ -97,17 +97,14 @@ type FutureBool = futures::future::Ready>; fn finished_auth(self, auth: Auth) -> Self::FutureAuth { - eprintln!("finished auth"); Box::pin(futures::future::ready(Ok((self, auth)))) } fn finished_bool(self, b: bool, s: Session) -> Self::FutureBool { - eprintln!("finished bool"); futures::future::ready(Ok((self, s, b))) } fn finished(self, s: Session) -> Self::FutureUnit { - eprintln!("finished"); Box::pin(futures::future::ready(Ok((self, s)))) } @@ -207,16 +204,30 @@ let mut done = false; while let Some(frame) = self.codec.decode(&mut self.input_bytes)? { - eprintln!("data: {:x?}", frame); + eprintln!("{:#?}", frame); + + // if the client flushed without giving us a command, we're expected to close + // the connection or else the client will just hang + if frame.command.is_empty() { + session.exit_status_request(channel, 0); + session.eof(channel); + session.close(channel); + return Ok((self, session)); + } - if frame.as_ref() == "command=ls-refs".as_bytes() { + if frame.command.as_ref() == "command=ls-refs".as_bytes() { ls_refs = true; - } else if frame.as_ref() == "command=fetch".as_bytes() { - fetch = true; - } else if frame.as_ref() == "done".as_bytes() { - fetch = false; - done = true; + } else if frame.command.as_ref() == "command=fetch".as_bytes() { + if frame.metadata.iter().any(|v| v.as_ref() == b"done") { + done = true; + } else { + fetch = true; + } } + } + + if !ls_refs && !fetch && !done { + return Ok((self, session)); } // echo -ne "0012command=fetch\n0001000ethin-pack\n0010include-tag\n000eofs-delta\n0032want d24d8020163b5fee57c9babfd0c595b8c90ba253\n0009done\n" diff --git a/chartered-git/src/git/codec.rs b/chartered-git/src/git/codec.rs index 96ce563..6cba5e0 100644 --- a/chartered-git/src/git/codec.rs +++ a/chartered-git/src/git/codec.rs @@ -18,63 +18,79 @@ } } +#[derive(Debug, Default, PartialEq, Eq)] +pub struct GitCommand { + pub command: Bytes, + pub metadata: Vec, +} + #[derive(Default)] -pub struct GitCodec; +pub struct GitCodec { + command: GitCommand, +} impl codec::Decoder for GitCodec { - type Item = Bytes; + type Item = GitCommand; type Error = anyhow::Error; fn decode(&mut self, src: &mut bytes::BytesMut) -> Result, Self::Error> { - if src.len() < 4 { - return Ok(None); - } - - let mut length_bytes = [0_u8; 4]; - length_bytes.copy_from_slice(&src[..4]); - let length = u16::from_str_radix(std::str::from_utf8(&length_bytes)?, 16)? as usize; - - if length == 0 // flush-pkt - || length == 1 // delim-pkt - || length == 2 - // response-end-pkt - { - eprintln!("pkt: {}", length); - src.advance(4); - return self.decode(src); - } - - if !(4..=65520).contains(&length) { - return Err( - std::io::Error::new(std::io::ErrorKind::InvalidData, "protocol abuse").into(), - ); - } - - if src.len() < length { - src.reserve(length - src.len()); - return Ok(None); - } - - let mut bytes = src.split_to(length); - bytes.advance(4); - - if bytes.ends_with(b"\n") { - bytes.truncate(bytes.len() - 1); + loop { + if src.len() < 4 { + return Ok(None); + } + + let mut length_bytes = [0_u8; 4]; + length_bytes.copy_from_slice(&src[..4]); + let length = u16::from_str_radix(std::str::from_utf8(&length_bytes)?, 16)? as usize; + + if length == 0 { + // flush + src.advance(4); + return Ok(Some(std::mem::take(&mut self.command))); + } else if length == 1 || length == 2 { + src.advance(4); + eprintln!("magic packet = {}", length); + continue; + } else if !(4..=65520).contains(&length) { + eprintln!("protocol abuse"); + return Err( + std::io::Error::new(std::io::ErrorKind::InvalidData, "protocol abuse").into(), + ); + } + + // not enough bytes in the buffer yet, ask for more + if src.len() < length { + src.reserve(length - src.len()); + return Ok(None); + } + + // length is inclusive of the 4 bytes that makes up itself + let mut data = src.split_to(length).freeze(); + data.advance(4); + + // strip newlines for conformity + if data.ends_with(b"\n") { + data.truncate(data.len() - 1); + } + + if self.command.command.is_empty() { + self.command.command = data; + } else { + self.command.metadata.push(data); + } } - - Ok(Some(bytes.freeze())) } } #[cfg(test)] mod test { - use bytes::BytesMut; + use bytes::{Bytes, BytesMut}; use std::fmt::Write; use tokio_util::codec::Decoder; #[test] fn decode() { - let mut codec = super::GitCodec; + let mut codec = super::GitCodec::default(); let mut bytes = BytesMut::new(); @@ -83,20 +99,42 @@ assert_eq!(res, None); bytes.write_char('\n').unwrap(); - bytes.write_str("0002").unwrap(); - bytes.write_str("0004").unwrap(); - bytes.write_str("0005a").unwrap(); - let res = codec.decode(&mut bytes).unwrap(); - assert_eq!(res.as_deref(), Some("agent=git/2.32.0".as_bytes())); + assert_eq!(res, None); + bytes.write_str("0000").unwrap(); let res = codec.decode(&mut bytes).unwrap(); - assert_eq!(res.as_deref(), Some("".as_bytes())); - + assert_eq!( + res, + Some(super::GitCommand { + command: Bytes::from_static(b"agent=git/2.32.0\n"), + metadata: vec![], + }) + ); + + bytes.write_str("0000").unwrap(); let res = codec.decode(&mut bytes).unwrap(); - assert_eq!(res.as_deref(), Some("a".as_bytes())); + assert_eq!( + res, + Some(super::GitCommand { + command: Bytes::new(), + metadata: vec![], + }) + ); + + bytes.write_str("0002").unwrap(); + bytes.write_str("0005a").unwrap(); + bytes.write_str("0001").unwrap(); + bytes.write_str("0005b").unwrap(); + bytes.write_str("0000").unwrap(); let res = codec.decode(&mut bytes).unwrap(); - assert_eq!(res.as_deref(), None); + assert_eq!( + res, + Some(super::GitCommand { + command: Bytes::from_static(b"a"), + metadata: vec![Bytes::from_static(b"b")], + }) + ); } } -- rgit 0.1.3