Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async support #3

Merged
merged 11 commits into from
Feb 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,25 +14,25 @@ sha2 = "0.10.2"
ctr = "0.9.1"
aes = "0.8.1"
log = "0.4.14"
ciborium-io = "0.2.0"
rand_core = "0.6.3"
x25519-dalek = { version = "2.0.0-pre.1", optional = true }
curve25519-dalek = { version = "4.0.0-pre.2", optional = true }
tokio = { version = "1.36", features = ["net", "io-util"]}
thiserror = "1"
rand = "0.8.5"

[dev-dependencies]
hex = "0.4.3"
x25519-dalek = "= 2.0.0-pre.1"
curve25519-dalek = "= 4.0.0-pre.2"
rand = "0.8.5"
tokio = { version = "1.36", features = ["rt-multi-thread", "macros"]}
base64 = "0.13.0"
ciborium-io = { version = "0.2.0", features = ["alloc"]}
anyhow = "1"

[features]
default = []
default = ["dalek"]
dalek = ["x25519-dalek", "curve25519-dalek"]
alloc = ["ciborium-io/alloc"]
std = ["ciborium-io/std"]

[[example]]
name = "time"
required-features = ["std", "dalek"]
required-features = ["dalek"]
70 changes: 23 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,69 +2,45 @@

Minimal ADNL implementation in Rust (client-server only, without p2p for now). Specification of ADNL is available [here](https://github.com/tonstack/ton-docs/blob/main/ADNL/README.md).

| Feature | Status |
| ------------- | -------------------------------- |
| ADNL Client | ✅ Implemented |
| ADNL Server | ❌ Not implemented |
| ADNL P2P | ❌ Not implemented |
| async | ❌ Not implemented |
| ed25519 libs | curve25519_dalek + x25519_dalek |
| Feature | Status |
|--------------|---------------------------------|
| ADNL Client | ✅ Implemented |
| ADNL Server | ❌ Not implemented |
| ADNL P2P | ❌ Not implemented |
| async | ✅ Implemented |
| ed25519 libs | curve25519_dalek + x25519_dalek |

## Quickstart
Run this example: `cargo run --example time --features "std dalek"`
Run this example: `cargo run --example time`

```rust
use adnl::{AdnlBuilder, AdnlClient};
use std::error::Error;
use std::net::{SocketAddrV4, TcpStream};
use x25519_dalek::StaticSecret;
use adnl::AdnlClient;
use anyhow::{anyhow, Context, Result};
use std::net::SocketAddrV4;

pub fn connect(
ls_public: &str,
ls_ip: &str,
ls_port: u16,
) -> Result<AdnlClient<TcpStream>, Box<dyn Error>> {
// decode liteserver public key
let remote_public: [u8; 32] = base64::decode(ls_public)?
.try_into()
.map_err(|_| "bad public key length")?;

// generate private key
let local_secret = StaticSecret::new(rand::rngs::OsRng);

// use TcpStream as a transport for our ADNL connection
let transport = TcpStream::connect(SocketAddrV4::new(ls_ip.parse()?, ls_port))?;

// build handshake using random session keys, encrypt it with ECDH(local_secret, remote_public)
// then perform handshake over our TcpStream
let client = AdnlBuilder::with_random_aes_params(&mut rand::rngs::OsRng)
.perform_ecdh(local_secret, remote_public)
.perform_handshake(transport)
.map_err(|e| format!("{:?}", e))?;
Ok(client)
}
#[tokio::main]
async fn main() -> Result<()> {
// decode liteserver public key
let remote_public: [u8; 32] = base64::decode("JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=")
.context("Error decode base64")?
.try_into().map_err(|_| anyhow!("Bad public key length"))?;

fn main() -> Result<(), Box<dyn Error>> {
let ls_ip = "65.21.74.140";
let ls_port = 46427;
// create AdnlClient
let mut client = connect(
"JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=",
"65.21.74.140",
46427,
)?;
let mut client =
AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?;

// already serialized TL with gettime query
let mut query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?;

// send over ADNL, use random nonce
client
.send(&mut query, &mut rand::random())
.map_err(|e| format!("{:?}", e))?;
client.send(&mut query).await?;

// receive result into vector, use 8192 bytes buffer
let mut result = Vec::<u8>::new();
client
.receive::<_, 8192>(&mut result)
.map_err(|e| format!("{:?}", e))?;
client.receive(&mut result).await?;

// get time from serialized TL answer
println!(
Expand Down
52 changes: 14 additions & 38 deletions examples/time.rs
Original file line number Diff line number Diff line change
@@ -1,54 +1,30 @@
use adnl::{AdnlBuilder, AdnlClient};
use std::error::Error;
use std::net::{SocketAddrV4, TcpStream};
use x25519_dalek::StaticSecret;
use adnl::AdnlClient;
use anyhow::{anyhow, Context, Result};
use std::net::SocketAddrV4;

pub fn connect(
ls_public: &str,
ls_ip: &str,
ls_port: u16,
) -> Result<AdnlClient<TcpStream>, Box<dyn Error>> {
#[tokio::main]
async fn main() -> Result<()> {
// decode liteserver public key
let remote_public: [u8; 32] = base64::decode(ls_public)?
let remote_public: [u8; 32] = base64::decode("JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=")
.context("Error decode base64")?
.try_into()
.map_err(|_| "bad public key length")?;
.map_err(|_| anyhow!("Bad public key length"))?;

// generate private key
let local_secret = StaticSecret::new(rand::rngs::OsRng);

// use TcpStream as a transport for our ADNL connection
let transport = TcpStream::connect(SocketAddrV4::new(ls_ip.parse()?, ls_port))?;

// build handshake using random session keys, encrypt it with ECDH(local_secret, remote_public)
// then perform handshake over our TcpStream
let client = AdnlBuilder::with_random_aes_params(&mut rand::rngs::OsRng)
.perform_ecdh(local_secret, remote_public)
.perform_handshake(transport)
.map_err(|e| format!("{:?}", e))?;
Ok(client)
}

fn main() -> Result<(), Box<dyn Error>> {
let ls_ip = "65.21.74.140";
let ls_port = 46427;
// create AdnlClient
let mut client = connect(
"JhXt7H1dZTgxQTIyGiYV4f9VUARuDxFl/1kVBjLSMB8=",
"65.21.74.140",
46427,
)?;
let mut client =
AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?;

// already serialized TL with gettime query
let mut query = hex::decode("7af98bb435263e6c95d6fecb497dfd0aa5f031e7d412986b5ce720496db512052e8f2d100cdf068c7904345aad16000000000000")?;

// send over ADNL, use random nonce
client
.send(&mut query, &mut rand::random())
.map_err(|e| format!("{:?}", e))?;
client.send(&mut query).await?;

// receive result into vector, use 8192 bytes buffer
let mut result = Vec::<u8>::new();
client
.receive::<_, 8192>(&mut result)
.map_err(|e| format!("{:?}", e))?;
client.receive(&mut result).await?;

// get time from serialized TL answer
println!(
Expand Down
49 changes: 18 additions & 31 deletions src/helper_types.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use ciborium_io::{Read, Write};
use sha2::{Digest, Sha256};
use std::io::Error;
use std::net::AddrParseError;
use thiserror::Error;

pub trait CryptoRandom: rand_core::RngCore + rand_core::CryptoRng {}

Expand All @@ -10,7 +12,7 @@ pub trait AdnlPublicKey {
let mut hasher = Sha256::new();
hasher.update([0xc6, 0xb4, 0x13, 0x48]); // type id - always ed25519
hasher.update(self.to_bytes());
AdnlAddress(hasher.finalize().try_into().unwrap())
AdnlAddress(hasher.finalize().into())
}

fn to_bytes(&self) -> [u8; 32];
Expand Down Expand Up @@ -144,36 +146,21 @@ impl AdnlSecret {
}
}

/// Empty transport to use when there is nothing to read or write
#[derive(Debug)]
pub struct Empty;

impl Write for Empty {
type Error = ();

fn write_all(&mut self, _data: &[u8]) -> Result<(), Self::Error> {
Ok(())
}

fn flush(&mut self) -> Result<(), Self::Error> {
Ok(())
}
}

impl Read for Empty {
type Error = ();

fn read_exact(&mut self, _data: &mut [u8]) -> Result<(), Self::Error> {
Ok(())
}
}

/// Common error type
#[derive(Debug)]
pub enum AdnlError<R: Read, W: Write, C: Write> {
ReadError(R::Error),
WriteError(W::Error),
ConsumeError(C::Error),
#[derive(Debug, Error)]
pub enum AdnlError {
#[error("Read error")]
ReadError(Error),
#[error("Write error")]
WriteError(Error),
#[error("Consume error")]
ConsumeError(Error),
#[error("Integrity error")]
IntegrityError,
#[error("TooShortPacket error")]
TooShortPacket,
#[error("Incorrect ip address")]
IncorrectAddr(AddrParseError),
#[error(transparent)]
OtherError(#[from] Error),
}
4 changes: 1 addition & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#![cfg_attr(not(feature = "std"), no_std)]

pub use helper_types::{
AdnlAddress, AdnlAesParams, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret, Empty,
AdnlAddress, AdnlAesParams, AdnlError, AdnlPrivateKey, AdnlPublicKey, AdnlSecret,
};
pub use primitives::handshake::AdnlHandshake;
pub use primitives::receive::AdnlReceiver;
Expand Down
12 changes: 6 additions & 6 deletions src/primitives/handshake.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::primitives::AdnlAes;
use crate::{AdnlAddress, AdnlAesParams, AdnlClient, AdnlError, AdnlPublicKey, AdnlSecret, Empty};
use crate::{AdnlAddress, AdnlAesParams, AdnlClient, AdnlError, AdnlPublicKey, AdnlSecret};
use aes::cipher::KeyIvInit;
use ciborium_io::{Read, Write};
use ctr::cipher::StreamCipher;
use sha2::{Digest, Sha256};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

/// Handshake packet, must be sent from client to server prior to any datagrams
pub struct AdnlHandshake<P: AdnlPublicKey> {
Expand Down Expand Up @@ -44,7 +44,7 @@ impl<P: AdnlPublicKey> AdnlHandshake<P> {
let mut raw_params = self.aes_params.to_bytes();
let mut hasher = Sha256::new();
hasher.update(raw_params);
let hash: [u8; 32] = hasher.finalize().try_into().unwrap();
let hash: [u8; 32] = hasher.finalize().into();

let mut key = [0u8; 32];
key[..16].copy_from_slice(&self.secret.as_bytes()[..16]);
Expand All @@ -62,10 +62,10 @@ impl<P: AdnlPublicKey> AdnlHandshake<P> {
}

/// Send handshake over the given transport, build [`AdnlClient`] on top of it
pub fn perform_handshake<T: Read + Write>(
pub async fn perform_handshake<T: AsyncReadExt + AsyncWriteExt + Unpin>(
&self,
transport: T,
) -> Result<AdnlClient<T>, AdnlError<T, T, Empty>> {
AdnlClient::perform_handshake(transport, self)
) -> Result<AdnlClient<T>, AdnlError> {
AdnlClient::perform_handshake(transport, self).await
}
}
29 changes: 18 additions & 11 deletions src/primitives/receive.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use crate::primitives::AdnlAes;
use crate::{AdnlAesParams, AdnlError, Empty};
use crate::{AdnlAesParams, AdnlError};
use aes::cipher::KeyIvInit;
use ciborium_io::{Read, Write};
use ctr::cipher::StreamCipher;
use sha2::{Digest, Sha256};
use tokio::io::{AsyncReadExt, AsyncWriteExt};

/// Low-level incoming datagram processor
pub struct AdnlReceiver {
Expand All @@ -25,17 +25,18 @@ impl AdnlReceiver {
///
/// You can adjust `BUFFER` according to your memory requirements.
/// Recommended size is 8192 bytes.
pub fn receive<R: Read, C: Write, const BUFFER: usize>(
pub async fn receive<R: AsyncReadExt + Unpin, C: AsyncWriteExt + Unpin, const BUFFER: usize>(
&mut self,
transport: &mut R,
consumer: &mut C,
) -> Result<(), AdnlError<R, Empty, C>> {
) -> Result<(), AdnlError> {
// read length
let mut length = [0u8; 4];
log::debug!("reading length");
transport
.read_exact(&mut length)
.map_err(|e| AdnlError::ReadError(e))?;
.await
.map_err(AdnlError::ReadError)?;
self.aes.apply_keystream(&mut length);
let length = u32::from_le_bytes(length);
log::debug!("length = {}", length);
Expand All @@ -50,7 +51,8 @@ impl AdnlReceiver {
log::debug!("reading nonce");
transport
.read_exact(&mut nonce)
.map_err(|e| AdnlError::ReadError(e))?;
.await
.map_err(AdnlError::ReadError)?;
self.aes.apply_keystream(&mut nonce);
hasher.update(nonce);

Expand All @@ -66,12 +68,14 @@ impl AdnlReceiver {
);
transport
.read_exact(&mut buffer)
.map_err(|e| AdnlError::ReadError(e))?;
.await
.map_err(AdnlError::ReadError)?;
self.aes.apply_keystream(&mut buffer);
hasher.update(buffer);
consumer
.write_all(&buffer)
.map_err(|e| AdnlError::ConsumeError(e))?;
.await
.map_err(AdnlError::WriteError)?;
bytes_to_read -= BUFFER;
}

Expand All @@ -81,20 +85,23 @@ impl AdnlReceiver {
let buffer = &mut buffer[..bytes_to_read];
transport
.read_exact(buffer)
.map_err(|e| AdnlError::ReadError(e))?;
.await
.map_err(AdnlError::ReadError)?;
self.aes.apply_keystream(buffer);
hasher.update(&buffer);
consumer
.write_all(buffer)
.map_err(|e| AdnlError::ConsumeError(e))?;
.await
.map_err(AdnlError::WriteError)?;
}
}

let mut given_hash = [0u8; 32];
log::debug!("reading hash");
transport
.read_exact(&mut given_hash)
.map_err(|e| AdnlError::ReadError(e))?;
.await
.map_err(AdnlError::ReadError)?;
self.aes.apply_keystream(&mut given_hash);

let real_hash = hasher.finalize();
Expand Down
Loading
Loading