Skip to content

Commit

Permalink
Async support (#3)
Browse files Browse the repository at this point in the history
* tokio support
* migrate errors to thiserror
* add convenience wrappers for easy quickstart
  • Loading branch information
navichok26 authored Feb 14, 2024
1 parent a6dd196 commit 1334bd9
Show file tree
Hide file tree
Showing 10 changed files with 184 additions and 190 deletions.
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

0 comments on commit 1334bd9

Please sign in to comment.