From e6e34e00f1aa6474021e55192be09b0b8d1d82c2 Mon Sep 17 00:00:00 2001 From: Marcin Anforowicz Date: Fri, 29 Nov 2024 21:28:39 -0800 Subject: [PATCH] Fixed a few doc typos. --- Cargo.toml | 3 - README.md | 2 +- TODO.md | 30 ---------- gday/README.md | 49 ++++++++------- gday_contact_exchange_protocol/README.md | 2 +- gday_contact_exchange_protocol/src/lib.rs | 60 +++++++++---------- .../tests/test_integration.rs | 8 +-- gday_encryption/Cargo.toml | 2 +- gday_encryption/README.md | 2 +- gday_encryption/src/lib.rs | 4 +- gday_file_transfer/src/file_meta.rs | 4 +- gday_file_transfer/src/lib.rs | 3 +- gday_file_transfer/src/offer.rs | 52 ++++++++-------- gday_file_transfer/src/transfer.rs | 7 ++- gday_hole_punch/src/contact_sharer.rs | 2 +- gday_server/src/lib.rs | 2 +- 16 files changed, 103 insertions(+), 129 deletions(-) delete mode 100644 TODO.md diff --git a/Cargo.toml b/Cargo.toml index 0bcbca2..bad44e4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,3 @@ version = "0.2.1" [profile.dist] inherits = "release" lto = "thin" - -[profile.release] -debug = true \ No newline at end of file diff --git a/README.md b/README.md index 6b90130..de3ed13 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ and file transfer end-to-end encrypted with - Automatically tries both IPv4 and IPv6. -- Immune to malicious servers impersonating your peer. +- Resistant to malicious servers impersonating your peer. Uses [SPAKE2](https://datatracker.ietf.org/doc/rfc9382/) to derive an encryption key from a shared secret. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 64dbb96..0000000 --- a/TODO.md +++ /dev/null @@ -1,30 +0,0 @@ -# To-Do's -Quick notes on items to consider implementing. -Not all of them are desirable or necessary. - -- Confirm gday server works properly on both ipv4 and ipv6. Maybe add a test. - -- Add TLS tests. - - -## Abandoned ideas - -- Make peer authentication not "block" hole punching. - "Blocking" might be an issue when the peer is receiving other - incoming connections. But this probably won't happen unless - the peer's device is acting as some sort of server. - -- Allow sending a simple text string instead of only files. - Though, I don't think this is a common use case. - -- Let the client select a source port, to utilize port forwarding. - However, turns out port forwarding works for inbound connections, - and not outbound ones, so this wouldn't help. - -- Restructure the hole puncher to force keeping connection to server open - during hole-punching. That might please some NATs that lose state when TCP connection is closed. - This is not really necessary, since I can just add a comment - telling any library users to not drop ServerConnection when calling connect to peer. - -- Make file transfer response msg list file names, instead of going by index? - I don't really see the advantage to doing this. diff --git a/gday/README.md b/gday/README.md index 00409cd..13142fc 100644 --- a/gday/README.md +++ b/gday/README.md @@ -4,53 +4,62 @@ Command line tool to securely send files (without a relay or port forwarding).
-peer_1: gday send image.jpg folder
-<Asks for confirmation>
-Tell your mate to run "gday get 1.1C30.C71E.A".
-Transfer complete.
+peer_1: gday send file.mp4 folder
+Tell your mate to run "gday get 1.n5xn8.wvqsf".
 
-peer_2: gday get 1.1C30.C71E.A
-<Asks for confirmation>
+peer_2: gday get 1.n5xn8.wvqsf
 Transfer complete.
 
-[![asciicast](https://asciinema.org/a/1jjPVyccHweqgwA5V3un4tCnU.svg)](https://asciinema.org/a/1jjPVyccHweqgwA5V3un4tCnU) +[![asciicast](https://asciinema.org/a/Z8OJJr8xHRAJh6fuqocNcm9Zu.svg)](https://asciinema.org/a/Z8OJJr8xHRAJh6fuqocNcm9Zu) ## Installation To run the executable directly: -1. Go to [releases](https://github.com/manforowicz/gday/releases) -and download the correct file for your platform. +1. Download an executable from [releases](https://github.com/manforowicz/gday/releases). 2. Extract it (on Linux: `tar xf `). 3. Run it: `./gday` To install with **cargo**: ``` -$ cargo install gday +cargo install gday ``` To install with **brew**: ``` -$ brew install manforowicz/tap/gday +brew install manforowicz/tap/gday ``` ## Features -- File transfer is always direct, without relay servers. -A server is only used to exchange socket addresses at the beginning. + - No limit on the size of files and folders sent. + +- Files are sent directly, without relay servers. +A server is only used to exchange socket addresses at the beginning. + +- Automatically resumes interrupted transfers. Just `gday send` the same files, and partial downloads will be detected and resumed. + - Doesn't require port forwarding. Instead, uses [TCP Hole Punching](https://bford.info/pub/net/p2pnat/) to traverse [NATs](https://en.wikipedia.org/wiki/Network_address_translation). -Note: this may not work on very restrictive NATs. -- Server connection encrypted with [TLS](https://docs.rs/rustls/) -and file transfer encrypted with [ChaCha20Poly1305](https://docs.rs/chacha20poly1305/). +This may not work on very restrictive NATs. If that happens, enable IPv6 or move to a different network. + +- If a contact exchange server is down, just uses a different one from the default list. Or specify your own with `--server`. + +- Server connection encrypted with +[TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security) +and file transfer end-to-end encrypted with +[ChaCha20Poly1305](https://en.wikipedia.org/wiki/ChaCha20-Poly1305). + - Automatically tries both IPv4 and IPv6. -- Immune to malicious servers impersonating your peer. -Uses [SPAKE2](https://docs.rs/spake2/) password authenticated key exchange -to derive an encryption key from a shared secret. + +- Resistant to malicious servers impersonating your peer. +Uses [SPAKE2](https://datatracker.ietf.org/doc/rfc9382/) to derive an +encryption key from a shared secret. + - No `unsafe` Rust in this repository. @@ -66,7 +75,7 @@ Commands: Options: -s, --server Use a custom gday server with this domain name -p, --port Connect to a custom server port - -u, --unencrypted Use raw TCP without TLS + -u, --unencrypted Use TCP without TLS -v, --verbosity Verbosity. (trace, debug, info, warn, error) [default: warn] -h, --help Print help -V, --version Print version diff --git a/gday_contact_exchange_protocol/README.md b/gday_contact_exchange_protocol/README.md index db06915..17ea72a 100644 --- a/gday_contact_exchange_protocol/README.md +++ b/gday_contact_exchange_protocol/README.md @@ -2,7 +2,7 @@ [![Crates.io Version](https://img.shields.io/crates/v/gday_contact_exchange_protocol)](https://crates.io/crates/gday_contact_exchange_protocol) [![docs.rs](https://img.shields.io/docsrs/gday_contact_exchange_protocol)](https://docs.rs/gday_contact_exchange_protocol/) -This protocol lets two users exchange their public and (optionally) private socket addresses via a server. +Protocol for peers to exchange their socket addresses via a server. See the [documentation](https://docs.rs/gday_contact_exchange_protocol/). diff --git a/gday_contact_exchange_protocol/src/lib.rs b/gday_contact_exchange_protocol/src/lib.rs index 4bd5f30..2c0d9a9 100644 --- a/gday_contact_exchange_protocol/src/lib.rs +++ b/gday_contact_exchange_protocol/src/lib.rs @@ -1,4 +1,4 @@ -//! This protocol lets two users exchange their public and (optionally) private socket addresses via a server. +//! Protocol for peers to exchange their socket addresses via a server. //! //! On it's own, this library doesn't do anything other than define a shared protocol. //! In most cases, you should use one of the following crates: @@ -86,7 +86,7 @@ pub const DEFAULT_PORT: u16 = 2311; /// Version of the protocol. /// Different numbers wound indicate /// incompatible protocol breaking changes. -pub const PROTOCOL_VERSION: u16 = 1; +pub const PROTOCOL_VERSION: u8 = 1; /// A message from client to server. #[derive(Serialize, Deserialize, Debug, Eq, PartialEq, Clone, Copy)] @@ -291,9 +291,13 @@ impl std::fmt::Display for FullContact { /// and 2 bytes holding the length of the following message (all in big-endian). pub fn write_to(msg: impl Serialize, writer: &mut impl Write) -> Result<(), Error> { let vec = serde_json::to_vec(&msg)?; - let len_byte = u16::try_from(vec.len())?; - writer.write_all(&PROTOCOL_VERSION.to_be_bytes())?; - writer.write_all(&len_byte.to_be_bytes())?; + let len = u16::try_from(vec.len())?; + + let mut header = [0; 3]; + header[0] = PROTOCOL_VERSION; + header[1..3].copy_from_slice(&len.to_be_bytes()); + + writer.write_all(&header)?; writer.write_all(&vec)?; writer.flush()?; Ok(()) @@ -308,9 +312,13 @@ pub async fn write_to_async( writer: &mut (impl AsyncWrite + Unpin), ) -> Result<(), Error> { let vec = serde_json::to_vec(&msg)?; - let len_byte = u16::try_from(vec.len())?; - writer.write_all(&PROTOCOL_VERSION.to_be_bytes()).await?; - writer.write_all(&len_byte.to_be_bytes()).await?; + let len = u16::try_from(vec.len())?; + + let mut header = [0; 3]; + header[0] = PROTOCOL_VERSION; + header[1..3].copy_from_slice(&len.to_be_bytes()); + + writer.write_all(&header).await?; writer.write_all(&vec).await?; writer.flush().await?; Ok(()) @@ -318,19 +326,15 @@ pub async fn write_to_async( /// Reads a message from `reader` using [`serde_json`]. /// -/// Assumes the message is prefixed with 2 bytes holding the [`PROTOCOL_VERSION`] -/// and 2 bytes holding the length of the following message (all in big-endian). +/// Assumes the message is prefixed with 1 byte holding the [`PROTOCOL_VERSION`] +/// and 2 big-endian bytes holding the length of the following message. pub fn read_from(reader: &mut impl Read) -> Result { - let mut protocol = [0_u8; 2]; - reader.read_exact(&mut protocol)?; - let protocol = u16::from_be_bytes(protocol); - if protocol != PROTOCOL_VERSION { + let mut header = [0_u8; 3]; + reader.read_exact(&mut header)?; + if header[0] != PROTOCOL_VERSION { return Err(Error::IncompatibleProtocol); } - - let mut len = [0_u8; 2]; - reader.read_exact(&mut len)?; - let len = u16::from_be_bytes(len) as usize; + let len = u16::from_be_bytes(header[1..3].try_into().unwrap()) as usize; let mut buf = vec![0; len]; reader.read_exact(&mut buf)?; @@ -339,21 +343,17 @@ pub fn read_from(reader: &mut impl Read) -> Result( reader: &mut (impl AsyncRead + Unpin), ) -> Result { - let mut protocol = [0_u8; 2]; - reader.read_exact(&mut protocol).await?; - let protocol = u16::from_be_bytes(protocol); - if protocol != PROTOCOL_VERSION { + let mut header = [0_u8; 3]; + reader.read_exact(&mut header).await?; + if header[0] != PROTOCOL_VERSION { return Err(Error::IncompatibleProtocol); } - - let mut len = [0_u8; 2]; - reader.read_exact(&mut len).await?; - let len = u16::from_be_bytes(len) as usize; + let len = u16::from_be_bytes(header[1..3].try_into().unwrap()) as usize; let mut buf = vec![0; len]; reader.read_exact(&mut buf).await?; @@ -372,8 +372,8 @@ pub enum Error { #[error("IO Error: {0}")] IO(#[from] std::io::Error), - /// Can't send message longer than 2^32 bytes. - #[error("Can't send message longer than 2^32 bytes: {0}")] + /// Can't send message longer than 2^16 bytes. + #[error("Can't send message longer than 2^16 bytes: {0}")] MsgTooLong(#[from] std::num::TryFromIntError), /// Received a message with an incompatible protocol version. diff --git a/gday_contact_exchange_protocol/tests/test_integration.rs b/gday_contact_exchange_protocol/tests/test_integration.rs index 78ae714..584430c 100644 --- a/gday_contact_exchange_protocol/tests/test_integration.rs +++ b/gday_contact_exchange_protocol/tests/test_integration.rs @@ -36,7 +36,7 @@ fn error_on_invalid_json() { let mut pipe = std::collections::VecDeque::new(); // gibberish json - pipe.write_all(&[0, 1, 0, 5, 52, 45, 77, 123, 12]).unwrap(); + pipe.write_all(&[1, 0, 5, 52, 45, 77, 123, 12]).unwrap(); let result: Result = read_from(&mut pipe); assert!(matches!(result, Err(Error::JSON(_)))); } @@ -46,7 +46,7 @@ fn error_on_incompatible_version() { let mut pipe = std::collections::VecDeque::new(); // invalid version - pipe.write_all(&[0, 2, 0, 5, 52, 45, 77, 123, 12]).unwrap(); + pipe.write_all(&[2, 0, 5, 52, 45, 77, 123, 12]).unwrap(); let result: Result = read_from(&mut pipe); assert!(matches!(result, Err(Error::IncompatibleProtocol))); } @@ -80,7 +80,7 @@ async fn error_on_invalid_json_async() { let (mut writer, mut reader) = tokio::io::duplex(1000); // gibberish json writer - .write_all(&[0, 1, 0, 5, 52, 45, 77, 123, 12]) + .write_all(&[1, 0, 5, 52, 45, 77, 123, 12]) .await .unwrap(); let result: Result = read_from_async(&mut reader).await; @@ -92,7 +92,7 @@ async fn error_on_incompatible_version_async() { let (mut writer, mut reader) = tokio::io::duplex(1000); // gibberish json writer - .write_all(&[0, 2, 0, 5, 52, 45, 77, 123, 12]) + .write_all(&[2, 0, 5, 52, 45, 77, 123, 12]) .await .unwrap(); let result: Result = read_from_async(&mut reader).await; diff --git a/gday_encryption/Cargo.toml b/gday_encryption/Cargo.toml index a64de73..d2601bc 100644 --- a/gday_encryption/Cargo.toml +++ b/gday_encryption/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "gday_encryption" -description = "Simple encrypted ChaCha20Poly1305 wrapper around an IO stream." +description = "Simple encrypted ChaCha20Poly1305 wrapper around an async IO stream." homepage = "https://github.com/manforowicz/gday/tree/main/gday_encryption" categories = ["cryptography"] diff --git a/gday_encryption/README.md b/gday_encryption/README.md index 055c991..2261391 100644 --- a/gday_encryption/README.md +++ b/gday_encryption/README.md @@ -2,7 +2,7 @@ [![Crates.io Version](https://img.shields.io/crates/v/gday_encryption)](https://crates.io/crates/gday_encryption) [![docs.rs](https://img.shields.io/docsrs/gday_encryption)](https://docs.rs/gday_encryption/) -A simple encrypted wrapper around an IO stream. +Simple encrypted ChaCha20Poly1305 wrapper around an async IO stream. Uses a streaming [chacha20poly1305](https://docs.rs/chacha20poly1305/latest/chacha20poly1305/) cipher. See the [documentation](https://docs.rs/gday_encryption/). diff --git a/gday_encryption/src/lib.rs b/gday_encryption/src/lib.rs index d684507..d5c9c91 100644 --- a/gday_encryption/src/lib.rs +++ b/gday_encryption/src/lib.rs @@ -1,6 +1,4 @@ -//! A simple encrypted wrapper around an IO stream. -//! -//! Uses a streaming [chacha20poly1305](https://docs.rs/chacha20poly1305/latest/chacha20poly1305/) cipher. +//! Simple encrypted ChaCha20Poly1305 wrapper around an async IO stream. //! //! This library is used by [gday_file_transfer](https://crates.io/crates/gday_file_transfer), //! which is used by [gday](https://crates.io/crates/gday). diff --git a/gday_file_transfer/src/file_meta.rs b/gday_file_transfer/src/file_meta.rs index bb7fe58..b070f1f 100644 --- a/gday_file_transfer/src/file_meta.rs +++ b/gday_file_transfer/src/file_meta.rs @@ -15,7 +15,7 @@ pub struct FileMeta { pub len: u64, } -/// Information about a locally stored file +/// Information about a locally stored file. #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord)] pub struct FileMetaLocal { /// The shortened path that will be offered to the peer @@ -194,7 +194,7 @@ fn suffix_with_number(path: &mut PathBuf, number: u32) { /// Returns the [`FileMetaLocal`] of each file, including those in nested directories. /// /// Returns an error if can't access a path, one path is the prefix -/// of another path, or two paths end in the same name. +/// of another path, or two of the given `paths` end in the same name. /// /// Each file's [`FileMeta::short_path`] will contain the path to the file, /// starting at the provided level, ignoring parent directories. diff --git a/gday_file_transfer/src/lib.rs b/gday_file_transfer/src/lib.rs index 3eed3ec..d0f05b4 100644 --- a/gday_file_transfer/src/lib.rs +++ b/gday_file_transfer/src/lib.rs @@ -26,7 +26,6 @@ //! # let (stream1, stream2) = tokio::io::duplex(64); //! # let mut stream1 = tokio::io::BufReader::new(stream1); //! # let mut stream2 = tokio::io::BufReader::new(stream2); -//! //! // Peer A offers files and folders they'd like to send //! let paths_to_send = ["folder/to/send/".into(), "a/file.txt".into()]; //! let files_to_send = get_file_metas(&paths_to_send)?; @@ -70,7 +69,7 @@ pub use crate::transfer::{receive_files, send_files, TransferReport}; /// Version of the protocol. /// Different numbers wound indicate /// incompatible protocol breaking changes. -pub const PROTOCOL_VERSION: u16 = 1; +pub const PROTOCOL_VERSION: u8 = 1; /// `gday_file_transfer` error. #[derive(Error, Debug)] diff --git a/gday_file_transfer/src/offer.rs b/gday_file_transfer/src/offer.rs index 4cd68e2..841b291 100644 --- a/gday_file_transfer/src/offer.rs +++ b/gday_file_transfer/src/offer.rs @@ -167,9 +167,13 @@ impl FileResponseMsg { /// and 4 bytes holding the length of the following message (all in big-endian). pub fn write_to(msg: impl Serialize, writer: &mut impl Write) -> Result<(), Error> { let vec = serde_json::to_vec(&msg)?; - let len_bytes = u32::try_from(vec.len())?; - writer.write_all(&PROTOCOL_VERSION.to_be_bytes())?; - writer.write_all(&len_bytes.to_be_bytes())?; + let len = u32::try_from(vec.len())?; + + let mut header = [0; 5]; + header[0] = PROTOCOL_VERSION; + header[1..5].copy_from_slice(&len.to_be_bytes()); + + writer.write_all(&header)?; writer.write_all(&vec)?; writer.flush()?; Ok(()) @@ -184,9 +188,13 @@ pub async fn write_to_async( writer: &mut (impl AsyncWrite + Unpin), ) -> Result<(), Error> { let vec = serde_json::to_vec(&msg)?; - let len_bytes = u32::try_from(vec.len())?; - writer.write_all(&PROTOCOL_VERSION.to_be_bytes()).await?; - writer.write_all(&len_bytes.to_be_bytes()).await?; + let len = u32::try_from(vec.len())?; + + let mut header = [0; 5]; + header[0] = PROTOCOL_VERSION; + header[1..5].copy_from_slice(&len.to_be_bytes()); + + writer.write_all(&header).await?; writer.write_all(&vec).await?; writer.flush().await?; Ok(()) @@ -194,19 +202,15 @@ pub async fn write_to_async( /// Reads a message from `reader` using [`serde_json`]. /// -/// Assumes the message is prefixed with 2 bytes holding the [`PROTOCOL_VERSION`] -/// and 4 bytes holding the length of the following message (all in big-endian). +/// Assumes the message is prefixed with 1 byte holding the [`PROTOCOL_VERSION`] +/// and 4 big-endian bytes holding the length of the following message. pub fn read_from(reader: &mut impl Read) -> Result { - let mut protocol = [0_u8; 2]; - reader.read_exact(&mut protocol)?; - let protocol = u16::from_be_bytes(protocol); - if protocol != PROTOCOL_VERSION { + let mut header = [0_u8; 5]; + reader.read_exact(&mut header)?; + if header[0] != PROTOCOL_VERSION { return Err(Error::IncompatibleProtocol); } - - let mut len = [0_u8; 4]; - reader.read_exact(&mut len)?; - let len = u32::from_be_bytes(len) as usize; + let len = u32::from_be_bytes(header[1..5].try_into().unwrap()) as usize; let mut buf = vec![0; len]; reader.read_exact(&mut buf)?; @@ -215,21 +219,17 @@ pub fn read_from(reader: &mut impl Read) -> Result( reader: &mut (impl AsyncRead + Unpin), ) -> Result { - let mut protocol = [0_u8; 2]; - reader.read_exact(&mut protocol).await?; - let protocol = u16::from_be_bytes(protocol); - if protocol != PROTOCOL_VERSION { + let mut header = [0_u8; 5]; + reader.read_exact(&mut header).await?; + if header[0] != PROTOCOL_VERSION { return Err(Error::IncompatibleProtocol); } - - let mut len = [0_u8; 4]; - reader.read_exact(&mut len).await?; - let len = u32::from_be_bytes(len) as usize; + let len = u32::from_be_bytes(header[1..5].try_into().unwrap()) as usize; let mut buf = vec![0; len]; reader.read_exact(&mut buf).await?; diff --git a/gday_file_transfer/src/transfer.rs b/gday_file_transfer/src/transfer.rs index 78f537c..404161d 100644 --- a/gday_file_transfer/src/transfer.rs +++ b/gday_file_transfer/src/transfer.rs @@ -20,8 +20,9 @@ pub struct TransferReport { /// Transfers the requested files to `writer`. /// -/// - `offer` is the `Vec` of [`FileMetaLocal`] offered to the peer. -/// - `response` is the peer's [`FileResponseMsg`]. +/// - `offer` is the `Vec` of [`FileMetaLocal`] you sent to your peer. +/// - `response` is the [`FileResponseMsg`] received from your peer. +/// - `writer` is the the IO stream on which the files will be sent. /// - `progress_callback` is a function that gets frequently /// called with [`TransferReport`] to report progress. /// @@ -84,7 +85,7 @@ pub async fn send_files( /// Receives the requested files from `reader`. /// /// - `offer` is the [`FileOfferMsg`] offered by the peer. -/// - `response` is your corresponding [`FileResponseMsg`]. +/// - `response` is the [`FileResponseMsg`] that you've sent in response. /// - `save_path` is the directory where the files should be saved. /// - `reader` is the IO stream on which the files will be received. /// - `progress_callback` is an function that gets frequently diff --git a/gday_hole_punch/src/contact_sharer.rs b/gday_hole_punch/src/contact_sharer.rs index c7c90c0..3760976 100644 --- a/gday_hole_punch/src/contact_sharer.rs +++ b/gday_hole_punch/src/contact_sharer.rs @@ -6,7 +6,7 @@ use sha2::Digest; use std::future::Future; /// Shares contacts on `room_code` in the gday server -/// that `server_connection`is connected to. +/// that `server_connection` is connected to. /// /// If `is_creator`, tries creating the room, otherwise tries joining it. /// diff --git a/gday_server/src/lib.rs b/gday_server/src/lib.rs index ccbae7b..4e88bfd 100644 --- a/gday_server/src/lib.rs +++ b/gday_server/src/lib.rs @@ -55,7 +55,7 @@ pub struct Args { pub request_limit: u32, /// Log verbosity. (trace, debug, info, warn, error) - #[arg(short, long, default_value = "info")] + #[arg(short, long, default_value = "debug")] pub verbosity: log::LevelFilter, }