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

Use tokio codecs for the protocol, use everscale-crypto for keys, add server implementation #4

Merged
merged 13 commits into from
Aug 21, 2024
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
Loading