From 7b04225719bb60a9fe48d667628a543e79d75153 Mon Sep 17 00:00:00 2001 From: Jordan Doyle Date: Sun, 29 Aug 2021 01:07:53 +0100 Subject: [PATCH] encoder --- 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>; - type FutureUnit = futures::future::Ready>; + type FutureUnit = Pin> + Send>>; type FutureBool = futures::future::Ready>; 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> 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 { - 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> for CryptoVec { - fn from(val: PktLine<'_>) -> Self { - Self::from(val.encode()) - } -} +// impl From> 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 { // S should be u16 - pub magic: [u8; 4], // "\x337t0c" - header magic value +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 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. -- rgit 0.1.3