diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..87fa24d --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,95 @@ +on: [push, pull_request] + +name: Rust CI + +jobs: + check: + name: Check + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: check + args: --manifest-path Cargo.toml + + test: + name: Test Suite + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - uses: actions-rs/cargo@v1 + with: + command: test + args: --manifest-path Cargo.toml + + fmt: + name: Rustfmt + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + override: true + - run: rustup component add rustfmt + - uses: actions-rs/cargo@v1 + with: + command: fmt + args: --manifest-path Cargo.toml --all -- --check + + coverage: + runs-on: ubuntu-latest + env: + CARGO_TERM_COLOR: always + steps: + - uses: actions/checkout@v4 + - name: Install Rust + run: rustup update stable + - name: Install cargo-llvm-cov + uses: taiki-e/install-action@cargo-llvm-cov + - name: Generate code coverage + run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info + env: + RUST_LOG: debug + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos + files: lcov.info + fail_ci_if_error: true + verbose: true + + + # clippy: + # name: Clippy + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # - uses: actions-rs/toolchain@v1 + # with: + # profile: minimal + # toolchain: stable + # override: true + # - run: rustup component add clippy + # - uses: actions-rs/cargo@v1 + # with: + # command: clippy + # args: -- -D warnings diff --git a/src/node/connection_reader.rs b/src/node/connection_reader.rs deleted file mode 100644 index d67e083..0000000 --- a/src/node/connection_reader.rs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright 2024 Kulpreet Singh - -// This file is part of Frost-Federation - -// Frost-Federation is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. - -// Frost-Federation is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Frost-Federation. If not, see -// . - -use tokio::net::tcp::OwnedReadHalf; -use tokio::sync::mpsc; -use tokio_util::bytes::Bytes; -use tokio_util::codec::{FramedRead, LengthDelimitedCodec}; -use tokio_util::sync::CancellationToken; - -// Bring StreamExt in scope for access to `next` calls -use tokio_stream::StreamExt; - -/// Read messages from the framed reader and enqueue it for noise and -/// higher layers to process. -pub struct ConnectionReader { - pub send_channel: mpsc::Sender, - pub framed_reader: FramedRead, - pub cancel_token: CancellationToken, -} - -impl ConnectionReader { - pub async fn start(&mut self) { - loop { - tokio::select! { - data = self.framed_reader.next() => { - if let Some(message) = data { - match message { - Ok(message) => { - log::debug!("Received from network ... {:?}", message); - if let Err(e) = self.send_channel.send(message.freeze()).await { - log::debug!("Error en-queuing message: {:?}", e); - } - } - Err(e) => { - log::debug!("Error reading from channel {:?}", e); - self.cancel_token.cancel(); - return; - } - } - - } - else { - self.cancel_token.cancel(); - return; - } - }, - _ = self.cancel_token.cancelled() => { - log::info!("Connection closed"); - return - } - } - } - } -} diff --git a/src/node/connection_writer.rs b/src/node/connection_writer.rs deleted file mode 100644 index a3fcede..0000000 --- a/src/node/connection_writer.rs +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2024 Kulpreet Singh - -// This file is part of Frost-Federation - -// Frost-Federation is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. - -// Frost-Federation is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Frost-Federation. If not, see -// . - -use tokio::net::tcp::OwnedWriteHalf; -use tokio::sync::mpsc; -use tokio_util::bytes::Bytes; -use tokio_util::codec::{FramedWrite, LengthDelimitedCodec}; -use tokio_util::sync::CancellationToken; - -// Bring SinkExt in scope for access to `send` calls -use futures::sink::SinkExt; - -/// Send message over the noise connection -pub struct ConnectionWriter { - pub receive_channel: mpsc::Receiver, - pub framed_writer: FramedWrite, - pub cancel_token: CancellationToken, -} - -impl ConnectionWriter { - pub async fn start(&mut self) { - loop { - tokio::select! { - Some(message) = self.receive_channel.recv() => { - log::debug!("Sending using framed writer {:?}", message); - if self.framed_writer.send(message).await.is_err() { - log::info!("Closing connection"); - self.cancel_token.cancel(); - } - }, - _ = self.cancel_token.cancelled() => { - log::info!("Connection closed"); - return; - } - } - } - } -} diff --git a/src/node/noise_handler.rs b/src/node/noise_handler.rs index 60258d8..cadad93 100644 --- a/src/node/noise_handler.rs +++ b/src/node/noise_handler.rs @@ -30,9 +30,7 @@ use tokio_util::{ // // We use 25519 instead of LN's choice of secp256k1 as // rust Noise implementation doesn't yet support secp256k1 -// noise_params: "Noise_XX_25519_ChaChaPoly_SHA256".parse().unwrap(), -// static PATTERN: &str = "Noise_NN_25519_ChaChaPoly_BLAKE2s"; static PATTERN: &str = "Noise_XX_25519_ChaChaPoly_SHA256"; const NOISE_MAX_MSG_LENGTH: usize = 65535; @@ -197,3 +195,38 @@ impl NoiseHandler { (reader, writer) } } + +#[cfg(test)] +mod tests { + + use super::NoiseHandler; + use tokio_util::bytes::Bytes; + + static TEST_KEY: &str = " +-----BEGIN PRIVATE KEY----- +MFECAQEwBQYDK2VwBCIEIJ7pILqR7yBPsVuysfGyofjOEm19skmtqcJYWiVwjKH1 +gSEA68zeZuy7PMMQC9ECPmWqDl5AOFj5bi243F823ZVWtXY= +-----END PRIVATE KEY----- +"; + + #[test] + fn it_builds_noise_handler() { + let handler = NoiseHandler::new(true, TEST_KEY.to_string()); + assert!(handler.initiator); + assert!(handler.handshake_state.is_some()); + assert!(handler.transport_state.is_none()); + } + + #[test] + fn it_builds_initiator_noise_handler() { + let mut handler = NoiseHandler::new(true, TEST_KEY.to_string()); + let noise_message: Bytes = handler.build_handshake_message(b"test bytes"); + let len = noise_message.len(); + assert_eq!(&noise_message[(len - 10)..], b"test bytes"); + + let mut responder = NoiseHandler::new(false, TEST_KEY.to_string()); + let read_message: Bytes = responder.read_handshake_message(noise_message); + let len = read_message.len(); + assert_eq!(&read_message[(len - 10)..], b"test bytes"); + } +} diff --git a/src/node/protocol.rs b/src/node/protocol.rs index b36fea6..6a60103 100644 --- a/src/node/protocol.rs +++ b/src/node/protocol.rs @@ -24,12 +24,10 @@ use serde::{Deserialize, Serialize}; mod handshake; mod heartbeat; -mod noise_handshake; mod ping; pub use handshake::HandshakeMessage; pub use heartbeat::HeartbeatMessage; -pub use noise_handshake::NoiseHandshakeMessage; pub use ping::PingMessage; use super::reliable_sender::ReliableSenderHandle; @@ -39,30 +37,16 @@ pub enum Message { Handshake(HandshakeMessage), Heartbeat(HeartbeatMessage), Ping(PingMessage), - NoiseHandshake(NoiseHandshakeMessage), } /// Methods for all protocol messages impl Message { - /// Return the message as bytes - pub fn as_bytes(&self) -> Option { - let mut s = flexbuffers::FlexbufferSerializer::new(); - self.serialize(&mut s).unwrap(); - Some(Bytes::from(s.take_buffer())) - } - - /// Build message from bytes - pub fn from_bytes(b: &[u8]) -> Result> { - Ok(flexbuffers::from_slice(b)?) - } - /// Generates the response to send for a message received pub fn response_for_received(&self) -> Result, String> { match self { Message::Handshake(m) => m.response_for_received(), Message::Heartbeat(m) => m.response_for_received(), Message::Ping(m) => m.response_for_received(), - Message::NoiseHandshake(m) => m.response_for_received(), } } } @@ -90,57 +74,35 @@ where fn response_for_received(&self) -> Result, String>; } -// #[cfg(test)] -// mod tests { -// use super::Message; -// use super::PingMessage; -// use super::ProtocolMessage; -// use bytes::Bytes; -// use serde::Serialize; -// use std::net::SocketAddr; -// use std::str::FromStr; - -// #[test] -// fn it_serialized_ping_message() { -// let ping_message = Message::Ping(PingMessage { -// message: String::from("ping"), -// }); -// let mut s = flexbuffers::FlexbufferSerializer::new(); -// ping_message.serialize(&mut s).unwrap(); -// let b = Bytes::from(s.take_buffer()); - -// let msg = Message::from_bytes(&b).unwrap(); -// assert_eq!(msg, ping_message); -// } - -// #[test] -// fn it_matches_start_message_for_ping() { -// let addr = SocketAddr::from_str("127.0.0.1:6680").unwrap(); -// let start_message = PingMessage::start(&addr).unwrap(); -// assert_eq!( -// start_message, -// Message::Ping(PingMessage { -// message: String::from("ping") -// }) -// ); -// } - -// #[test] -// fn it_invoked_received_message_after_deseralization() { -// let b: Bytes = Message::Ping(PingMessage { -// message: String::from("ping"), -// }) -// .as_bytes() -// .unwrap(); - -// let msg: Message = Message::from_bytes(&b).unwrap(); - -// let response = msg.response_for_received().unwrap(); -// assert_eq!( -// response, -// Some(Message::Ping(PingMessage { -// message: String::from("pong") -// })) -// ); -// } -// } +#[cfg(test)] +mod tests { + use super::Message; + use super::PingMessage; + use super::ProtocolMessage; + + #[test] + fn it_matches_start_message_for_ping() { + let start_message = PingMessage::start().unwrap(); + assert_eq!( + start_message, + Message::Ping(PingMessage { + message: String::from("ping") + }) + ); + } + + #[test] + fn it_invokes_received_message_after_deseralization() { + let msg = Message::Ping(PingMessage { + message: String::from("ping"), + }); + + let response = msg.response_for_received().unwrap(); + assert_eq!( + response, + Some(Message::Ping(PingMessage { + message: String::from("pong") + })) + ); + } +} diff --git a/src/node/protocol/heartbeat.rs b/src/node/protocol/heartbeat.rs index 57ef087..c748e82 100644 --- a/src/node/protocol/heartbeat.rs +++ b/src/node/protocol/heartbeat.rs @@ -37,22 +37,24 @@ impl ProtocolMessage for HeartbeatMessage { } } -// #[cfg(test)] -// mod tests { - -// use crate::node::protocol::{HeartbeatMessage, Message}; - -// #[test] -// fn it_matches_start_message_for_handshake() { -// if let Some(Message::Heartbeat(start_message)) = HeartbeatMessage::start() { -// assert_eq!(start_message.from, String::from("127.0.0.1:6680")) -// } -// } - -// #[test] -// fn it_matches_response_message_for_correct_handshake_start() { -// let start_message = HeartbeatMessage::start().unwrap(); -// let response = start_message.response_for_received().unwrap(); -// assert_eq!(response, None); -// } -// } +#[cfg(test)] +mod tests { + + use std::time::SystemTime; + + use crate::node::protocol::{HeartbeatMessage, Message, ProtocolMessage}; + + #[test] + fn it_matches_start_message_for_handshake() { + if let Some(Message::Heartbeat(start_message)) = HeartbeatMessage::start() { + assert!(start_message.time < SystemTime::now()); + } + } + + #[test] + fn it_matches_response_message_for_correct_handshake_start() { + let start_message = HeartbeatMessage::start().unwrap(); + let response = start_message.response_for_received().unwrap(); + assert_eq!(response, None); + } +} diff --git a/src/node/protocol/noise_handshake.rs b/src/node/protocol/noise_handshake.rs deleted file mode 100644 index b2191e9..0000000 --- a/src/node/protocol/noise_handshake.rs +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright 2024 Kulpreet Singh - -// This file is part of Frost-Federation - -// Frost-Federation is free software: you can redistribute it and/or -// modify it under the terms of the GNU General Public License as -// published by the Free Software Foundation, either version 3 of the -// License, or (at your option) any later version. - -// Frost-Federation is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -// General Public License for more details. - -// You should have received a copy of the GNU General Public License -// along with Frost-Federation. If not, see -// . - -use super::{Message, ProtocolMessage}; -use serde::{Deserialize, Serialize}; - -#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)] -pub struct NoiseHandshakeMessage { - pub message: String, -} - -impl ProtocolMessage for NoiseHandshakeMessage { - fn start() -> Option { - Some(Message::NoiseHandshake(NoiseHandshakeMessage { - message: String::from("-> e"), - })) - } - - fn response_for_received(&self) -> Result, String> { - match self.message.as_str() { - "-> e" => Ok(Some(Message::NoiseHandshake(NoiseHandshakeMessage { - message: String::from("<- e, ee, s, es"), - }))), - "<- e, ee, s, es" => Ok(Some(Message::NoiseHandshake(NoiseHandshakeMessage { - message: String::from("-> s, se"), - }))), - "-> s, se" => Ok(Some(Message::NoiseHandshake(NoiseHandshakeMessage { - message: String::from("finished"), - }))), - _ => Ok(None), - } - } -} diff --git a/src/node/protocol/ping.rs b/src/node/protocol/ping.rs index e0becf3..8368e8e 100644 --- a/src/node/protocol/ping.rs +++ b/src/node/protocol/ping.rs @@ -41,3 +41,28 @@ impl ProtocolMessage for PingMessage { } } } + +#[cfg(test)] +mod tests { + + use crate::node::protocol::{Message, PingMessage, ProtocolMessage}; + + #[test] + fn it_matches_start_message_for_ping() { + if let Some(Message::Ping(start_message)) = PingMessage::start() { + assert_eq!(start_message.message, "ping".to_string()); + } + } + + #[test] + fn it_matches_response_message_for_correct_handshake_start() { + let start_message = PingMessage::start().unwrap(); + let response = start_message.response_for_received().unwrap().unwrap(); + assert_eq!( + response, + Message::Ping(PingMessage { + message: "pong".to_string() + }) + ); + } +} diff --git a/src/node/reliable_sender.rs b/src/node/reliable_sender.rs index 1862cb0..4f58f1a 100644 --- a/src/node/reliable_sender.rs +++ b/src/node/reliable_sender.rs @@ -203,3 +203,28 @@ impl ReliableSenderHandle { receiver.await? } } + +#[cfg(test)] +mod tests { + use crate::node::reliable_sender::ReliableNetworkMessage; + + use crate::node::protocol::{Message, PingMessage}; + use serde::Serialize; + use tokio_util::bytes::Bytes; + + #[test] + fn it_serialized_ping_message() { + let ping_reliable_message = ReliableNetworkMessage::Send( + Message::Ping(PingMessage { + message: String::from("ping"), + }), + 1, + ); + let mut s = flexbuffers::FlexbufferSerializer::new(); + ping_reliable_message.serialize(&mut s).unwrap(); + let b = Bytes::from(s.take_buffer()); + + let msg = ReliableNetworkMessage::from_bytes(&b).unwrap(); + assert_eq!(msg, ping_reliable_message); + } +}