diff --git a/Cargo.lock b/Cargo.lock index 07e3ad6..2e6e5a9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,6 +262,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + [[package]] name = "libc" version = "0.2.112" @@ -364,6 +370,8 @@ name = "nemicosm" version = "0.1.0" dependencies = [ "anyhow", + "bytes", + "leb128", "native-tls", "rustls 0.20.2", "trust-dns-resolver", diff --git a/Cargo.toml b/Cargo.toml index ff09e0a..3387705 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,6 +7,8 @@ edition = "2021" [dependencies] anyhow = "1.0.52" +bytes = "1.1.0" +leb128 = "0.2.5" native-tls = "0.2.8" rustls = { version = "0.20.2", features = ["dangerous_configuration"] } trust-dns-resolver = { version = "0.20.3", features = ["dns-over-rustls"] } diff --git a/src/gen.rs b/src/gen.rs new file mode 100644 index 0000000..3c99be6 --- /dev/null +++ b/src/gen.rs @@ -0,0 +1,7 @@ +trait Object { + fn id(&self) -> u64; + + fn interfaces(self) -> Vec; + + fn release(self); +} diff --git a/src/leb128.rs b/src/leb128.rs new file mode 100644 index 0000000..5d9b49a --- /dev/null +++ b/src/leb128.rs @@ -0,0 +1,234 @@ +/* +Taken from the `leb128` crate, which is Apache2 / MIT dual licensed + +Copyright (c) 2015 The Rust Project Developers + +Permission is hereby granted, free of charge, to any +person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the +Software without restriction, including without +limitation the rights to use, copy, modify, merge, +publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software +is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice +shall be included in all copies or substantial portions +of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF +ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT +SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR +IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +DEALINGS IN THE SOFTWARE. +*/ + +/* +We have our own version because it's important to know how many bytes were read; + +TODO: If somehow this is hot, theirs a branchless SIMD version, I read about on +wikipedia +*/ + +pub const CONTINUATION_BIT: u8 = 1 << 7; +pub const SIGN_BIT: u8 = 1 << 6; + +pub fn low_bits_of_byte(byte: u8) -> u8 { + byte & !CONTINUATION_BIT +} + +pub fn low_bits_of_u64(val: u64) -> u8 { + let byte = val & (std::u8::MAX as u64); + low_bits_of_byte(byte as u8) +} + +/// A module for reading LEB128-encoded signed and unsigned integers. +pub mod read { + use super::{low_bits_of_byte, CONTINUATION_BIT, SIGN_BIT}; + use std::fmt; + use std::io; + + /// An error type for reading LEB128-encoded values. + #[derive(Debug)] + pub enum Error { + /// There was an underlying IO error. + IoError(io::Error), + /// The number being read is larger than can be represented. + Overflow, + } + + impl From for Error { + fn from(e: io::Error) -> Self { + Error::IoError(e) + } + } + + impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + match *self { + Error::IoError(ref e) => e.fmt(f), + Error::Overflow => { + write!(f, "The number being read is larger than can be represented") + } + } + } + } + + impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match *self { + Error::IoError(ref e) => Some(e), + Error::Overflow => None, + } + } + } + + /// Read an unsigned LEB128-encoded number from the `std::io::Read` stream + /// `r`. + /// + /// On success, return the number, and the number of bytes read. + pub fn unsigned(r: &mut R) -> Result<(u64, usize), Error> + where + R: ?Sized + io::Read, + { + let mut result = 0; + let mut shift = 0; + let mut read = 0; + + loop { + let mut buf = [0]; + r.read_exact(&mut buf)?; + read += 1; + + if shift == 63 && buf[0] != 0x00 && buf[0] != 0x01 { + while buf[0] & CONTINUATION_BIT != 0 { + r.read_exact(&mut buf)?; + read += 1; + } + return Err(Error::Overflow); + } + + let low_bits = low_bits_of_byte(buf[0]) as u64; + result |= low_bits << shift; + + if buf[0] & CONTINUATION_BIT == 0 { + return Ok((result, read)); + } + + shift += 7; + } + } + + /// Read a signed LEB128-encoded number from the `std::io::Read` stream `r`. + /// + /// On success, return the number and the number of bytes read. + pub fn signed(r: &mut R) -> Result<(i64, usize), Error> + where + R: ?Sized + io::Read, + { + let mut result = 0; + let mut shift = 0; + let size = 64; + let mut byte; + let mut read = 0; + + loop { + let mut buf = [0]; + r.read_exact(&mut buf)?; + read += 1; + + byte = buf[0]; + if shift == 63 && byte != 0x00 && byte != 0x7f { + while buf[0] & CONTINUATION_BIT != 0 { + r.read_exact(&mut buf)?; + read += 1; + } + return Err(Error::Overflow); + } + + let low_bits = low_bits_of_byte(byte) as i64; + result |= low_bits << shift; + shift += 7; + + if byte & CONTINUATION_BIT == 0 { + break; + } + } + + if shift < size && (SIGN_BIT & byte) == SIGN_BIT { + // Sign extend the result. + result |= !0 << shift; + } + + Ok((result, read)) + } +} + +/// A module for writing LEB128-encoded signed and unsigned integers. +pub mod write { + use super::{low_bits_of_u64, CONTINUATION_BIT}; + use std::io; + + /// Write `val` to the `std::io::Write` stream `w` as an unsigned LEB128 value. + /// + /// On success, return the number of bytes written to `w`. + pub fn unsigned(w: &mut W, mut val: u64) -> Result + where + W: ?Sized + io::Write, + { + let mut bytes_written = 0; + loop { + let mut byte = low_bits_of_u64(val); + val >>= 7; + if val != 0 { + // More bytes to come, so set the continuation bit. + byte |= CONTINUATION_BIT; + } + + let buf = [byte]; + w.write_all(&buf)?; + bytes_written += 1; + + if val == 0 { + return Ok(bytes_written); + } + } + } + + /// Write `val` to the `std::io::Write` stream `w` as a signed LEB128 value. + /// + /// On success, return the number of bytes written to `w`. + pub fn signed(w: &mut W, mut val: i64) -> Result + where + W: ?Sized + io::Write, + { + let mut bytes_written = 0; + loop { + let mut byte = val as u8; + // Keep the sign bit for testing + val >>= 6; + let done = val == 0 || val == -1; + if done { + byte &= !CONTINUATION_BIT; + } else { + // Remove the sign bit + val >>= 1; + // More bytes to come, so set the continuation bit. + byte |= CONTINUATION_BIT; + } + + let buf = [byte]; + w.write_all(&buf)?; + bytes_written += 1; + + if done { + return Ok(bytes_written); + } + } + } +} diff --git a/src/main.rs b/src/main.rs index 763f301..a10aa46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,15 @@ +// mod leb128; +mod num; +mod object; +mod proto; + use std::{ io::{Read, Write}, net::TcpStream, sync::Arc, }; -use anyhow::{Context, Result}; +use anyhow::{ensure, Context, Result}; // use native_tls::TlsConnector; // use native_tls::TlsConnector; use rustls::{ @@ -17,8 +22,12 @@ use trust_dns_resolver::{ }; use uuid::Uuid; +use num::Vu64; + +use crate::num::Vu32; + fn main() -> Result<()> { - let (port, host) = resolve_dns("daeken.dev")?; + // let (port, host) = resolve_dns("daeken.dev")?; let port = 12345; let host = "localhost"; @@ -29,25 +38,61 @@ fn main() -> Result<()> { let mut tls_conn = make_tls_connection(tls_conf, &host, port) .with_context(|| format!("Can't connect to {}:{}", host, port))?; - let uuid = Uuid::new_v4(); - dbg!(&uuid); - tls_conn - .write_all(uuid.as_bytes()) - .context("Can't write UUID")?; + let mut uuid = Uuid::new_v4().as_u128().to_le_bytes(); + uuid[0] = 0; + uuid[1] = 0; + uuid[2] = 0; + uuid[3] = 0; + + tls_conn.write_all(&uuid).context("Can't write UUID")?; + + let uuid = Uuid::from_bytes(uuid); + dbg!(uuid); let mut serv_uuid = [0; 16]; tls_conn.read_exact(&mut serv_uuid)?; let serv_uuid = Uuid::from_bytes(serv_uuid); + + ensure!(serv_uuid != uuid); + dbg!(serv_uuid); - // Hangs ATM - let mut new = [0; 100]; - tls_conn.write_all(&new)?; - let len = tls_conn.read(&mut new)?; + let mut command_no = 0; + let mut recv_buff = Vec::new(); - dbg!(&new[..len]); + loop { + // Recieve step + let mut compression = [0]; + let l = tls_conn.read(&mut compression)?; + match l { + 0 => {} + 1 => { + assert_eq!(compression, [0]); - Ok(()) + // Handle recieve + let len = Vu64::read(&mut tls_conn)?; + dbg!(len); + recv_buff.resize(len.0 as usize, 0); + tls_conn.read_exact(&mut recv_buff)?; + dbg!(&recv_buff); + + let mut recv_buff = &recv_buff[..]; + let seq_num = Vu64::read(&mut recv_buff)?; + + if (seq_num & 1) == 0 { + // LSB = 0: This is a call, we need to respond + dbg!(seq_num); + let command_no = Vu32::read(&mut recv_buff)?; + let obj_id = Vu64::read(&mut recv_buff)?; + let serialised = recv_buff; + dbg!(command_no, obj_id, serialised); + } else { + // This is a reponce to something we've asked for + } + } + _ => unreachable!("Should only have read one byte"), + } + } } // TODO: How to cache this? diff --git a/src/num.rs b/src/num.rs new file mode 100644 index 0000000..0113122 --- /dev/null +++ b/src/num.rs @@ -0,0 +1,72 @@ +use std::{ + io::{self, Read, Write}, + num::TryFromIntError, + ops::BitAnd, +}; + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct Vu64(pub(crate) u64); + +impl Vu64 { + pub(crate) fn read(r: &mut R) -> io::Result { + leb128::read::unsigned(r).map_err(leb_err).map(Self) + } + + pub(crate) fn write(&self, w: &mut W) -> io::Result { + leb128::write::unsigned(w, self.0) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +pub(crate) struct Vu32(pub(crate) u32); + +impl Vu32 { + pub(crate) fn read(r: &mut R) -> io::Result { + leb128::read::unsigned(r) + .map_err(leb_err)? + .try_into() + .map(Self) + .map_err(conv_err) + } + + pub(crate) fn write(&self, w: &mut W) -> io::Result { + leb128::write::unsigned(w, self.0.into()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] + +pub(crate) struct Vi32(pub(crate) i32); + +impl Vi32 { + pub(crate) fn read(r: &mut R) -> io::Result { + leb128::read::signed(r) + .map_err(leb_err)? + .try_into() + .map(Self) + .map_err(conv_err) + } + + pub(crate) fn write(&self, w: &mut W) -> io::Result { + leb128::write::signed(w, self.0.into()) + } +} + +fn conv_err(e: TryFromIntError) -> io::Error { + io::Error::new(io::ErrorKind::InvalidData, e) +} + +fn leb_err(e: leb128::read::Error) -> io::Error { + match e { + leb128::read::Error::IoError(e) => e, + leb128::read::Error::Overflow => io::Error::new(io::ErrorKind::InvalidData, e), + } +} + +impl BitAnd for Vu64 { + type Output = u64; + + fn bitand(self, rhs: u64) -> Self::Output { + self.0 & rhs + } +} diff --git a/src/object.rs b/src/object.rs new file mode 100644 index 0000000..850fead --- /dev/null +++ b/src/object.rs @@ -0,0 +1,5 @@ +struct Object { + handler: Handler, +} + +type Handler = fn(u32, &[u32]); diff --git a/src/proto.rs b/src/proto.rs new file mode 100644 index 0000000..527c33c --- /dev/null +++ b/src/proto.rs @@ -0,0 +1,41 @@ +use bytes::Bytes; + +use crate::num::{Vi32, Vu64}; + +type ObjId = Vu64; + +struct Header { + compression: Compression, + wire_lenght: Vu64, +} + +struct Call { + header: Header, + /// Identifies what call/responce this is part of + /// + /// LSB always 0 + sequence: Vu64, + /// What function to execute + command_number: Vi32, + /// What object to execute it on + object: ObjId, + /// Serialized arguments to the function + data: Bytes, +} + +struct Responce { + header: Header, + /// Identifies what call/responce this is part of + /// + /// LSB always 1 + sequence: Vu64, + /// Result of the call + status: Vi32, + /// Serialized result of the call + data: Bytes, +} + +#[repr(u8)] +enum Compression { + None = 0, +}