🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-08-28 3:07:24.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-08-28 13:58:50.0 +01:00:00
commit
6e682b824ba1c6012af93ce2813abeb93aa47806 [patch]
tree
b994f58c1b8eac6685dd1d78455e7db14a510b86
parent
f40e31b0376638a7e4471b248cf37c3bc77d677b
download
6e682b824ba1c6012af93ce2813abeb93aa47806.tar.gz

helpful things



Diff

 src/main.rs         | 35 +++++++++++++++++++++++++++++++++--
 src/git/codec.rs    | 16 ++++++++++++++++
 src/git/mod.rs      | 25 +++++++++++++++++++++++--
 src/git/packfile.rs | 26 ++++++++++++++++++++++++++
 4 files changed, 87 insertions(+), 15 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index bb8bf81..4acc4b3 100644
--- a/src/main.rs
+++ a/src/main.rs
@@ -85,13 +85,13 @@
            session.close(channel);
        } else {
            // TODO: check GIT_PROTOCOL=version=2 set
            session.data(channel, PktLine(b"version 2\n").into());
            session.data(channel, PktLine(b"agent=chartered/0.1.0\n").into());
            session.data(channel, PktLine(b"ls-refs=unborn\n").into());
            session.data(channel, PktLine(b"fetch=shallow wait-for-done\n").into());
            session.data(channel, PktLine(b"server-option\n").into());
            session.data(channel, PktLine(b"object-info\n").into());
            session.data(channel, CryptoVec::from_slice(git::END_OF_MESSAGE));
            session.data(channel, PktLine::Data(b"version 2\n").into());
            session.data(channel, PktLine::Data(b"agent=chartered/0.1.0\n").into());
            session.data(channel, PktLine::Data(b"ls-refs=unborn\n").into());
            session.data(channel, PktLine::Data(b"fetch=shallow wait-for-done\n").into());
            session.data(channel, PktLine::Data(b"server-option\n").into());
            session.data(channel, PktLine::Data(b"object-info\n").into());
            session.data(channel, PktLine::Flush.into());
        }

        futures::future::ready(Ok((self, session)))
@@ -112,11 +112,30 @@
        self.finished_auth(server::Auth::Accept)
    }

    fn data(mut self, _channel: ChannelId, data: &[u8], session: Session) -> Self::FutureUnit {
    fn data(mut self, channel: ChannelId, data: &[u8], mut session: Session) -> Self::FutureUnit {
        self.input_bytes.extend_from_slice(data);

        let mut ls_refs = false;

        while let Some(frame) = self.codec.decode(&mut self.input_bytes).unwrap() {
            eprintln!("data: {:x?}", frame);

            if frame.as_ref() == "command=ls-refs".as_bytes() {
                ls_refs = true;
            }
        }

        // echo -ne "0014command=ls-refs\n0014agent=git/2.321\n00010008peel000bsymrefs000aunborn0014ref-prefix HEAD\n0000"
        // GIT_PROTOCOL=version=2 ssh -o SendEnv=GIT_PROTOCOL git@github.com git-upload-pack '/w4/chartered.git'
        // ''.join([('{:04x}'.format(len(v) + 5)), v, "\n"])
        // echo -ne "0012command=fetch\n0001000ethin-pack\n0010no-progress\n0010include-tag\n000eofs-delta\n0032want 1a1b25ae7c87a0e87b7a9aa478a6bc4331c6b954\n0009done\n"
        // sends a 000dpackfile back
        // https://shafiul.github.io/gitbook/7_the_packfile.html
        if ls_refs {
            session.data(channel, PktLine::Data(b"1a1b25ae7c87a0e87b7a9aa478a6bc4331c6b954 HEAD symref-target:refs/heads/master\n").into());
            session.data(channel, PktLine::Flush.into());

            // next command will be a fetch like above
        }

        futures::future::ready(Ok((self, session)))
diff --git a/src/git/codec.rs b/src/git/codec.rs
index 42e27b6..5fe3752 100644
--- a/src/git/codec.rs
+++ a/src/git/codec.rs
@@ -21,11 +21,12 @@
            || length == 1 // delim-pkt
            || length == 2 // response-end-pkt
        {
            eprintln!("pkt: {}", length);
            src.advance(4);
            return self.decode(src);
        }

        if length > 65520 || length <= 4 {
        if length > 65520 || length < 4 {
            return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, "protocol abuse").into());
        }

@@ -62,7 +63,20 @@
        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()));

        let res = codec.decode(&mut bytes).unwrap();
        assert_eq!(res.as_deref(), Some("".as_bytes()));

        let res = codec.decode(&mut bytes).unwrap();
        assert_eq!(res.as_deref(), Some("a".as_bytes()));

        let res = codec.decode(&mut bytes).unwrap();
        assert_eq!(res.as_deref(), None);
    }
}
diff --git a/src/git/mod.rs b/src/git/mod.rs
index 7e366b3..a312d7a 100644
--- a/src/git/mod.rs
+++ a/src/git/mod.rs
@@ -1,17 +1,30 @@
pub mod codec;
pub mod packfile;

use thrussh::CryptoVec;

pub const END_OF_MESSAGE: &'static [u8] = b"0000";

pub struct PktLine<'a>(pub &'a [u8]);
pub enum PktLine<'a> {
    Data(&'a [u8]),
    Flush,
    Delimiter,
    ResponseEnd,
}

impl PktLine<'_> {
    // todo: encode to connection's `bytes::BytesMut`
    pub fn encode(&self) -> Vec<u8> {
        let mut v = Vec::new();
        v.extend_from_slice(format!("{:04x}", self.0.len() + 4).as_ref());
        v.extend_from_slice(self.0);

        match self {
            Self::Data(data) => {
                v.extend_from_slice(format!("{:04x}", data.len() + 4).as_ref());
                v.extend_from_slice(data);    
            },
            Self::Flush => v.extend_from_slice(b"0000"),
            Self::Delimiter => v.extend_from_slice(b"0001"),
            Self::ResponseEnd => v.extend_from_slice(b"0002"),
        }

        v
    }
}
@@ -24,7 +37,7 @@

impl<'a> From<&'a str> for PktLine<'a> {
    fn from(val: &'a str) -> Self {
        PktLine(val.as_bytes())
        PktLine::Data(val.as_bytes())
    }
}

diff --git a/src/git/packfile.rs b/src/git/packfile.rs
new file mode 100644
index 0000000..6d205d4 100644
--- /dev/null
+++ a/src/git/packfile.rs
@@ -1,0 +1,26 @@
// The offset/sha1[] tables are sorted by sha1[] values (this is to
// allow binary search of this table), and fanout[] table points at
// the offset/sha1[] table in a specific way (so that part of the
// latter table that covers all hashes that start with a given byte
// can be found to avoid 8 iterations of the binary search).
//
// packfile indexes are not neccesary to extract objects from a packfile
pub struct PackFileIndex<const S: usize> { // S should be u16
    pub magic: [u8; 4], // "\x337t0c" - header magic value
    pub version: [u8; 4], // "0002", - header version
    pub fanout: [[u8; 4]; 255],
    pub size: u16, // fanout[256] => size == S
    pub sha1: [[u8; 20]; S], // sha listing
    pub crc: [[u8; 4]; S], // checksum
    pub offset: [[u8; 4]; S], // packfile offsets
    // 64b_offset: [[u8; 8]; N], // for packfiles over 2gb
    pub packfile_checksum: [u8; 20], // sha1
    pub idxfiel_checksum: [u8; 20], // sha1
}

// The packfile itself is a very simple format. There is a header, a
// series of packed objects (each with it's own header and body) and
// then a checksum trailer. The first four bytes is the string 'PACK',
// which is sort of used to make sure you're getting the start of the
// packfile correctly. This is followed by a 4-byte packfile version
// number and then a 4-byte number of entries in that file.