🏡 index : ~doyle/chartered.git

author Jordan Doyle <jordan@doyle.la> 2021-08-29 1:07:53.0 +01:00:00
committer Jordan Doyle <jordan@doyle.la> 2021-08-29 10:50:13.0 +01:00:00
commit
7b04225719bb60a9fe48d667628a543e79d75153 [patch]
tree
03edcfc2ecff11518a255628ffa757d27ca09a74
parent
6e682b824ba1c6012af93ce2813abeb93aa47806
download
7b04225719bb60a9fe48d667628a543e79d75153.tar.gz

encoder



Diff

 src/main.rs         | 212 +++++++++++++++++++++++++++++++++++++++++++-------------------------------------
 src/git/codec.rs    |  26 +++++++++++++++++++++++---
 src/git/mod.rs      |  39 ++++++++++++++++++++++-----------------
 src/git/packfile.rs |  15 ++++++++-------
 4 files changed, 126 insertions(+), 166 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 4acc4b3..1befe48 100644
--- a/src/main.rs
+++ a/src/main.rs
@@ -1,15 +1,16 @@
pub mod git;

use crate::git::PktLine;


use std::sync::{Arc};
use crate::git::codec::GitCodec;
use bytes::BytesMut;
use futures::future::Future;
use git::codec::Encoder;
use std::{fmt::Write, pin::Pin, sync::Arc};
use thrussh::server::{Auth, Session};
use thrussh::*;
use thrussh_keys::*;
use bytes::BytesMut;
use crate::git::codec::GitCodec;
use tokio_util::codec::Decoder;
use tokio_util::codec::{Decoder, Encoder as TokioEncoder};

#[tokio::main]
async fn main() {
@@ -40,12 +41,26 @@
struct Handler {
    codec: GitCodec,
    input_bytes: BytesMut,
    output_bytes: BytesMut,
}

impl Handler {
    fn write(&mut self, packet: PktLine<'_>) -> Result<(), anyhow::Error> {
        Encoder {}.encode(packet, &mut self.output_bytes)
    }

    fn flush(&mut self, session: &mut Session, channel: ChannelId) {
        session.data(
            channel,
            CryptoVec::from_slice(self.output_bytes.split().as_ref()),
        )
    }
}

impl server::Handler for Handler {
    type Error = anyhow::Error;
    type FutureAuth = futures::future::Ready<Result<(Self, server::Auth), anyhow::Error>>;
    type FutureUnit = futures::future::Ready<Result<(Self, Session), anyhow::Error>>;
    type FutureUnit = Pin<Box<dyn Future<Output = Result<(Self, Session), Self::Error>> + Send>>;
    type FutureBool = futures::future::Ready<Result<(Self, Session, bool), anyhow::Error>>;

    fn finished_auth(self, auth: Auth) -> Self::FutureAuth {
@@ -60,41 +75,49 @@

    fn finished(self, s: Session) -> Self::FutureUnit {
        eprintln!("finished");
        futures::future::ready(Ok((self, s)))
        Box::pin(futures::future::ready(Ok((self, s))))
    }

    fn shell_request(self, channel: ChannelId, mut session: Session) -> Self::FutureUnit {
        session.data(channel, CryptoVec::from_slice(b"Hi there! You've successfully authenticated, but chartered does not provide shell access.\r\n"));
        session.close(channel);
        futures::future::ready(Ok((self, session)))
    fn shell_request(mut self, channel: ChannelId, mut session: Session) -> Self::FutureUnit {
        Box::pin(async move {
            write!(&mut self.output_bytes, "Hi there! You've successfully authenticated, but chartered does not provide shell access.\r\n")?;
            self.flush(&mut session, channel);
            session.close(channel);
            Ok((self, session))
        })
    }

    fn exec_request(
        self,
        mut self,
        channel: ChannelId,
        data: &[u8],
        mut session: Session,
    ) -> Self::FutureUnit {
        eprintln!("exec {:x?}", data);

        if !data.starts_with(b"git-upload-pack ") {
            session.data(
                channel,
                CryptoVec::from_slice(b"Sorry, I have no clue who you are\r\n"),
            );
            session.close(channel);
        } else {
            // TODO: check GIT_PROTOCOL=version=2 set
            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)))
        let git_upload_pack = data.starts_with(b"git-upload-pack ");

        Box::pin(async move {
            if git_upload_pack {
                // TODO: check GIT_PROTOCOL=version=2 set
                self.write(PktLine::Data(b"version 2\n"))?;
                self.write(PktLine::Data(b"agent=chartered/0.1.0\n"))?;
                self.write(PktLine::Data(b"ls-refs=unborn\n"))?;
                self.write(PktLine::Data(b"fetch=shallow wait-for-done\n"))?;
                self.write(PktLine::Data(b"server-option\n"))?;
                self.write(PktLine::Data(b"object-info\n"))?;
                self.write(PktLine::Flush)?;
                self.flush(&mut session, channel);
            } else {
                session.data(
                    channel,
                    CryptoVec::from_slice(b"Sorry, I have no clue who you are\r\n"),
                );
                session.close(channel);
            }

            Ok((self, session))
        })
    }

    fn subsystem_request(
@@ -104,7 +127,7 @@
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("subsystem req: {}", data);
        futures::future::ready(Ok((self, session)))
        Box::pin(futures::future::ready(Ok((self, session))))
    }

    fn auth_publickey(self, _: &str, _: &key::PublicKey) -> Self::FutureAuth {
@@ -115,115 +138,32 @@
    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;
        Box::pin(async move {
            let mut ls_refs = false;

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

            if frame.as_ref() == "command=ls-refs".as_bytes() {
                ls_refs = true;
                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)))
    }

    fn extended_data(
        self,
        _channel: ChannelId,
        code: u32,
        data: &[u8],
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("got extended data: {:x?} ({})", data, code);
        futures::future::ready(Ok((self, session)))
    }

    fn signal(self, _channel: ChannelId, signal_name: Sig, session: Session) -> Self::FutureUnit {
        eprintln!("signal: {:#?}", signal_name);
        futures::future::ready(Ok((self, session)))
    }

    fn window_change_request(
        self,
        _channel: ChannelId,
        _col_width: u32,
        _row_height: u32,
        _pix_width: u32,
        _pix_height: u32,
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("window change req");
        futures::future::ready(Ok((self, session)))
    }

    fn env_request(
        self,
        _channel: ChannelId,
        variable_name: &str,
        variable_value: &str,
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("set env {} = {}", variable_name, variable_value);
        futures::future::ready(Ok((self, session)))
    }

    fn x11_request(
        self,
        _channel: ChannelId,
        _single_connection: bool,
        _x11_auth_protocol: &str,
        _x11_auth_cookie: &str,
        _x11_screen_number: u32,
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("x11 req");
        futures::future::ready(Ok((self, session)))
    }

    fn pty_request(
        self,
        _channel: ChannelId,
        _term: &str,
        _col_width: u32,
        _row_height: u32,
        _pix_width: u32,
        _pix_height: u32,
        _modes: &[(Pty, u32)],
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("pty req");
        futures::future::ready(Ok((self, session)))
    }

    fn channel_open_direct_tcpip(
        self,
        _channel: ChannelId,
        _host_to_connect: &str,
        _port_to_connect: u32,
        _originator_address: &str,
        _originator_port: u32,
        session: Session,
    ) -> Self::FutureUnit {
        eprintln!("direct tcpip");
        futures::future::ready(Ok((self, session)))
    }
            // 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 {
                self.write(PktLine::Data(b"1a1b25ae7c87a0e87b7a9aa478a6bc4331c6b954 HEAD symref-target:refs/heads/master\n"))?;
                self.write(PktLine::Flush)?;
                self.flush(&mut session, channel);

                // next command will be a fetch like above
            }

    fn channel_eof(self, _channel: ChannelId, session: Session) -> Self::FutureUnit {
        eprintln!("eof");
        futures::future::ready(Ok((self, session)))
            Ok((self, session))
        })
    }
}
diff --git a/src/git/codec.rs b/src/git/codec.rs
index 5fe3752..fc12996 100644
--- a/src/git/codec.rs
+++ a/src/git/codec.rs
@@ -1,6 +1,21 @@
use bytes::{Buf, Bytes, BytesMut};
use tokio_util::codec;
use bytes::{Bytes, Buf};

use super::PktLine;

pub struct Encoder {
    // buf: BytesMut,
}

impl codec::Encoder<PktLine<'_>> for Encoder {
    type Error = anyhow::Error;

    fn encode(&mut self, item: PktLine<'_>, dst: &mut BytesMut) -> Result<(), Self::Error> {
        item.encode_to(dst)?;
        Ok(())
    }
}

#[derive(Default)]
pub struct GitCodec;

@@ -19,7 +34,8 @@

        if length == 0 // flush-pkt
            || length == 1 // delim-pkt
            || length == 2 // response-end-pkt
            || length == 2
        // response-end-pkt
        {
            eprintln!("pkt: {}", length);
            src.advance(4);
@@ -27,7 +43,9 @@
        }

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

        if src.len() < length {
@@ -48,9 +66,9 @@

#[cfg(test)]
mod test {
    use tokio_util::codec::Decoder;
    use bytes::BytesMut;
    use std::fmt::Write;
    use tokio_util::codec::Decoder;

    #[test]
    fn decode() {
diff --git a/src/git/mod.rs b/src/git/mod.rs
index a312d7a..405c092 100644
--- a/src/git/mod.rs
+++ a/src/git/mod.rs
@@ -1,7 +1,8 @@
pub mod codec;
pub mod packfile;

use thrussh::CryptoVec;
use bytes::BytesMut;
use std::fmt::Write;

pub enum PktLine<'a> {
    Data(&'a [u8]),
@@ -11,29 +12,26 @@
}

impl PktLine<'_> {
    // todo: encode to connection's `bytes::BytesMut`
    pub fn encode(&self) -> Vec<u8> {
        let mut v = Vec::new();

    pub fn encode_to(&self, buf: &mut BytesMut) -> Result<(), anyhow::Error> {
        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"),
                write!(buf, "{:04x}", data.len() + 4)?;
                buf.extend_from_slice(&data);
            }
            Self::Flush => buf.extend_from_slice(b"0000"),
            Self::Delimiter => buf.extend_from_slice(b"0001"),
            Self::ResponseEnd => buf.extend_from_slice(b"0002"),
        }

        v
        Ok(())
    }
}

impl From<PktLine<'_>> for CryptoVec {
    fn from(val: PktLine<'_>) -> Self {
        Self::from(val.encode())
    }
}
// impl From<PktLine<'_>> for CryptoVec {
//     fn from(val: PktLine<'_>) -> Self {
//         Self::from(val.encode())
//     }
// }

impl<'a> From<&'a str> for PktLine<'a> {
    fn from(val: &'a str) -> Self {
@@ -43,9 +41,12 @@

#[cfg(test)]
mod test {
    use bytes::BytesMut;

    #[test]
    fn test_pkt_line() {
        let encoded = super::PktLine(b"agent=git/2.32.0\n").encode();
        assert_eq!(encoded, b"0015agent=git/2.32.0\n");
        let mut buffer = BytesMut::new();
        super::PktLine::Data(b"agent=git/2.32.0\n").encode_to(&mut buffer).unwrap();
        assert_eq!(buffer.as_ref(), b"0015agent=git/2.32.0\n");
    }
}
diff --git a/src/git/packfile.rs b/src/git/packfile.rs
index 6d205d4..b83c539 100644
--- a/src/git/packfile.rs
+++ a/src/git/packfile.rs
@@ -5,17 +5,18 @@
// 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 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 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
    pub idxfiel_checksum: [u8; 20],  // sha1
}

// The packfile itself is a very simple format. There is a header, a
@@ -23,4 +24,4 @@
// 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.
// number and then a 4-byte number of entries in that file.