Skip to content

Commit

Permalink
Use tokio codecs for the protocol, use everscale-crypto for keys, add…
Browse files Browse the repository at this point in the history
… server implementation (#4)

* Add handshake decryption

* Add tests

* Huge refactoring

* migrate to codec

* Upd dalek

* add client & server codecs

* Add possibility to use multiple private keys for server

* Update Cargo.toml

* Update Cargo.toml

* Update Cargo.toml

* Move from complex traits to direct everscale-crypto usage

* Bump version

* fix doc
  • Loading branch information
hacker-volodya authored Aug 21, 2024
1 parent eb5a6fe commit c92fd6a
Show file tree
Hide file tree
Showing 19 changed files with 740 additions and 551 deletions.
27 changes: 14 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ repository = "https://github.com/tonstack/adnl-rs"
keywords = ["ton"]
categories = ["network-programming"]
license = "MIT"
version = "1.0.0"
version = "2.0.0"
authors = ["Vladimir Lebedev <[email protected]>"]
edition = "2021"

Expand All @@ -15,24 +15,25 @@ ctr = "0.9.1"
aes = "0.8.1"
log = "0.4.14"
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"]}
tokio = { version = "1", features = ["net", "io-util"] }
tokio-util = { version = "0.7.10", features = ["codec"] }
thiserror = "1"
rand = "0.8.5"
futures = "0.3"
pin-project = "1"
hex = "0.4.3"
everscale-crypto = "0.2.1"

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

[features]
default = ["dalek"]
dalek = ["x25519-dalek", "curve25519-dalek"]
base64 = "0.22.1"

[[example]]
name = "time"
required-features = ["dalek"]

[[example]]
name = "echo_client"

[[example]]
name = "echo_server"
41 changes: 14 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,44 +5,31 @@

Minimal client-server ADNL implementation in Rust. 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 |
| async | ✅ Implemented |
| ed25519 libs | curve25519_dalek + x25519_dalek |

## Quickstart
Run this example: `cargo run --example time`

```rust
use adnl::AdnlClient;
use anyhow::{anyhow, Context, Result};
use std::net::SocketAddrV4;

use adnl::AdnlPeer;
use base64::Engine as _;
use futures::{SinkExt, StreamExt};
use std::error::Error;

#[tokio::main]
async fn main() -> Result<()> {
async fn main() -> Result<(), Box<dyn Error>> {
// 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"))?;
let remote_public = base64::engine::general_purpose::STANDARD.decode("n4VDnSCUuSpjnCyUk9e3QOOd6o0ItSWYbTnW3Wnn8wk=")?;

let ls_ip = "65.21.74.140";
let ls_port = 46427;
// create AdnlClient
let mut client =
AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?;
// act as a client: connect to ADNL server and perform handshake
let mut client = AdnlPeer::connect(remote_public, "5.9.10.47:19949").await?;

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

// send over ADNL, use random nonce
client.send(&mut query).await?;
// send over ADNL
client.send(query.into()).await?;

// receive result into vector, use 8192 bytes buffer
let mut result = Vec::<u8>::new();
client.receive(&mut result).await?;
// receive result
let result = client.next().await.ok_or_else(|| "no result")??;

// get time from serialized TL answer
println!(
Expand Down
26 changes: 26 additions & 0 deletions examples/echo_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use adnl::AdnlPeer;
use futures::{SinkExt, StreamExt};
use std::{env, error::Error};

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());

let public_key_hex = env::args().nth(2).unwrap_or_else(|| {
"691a14528fb2911839649c489cb4cbec1f4aa126c244c0ea2ac294eb568a7037".to_string()
});

// act as a client: connect to ADNL server and perform handshake
let mut client = AdnlPeer::connect(hex::decode(public_key_hex)?, addr).await?;

// send over ADNL
client.send("hello".as_bytes().into()).await?;

// receive result
let result = client.next().await.ok_or("packet must be received")??;

println!("received: {}", String::from_utf8(result.to_vec())?);
Ok(())
}
67 changes: 67 additions & 0 deletions examples/echo_server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
//! Adopted from https://github.com/tokio-rs/tokio/blob/b32826bc937a34e4d871c89bb2c3711ed3e20cdc/examples/echo.rs

use std::{env, error::Error};

use adnl::crypto::{KeyPair, SecretKey};
use adnl::{AdnlAddress, AdnlPeer};
use futures::{SinkExt, StreamExt};
use tokio::net::TcpListener;

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
// Allow passing an address to listen on as the first argument of this
// program, but otherwise we'll just set up our TCP listener on
// 127.0.0.1:8080 for connections.
let addr = env::args()
.nth(1)
.unwrap_or_else(|| "127.0.0.1:8080".to_string());

// ADNL: get private key from environment variable KEY or use default insecure one
let private_key_hex = env::var("KEY").unwrap_or_else(|_| {
"f0971651aec4bb0d65ec3861c597687fda9c1e7d2ee8a93acb9a131aa9f3aee7".to_string()
});
let private_key_bytes: [u8; 32] = hex::decode(private_key_hex)?.try_into().unwrap();
let keypair = KeyPair::from(&SecretKey::from_bytes(private_key_bytes));

// Next up we create a TCP listener which will listen for incoming
// connections. This TCP listener is bound to the address we determined
// above and must be associated with an event loop.
let listener = TcpListener::bind(&addr).await?;
println!("Listening on: {}", addr);

// ADNL: print public key and adnl address associated with given private key
println!(
"Public key is: {}",
hex::encode(keypair.public_key.as_bytes())
);
println!(
"Address is: {}",
hex::encode(AdnlAddress::from(&keypair.public_key).as_bytes())
);

loop {
// Asynchronously wait for an inbound socket.
let (socket, _) = listener.accept().await?;

// And this is where much of the magic of this server happens. We
// crucially want all clients to make progress concurrently, rather than
// blocking one on completion of another. To achieve this we use the
// `tokio::spawn` function to execute the work in the background.
//
// Essentially here we're executing a new task to run concurrently,
// which will allow all of our clients to be processed concurrently.

let private_key = keypair.clone();
tokio::spawn(async move {
// ADNL: handle handshake
let mut adnl_server = AdnlPeer::handle_handshake(socket, |_| Some(private_key.clone()))
.await
.expect("handshake failed");

// In a loop, read data from the socket and write the data back.
while let Some(Ok(packet)) = adnl_server.next().await {
let _ = adnl_server.send(packet).await;
}
});
}
}
35 changes: 15 additions & 20 deletions examples/time.rs
Original file line number Diff line number Diff line change
@@ -1,30 +1,25 @@
use adnl::AdnlClient;
use anyhow::{anyhow, Context, Result};
use std::net::SocketAddrV4;
use adnl::AdnlPeer;
use base64::Engine as _;
use futures::{SinkExt, StreamExt};
use std::error::Error;

#[tokio::main]
async fn main() -> Result<()> {
async fn main() -> Result<(), Box<dyn Error>> {
// 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"))?;
let remote_public = base64::engine::general_purpose::STANDARD
.decode("n4VDnSCUuSpjnCyUk9e3QOOd6o0ItSWYbTnW3Wnn8wk=")?;

let ls_ip = "65.21.74.140";
let ls_port = 46427;
// create AdnlClient
let mut client =
AdnlClient::connect(remote_public, SocketAddrV4::new(ls_ip.parse()?, ls_port)).await?;
// act as a client: connect to ADNL server and perform handshake
let mut client = AdnlPeer::connect(remote_public, "5.9.10.47:19949").await?;

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

// send over ADNL, use random nonce
client.send(&mut query).await?;
// send over ADNL
client.send(query.into()).await?;

// receive result into vector, use 8192 bytes buffer
let mut result = Vec::<u8>::new();
client.receive(&mut result).await?;
// receive result
let result = client.next().await.ok_or_else(|| "no result")??;

// get time from serialized TL answer
println!(
Expand Down
Loading

0 comments on commit c92fd6a

Please sign in to comment.