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
Diff
chartered-git/src/main.rs | 31 ++++++++++++++++++++++++-------
chartered-git/src/git/codec.rs | 136 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------------------
2 files changed, 108 insertions(+), 59 deletions(-)
@@ -97,17 +97,14 @@
type FutureBool = futures::future::Ready<Result<(Self, Session, bool), anyhow::Error>>;
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 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));
}
@@ -18,63 +18,79 @@
}
}
#[derive(Debug, Default, PartialEq, Eq)]
pub struct GitCommand {
pub command: Bytes,
pub metadata: Vec<Bytes>,
}
#[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<Option<Self::Item>, 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
|| length == 1
|| length == 2
{
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 {
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(),
);
}
if src.len() < length {
src.reserve(length - src.len());
return Ok(None);
}
let mut data = src.split_to(length).freeze();
data.advance(4);
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")],
})
);
}
}