From 6e682b824ba1c6012af93ce2813abeb93aa47806 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sat, 28 Aug 2021 03:07:24 +0100 Subject: [PATCH] helpful things --- 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 { 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 { // 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.-- rgit 0.1.3