🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-09-09 22:09:02.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-09-09 22:09:02.0 +01:00:00
commit
5475c7913569fa3e7fd609e7a6854107d80d6fee [patch]
tree
346ed47f09847790027fd6ccc059b82d841fc830
parent
b60dd4ea78d2ba2eac2ffd8492392eaf29056bf6
download
5475c7913569fa3e7fd609e7a6854107d80d6fee.tar.gz

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(-)

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<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 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<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 // 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")],
            })
        );
    }
}