From d7b978569f586d3fb5d99cd4dcb0fd76063021be Mon Sep 17 00:00:00 2001 From: Lazy Panda Date: Sun, 15 Sep 2024 05:07:24 +0800 Subject: [PATCH] feat: turn rest api rfc support; (#98) --- .github/workflows/release.yml | 15 +- Cargo.lock | 2 + README.md | 2 +- docs/README.md | 2 +- docs/configure.md | 44 +- docs/http-hooks.md | 57 +++ docs/web-hooks.md | 57 --- stun/src/message.rs | 6 +- stun/src/util.rs | 4 +- tests/Cargo.toml | 1 + tests/benches/benchmark.rs | 41 +- tests/src/lib.rs | 543 ++++++++++++++---------- turn-server.toml | 17 +- turn-server/Cargo.toml | 1 + turn-server/src/config.rs | 37 +- turn-server/src/lib.rs | 16 +- turn-server/src/main.rs | 3 +- turn-server/src/observer.rs | 6 +- turn-server/src/{api.rs => publicly.rs} | 54 ++- turn-server/src/statistics.rs | 6 +- turn/src/router/nodes.rs | 4 +- turn/src/router/nonces.rs | 6 +- 22 files changed, 539 insertions(+), 385 deletions(-) create mode 100644 docs/http-hooks.md delete mode 100644 docs/web-hooks.md rename turn-server/src/{api.rs => publicly.rs} (87%) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 03fe974..807038e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@v4 - - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | ~/.cargo/bin/ @@ -70,7 +70,7 @@ jobs: - name: Upload artifact (Linux) if: runner.os == 'Linux' && matrix.arch == 'x86_64' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: linux-x86_64 path: | @@ -78,15 +78,15 @@ jobs: - name: Upload artifact (Linux) if: runner.os == 'Linux' && matrix.arch == 'aarch64' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: - name: linux-x86_64 + name: linux-aarch64 path: | ./target/aarch64-unknown-linux-gnu/release/turn-server-linux-aarch64 - name: Upload artifact (Windows) if: runner.os == 'Windows' && matrix.arch == 'x86_64' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: windows-x86_64 path: | @@ -95,12 +95,9 @@ jobs: needs: build runs-on: ubuntu-latest steps: - - - name: Checkout code - uses: actions/checkout@v3 - name: Download All Artifacts - uses: actions/download-artifact@v4.1.7 + uses: actions/download-artifact@v4 with: path: artifacts - diff --git a/Cargo.lock b/Cargo.lock index 1e855e8..e5b9fc6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1704,6 +1704,7 @@ dependencies = [ name = "tests" version = "0.1.0" dependencies = [ + "base64", "bytes", "criterion", "once_cell", @@ -1967,6 +1968,7 @@ dependencies = [ "anyhow", "async-trait", "axum", + "base64", "bytes", "clap", "log", diff --git a/README.md b/README.md index 8147247..407be9f 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ If you have extensive standard support requirements for turn servers and need mo * [start the server](./docs/start-the-server.md) * [configure](./docs/configure.md) * [rest api](./docs/rest-api.md) - * [web hooks](./docs/web-hooks.md) + * [http hooks](./docs/http-hooks.md) * [driver](./drivers) ## Features diff --git a/docs/README.md b/docs/README.md index 25334d1..f4719e8 100644 --- a/docs/README.md +++ b/docs/README.md @@ -5,4 +5,4 @@ * [Start the server](start-the-server.md) * [Configure](configure.md) * [REST API](rest-api.md) -* [Web Hooks](web-hooks.md) +* [HTTP Hooks](http-hooks.md) diff --git a/docs/configure.md b/docs/configure.md index 384f52e..853e705 100644 --- a/docs/configure.md +++ b/docs/configure.md @@ -45,7 +45,7 @@ external = "127.0.0.1:3478" # environment. bind = "127.0.0.1:3000" -# web hooks url +# hooks url # # This option is used to specify the http address of the hooks service. # @@ -62,6 +62,15 @@ bind = "127.0.0.1:3000" # An enum representing the available verbosity levels of the logger. level = "info" +[auth] +# Static authentication key value (string) that applies only to the TURN +# REST API. +# +# If set, the turn server will not request external services via the HTTP +# Hooks API to obtain the key. +# +# static_auth_secret = "" + # static user password # # This option can be used to specify the @@ -69,9 +78,9 @@ level = "info" # verification. Note: this is a high-priority authentication method, turn # The server will try to use static authentication first, and then use # external control service authentication. -[auth] -user1 = "test" -user2 = "test" +[auth.static_credentials] +# user1 = "test" +# user2 = "test" ``` ## Configuration keys @@ -80,7 +89,7 @@ user2 = "test" ### `turn.realm` -* Type: strings +* Type: string * Default: "localhost" This option describes the realm of the turn service. For the definition of realm, please refer to [RFC](https://datatracker.ietf.org/doc/html/rfc5766#section-3). @@ -98,7 +107,7 @@ This option describes the interface to which the turn service is bound. A turn s ### `[turn.interfaces.transport]` -* Type: enum of strings +* Type: enum of string Describes the transport protocol used by the interface. The value can be `udp` or `tcp`, which correspond to udp turn and tcp turn respectively, and choose whether to bind the turn service to a udp socket or a tcp socket. @@ -106,7 +115,7 @@ Describes the transport protocol used by the interface. The value can be `udp` o ### `[turn.interfaces.bind]` -* Type: strings +* Type: string The IP address and port number bound to the interface. This is the address to which the internal socket is bound. @@ -114,7 +123,7 @@ The IP address and port number bound to the interface. This is the address to wh ### `[turn.interfaces.external]` -* Type: strings +* Type: string bind is used to bind to the address of your local NIC, for example, you have two NICs A and B on your server, the IP address of NIC A is 192.168.1.2, and the address of NIC B is 192.168.1.3, if you bind to NIC A, you should bind to the address of 192.168.1.2, and bind to 0.0.0.0 means that it listens to all of them at the same time. @@ -126,7 +135,7 @@ As for why bind and external are needed, this is because for the stun protocol, ### `api.bind` -* Type: strings +* Type: string * Default: "127.0.0.1:3000" Describes the address to which the turn api server is bound. @@ -139,7 +148,7 @@ The turn service provides an external REST API. External parties can control the ### `api.hooks` -* Type: strings +* Type: string * Default: None Describes the address of external Web Hooks. The default value is empty. The purpose of Web Hooks is to allow the turn service to push to external services when authentication is required and event updates occur. @@ -152,15 +161,26 @@ The turn service provides an external REST API. External parties can control the ### `log.level` -* Type: enum of strings +* Type: enum of string * Default: "info" Describes the log level of the turn service. Possible values ​​are `"error"`, `"warn"`, `"info"`, `"debug"`, `"trace"`. *** -### `auth` +### `auth.static_credentials` * Type: key values Describes static authentication information, with username and password as key pair. Static identity authentication is authentication information provided to the turn service in advance. The turn service will first look for this table when it needs to authenticate the turn session. If it cannot find it, it will use Web Hooks for external authentication. + +*** + +### `auth.static_auth_secret` + +* Type: string +* Default: None + +Static authentication key value (string) that applies only to the TURN REST API. + +If set, the turn server will not request external services via the HTTP Hooks API to obtain the key. diff --git a/docs/http-hooks.md b/docs/http-hooks.md new file mode 100644 index 0000000..7075dc7 --- /dev/null +++ b/docs/http-hooks.md @@ -0,0 +1,57 @@ +# Web Hooks + +#### Global Request Headers + +* `realm` - string - turn server realm +* `rid` - string - The runtime ID of the turn server + +rid: A new ID is generated each time the server is started. This is a random string. Its main function is to determine whether the turn server has been restarted. + +*** + +### GET - `/password?addr=&name=` + +Get the current user's password, which is mainly used to provide authentication for the turn server. + +*** + +### POST - `/events` - Events + +binding request: + +* `kind` - string - "binding" +* `addr` - string - The IP address and port number of the UDP or TCP connection used by the client. + +allocate request: + +* `kind` - string - "allocated" +* `name` - string - The username used for the turn session. +* `addr` - string - The IP address and port number of the UDP or TCP connection used by the client. +* `port` - uint16 - The port to which the request is assigned. + +channel binding request: + +* `kind` - string - "channel_bind" +* `name` - string - The username used for the turn session. +* `addr` - string - The IP address and port number of the UDP or TCP connection used by the client. +* `channel` - uint16 - The channel to which the request is binding. + +create permission request: + +* `kind` - string - "create_permission" +* `name` - string - The username used for the turn session. +* `addr` - string - The IP address and port number of the UDP or TCP connection used by the client. +* `relay` - uint16 - The port number of the other side specified when the privilege was created. + +refresh request: + +* `kind` - string - "refresh" +* `name` - string - The username used for the turn session. +* `addr` - string - The IP address and port number of the UDP or TCP connection used by the client. +* `expiration` - uint32 - Time to expiration in seconds. + +session closed: + +* `kind` - string - "abort" +* `name` - string - The username used for the turn session. +* `addr` - string - The IP address and port number of the UDP or TCP connection used by the client. diff --git a/docs/web-hooks.md b/docs/web-hooks.md deleted file mode 100644 index 6ec3ea6..0000000 --- a/docs/web-hooks.md +++ /dev/null @@ -1,57 +0,0 @@ -# Web Hooks - -#### Global Request Headers - -* `realm` - string - turn server realm -* `rid` - string - The runtime ID of the turn server - -rid: A new ID is generated each time the server is started. This is a random string. Its main function is to determine whether the turn server has been restarted. - -*** - -### GET - `/password?addr=&name=` - -Get the current user's password, which is mainly used to provide authentication for the turn server. - -*** - -### POST - `/events` - Events - -binding request: - -* `kind` - string - "binding" -* `addr` - string - -allocate request: - -* `kind` - string - "allocated" -* `name` - string -* `addr` - string -* `port` - uint16 - -channel binding request: - -* `kind` - string - "channel_bind" -* `name` - string -* `addr` - string -* `channel` - uint16 - -create permission request: - -* `kind` - string - "create_permission" -* `name` - string -* `addr` - string -* `channel` - uint16 - -refresh request: - -* `kind` - string - "refresh" -* `name` - string -* `addr` - string -* `expiration` - uint32 - -session closed: - -* `kind` - string - "abort" -* `name` - string -* `addr` - string diff --git a/stun/src/message.rs b/stun/src/message.rs index 704668d..7dcda98 100644 --- a/stun/src/message.rs +++ b/stun/src/message.rs @@ -213,7 +213,7 @@ impl<'a, 'b> MessageWriter<'a> { // long key, // digest the message buffer, // create the new MessageIntegrity attribute. - let hmac_output = util::hmac_sha1(auth, vec![self.raw])?.into_bytes(); + let hmac_output = util::hmac_sha1(auth, &[self.raw])?.into_bytes(); let property_buf = hmac_output.as_slice(); // write MessageIntegrity attribute. @@ -325,14 +325,14 @@ impl<'a, 'b> MessageReader<'a, 'b> { // create multiple submit. let size_buf = (self.valid_offset + 4).to_be_bytes(); - let body = vec![ + let body = [ &self.buf[0..2], &size_buf, &self.buf[4..self.valid_offset as usize], ]; // digest the message buffer. - let hmac_output = util::hmac_sha1(auth, body)?.into_bytes(); + let hmac_output = util::hmac_sha1(auth, &body)?.into_bytes(); let property_buf = hmac_output.as_slice(); // Compare local and original attribute. diff --git a/stun/src/util.rs b/stun/src/util.rs index 73cc852..5f6b157 100644 --- a/stun/src/util.rs +++ b/stun/src/util.rs @@ -70,12 +70,12 @@ pub fn long_key(username: &str, key: &str, realm: &str) -> [u8; 16] { /// 0x74, 0xe2, 0x3c, 0x26, 0xc5, 0xb1, 0x03, 0xb2, 0x6d, /// ]; /// -/// let hmac_output = stun::util::hmac_sha1(&key, vec![&buffer]) +/// let hmac_output = stun::util::hmac_sha1(&key, &[&buffer]) /// .unwrap() /// .into_bytes(); /// assert_eq!(hmac_output.as_slice(), &sign); /// ``` -pub fn hmac_sha1(key: &[u8], source: Vec<&[u8]>) -> Result>, StunError> { +pub fn hmac_sha1(key: &[u8], source: &[&[u8]]) -> Result>, StunError> { match Hmac::::new_from_slice(key) { Err(_) => Err(StunError::ShaFailed), Ok(mut mac) => { diff --git a/tests/Cargo.toml b/tests/Cargo.toml index 6461b02..844e597 100644 --- a/tests/Cargo.toml +++ b/tests/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" publish = false [dependencies] +base64 = "0.22.1" once_cell = "1.18.0" tokio = { version = "1", features = ["full"] } stun = { path = "../stun", version = "1" } diff --git a/tests/benches/benchmark.rs b/tests/benches/benchmark.rs index 41c0355..be9cac1 100644 --- a/tests/benches/benchmark.rs +++ b/tests/benches/benchmark.rs @@ -1,43 +1,26 @@ use criterion::*; -use tests::{allocate_request, create_client, create_permission_request, create_turn, indication}; -use tokio::{net::UdpSocket, runtime::Runtime}; +use tests::{create_turn_server, AuthMethod, TurnClient}; -fn create_turn_block(rt: &Runtime) { - rt.block_on(async { create_turn().await }) -} - -fn create_client_block(rt: &Runtime) -> UdpSocket { - rt.block_on(async { create_client().await }) -} - -fn allocate_request_block(rt: &Runtime, socket: &UdpSocket) -> u16 { - rt.block_on(async { allocate_request(&socket).await }) -} +fn criterion_benchmark(c: &mut Criterion) { + let bind = "127.0.0.1:3578".parse().unwrap(); + create_turn_server(&AuthMethod::Static, bind); -fn create_permission_request_block(rt: &Runtime, socket: &UdpSocket, port: u16) { - rt.block_on(async { create_permission_request(&socket, port).await }) -} + let mut local = TurnClient::new(&AuthMethod::Static, bind); + let mut peer = TurnClient::new(&AuthMethod::Static, bind); -fn criterion_benchmark(c: &mut Criterion) { - let rt = Runtime::new().unwrap(); + let local_port = local.allocate_request(); + let peer_port = peer.allocate_request(); - create_turn_block(&rt); - let local = create_client_block(&rt); - let peer = create_client_block(&rt); - let local_port = allocate_request_block(&rt, &local); - let peer_port = allocate_request_block(&rt, &peer); - create_permission_request_block(&rt, &local, peer_port); - create_permission_request_block(&rt, &peer, local_port); + local.create_permission_request(peer_port); + peer.create_permission_request(local_port); let mut turn_relay = c.benchmark_group("turn_relay"); turn_relay.bench_function("send_indication_local_to_peer", |b| { - b.to_async(&rt) - .iter(|| indication(&local, &peer, peer_port)) + b.iter(|| local.indication(&peer, peer_port)) }); turn_relay.bench_function("send_indication_peer_to_local", |b| { - b.to_async(&rt) - .iter(|| indication(&peer, &local, local_port)) + b.iter(|| peer.indication(&local, local_port)) }); turn_relay.finish(); diff --git a/tests/src/lib.rs b/tests/src/lib.rs index c9cedf5..35f93e1 100644 --- a/tests/src/lib.rs +++ b/tests/src/lib.rs @@ -1,282 +1,391 @@ -#![allow(static_mut_refs)] - +use base64::prelude::*; use bytes::BytesMut; -use stun::attribute::{ - ChannelNumber, Data, ErrKind, ErrorCode, Lifetime, MappedAddress, Realm, ReqeestedTransport, - ResponseOrigin, Transport, UserName, XorMappedAddress, XorPeerAddress, XorRelayedAddress, +use once_cell::sync::Lazy; +use stun::{ + attribute::{ + ChannelNumber, Data, ErrKind, ErrorCode, Lifetime, MappedAddress, Realm, + ReqeestedTransport, ResponseOrigin, Transport, UserName, XorMappedAddress, XorPeerAddress, + XorRelayedAddress, + }, + Decoder, Kind, MessageReader, MessageWriter, Method, Payload, }; -use once_cell::sync::Lazy; use rand::seq::SliceRandom; -use stun::{Decoder, Kind, MessageReader, MessageWriter, Method, Payload}; -use tokio::net::UdpSocket; +use tokio::{net::UdpSocket, runtime::Runtime}; use turn_server::{ config::{self, *}, - server_main, + startup, }; -use std::collections::HashMap; -use std::net::{IpAddr, Ipv4Addr, SocketAddr}; -use std::sync::Arc; - -// global static var - -pub const BIND_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); -pub const BIND_ADDR: SocketAddr = SocketAddr::new(BIND_IP, 3478); -pub const USERNAME: &str = "user1"; -pub const PASSWORD: &str = "test"; -pub const REALM: &str = "localhost"; - -static mut RECV_BUF: [u8; 1500] = [0u8; 1500]; -static mut SEND_BUF: Lazy = Lazy::new(|| BytesMut::with_capacity(2048)); -static TOKEN_BUF: Lazy<[u8; 12]> = Lazy::new(|| { - let mut rng = rand::thread_rng(); - let mut token = [0u8; 12]; - token.shuffle(&mut rng); - token -}); - -static KEY_BUF: Lazy<[u8; 16]> = Lazy::new(|| stun::util::long_key(USERNAME, PASSWORD, REALM)); -static mut DECODER: Lazy = Lazy::new(Decoder::new); - -// global static var end - -fn get_message_from_payload<'a, 'b>(payload: Payload<'a, 'b>) -> MessageReader<'a, 'b> { - if let Payload::Message(m) = payload { - m - } else { - panic!("get message from payload failed!") - } +use std::{ + collections::HashMap, + net::{IpAddr, Ipv4Addr, SocketAddr}, + sync::Arc, + thread::sleep, + time::Duration, +}; + +static RUNTIME: Lazy = Lazy::new(|| Runtime::new().unwrap()); + +static BIND_IP: IpAddr = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); +static USERNAME: &str = "user1"; +static PASSWORD: &str = "test"; +static REALM: &str = "localhost"; + +#[derive(Debug, Clone)] +pub enum AuthMethod { + Static, + Secret(String), + Hooks(String), } -pub async fn create_turn() { - let mut auth = HashMap::new(); - auth.insert(USERNAME.to_string(), PASSWORD.to_string()); - - // Because it is testing, it is not reasonable to start a separate process - // to start turn-server, and the configuration file is not convenient to - // pass, so turn-server is used as a library here, and the server is - // started with a custom configuration. - tokio::spawn(async move { - server_main(Arc::new(Config { - auth, +pub fn create_turn_server(auth_method: &AuthMethod, bind: SocketAddr) { + let mut static_credentials = HashMap::new(); + let mut static_auth_secret = None; + let mut api = Api::default(); + + match auth_method { + AuthMethod::Static => { + static_credentials.insert(USERNAME.to_string(), PASSWORD.to_string()); + } + AuthMethod::Secret(secret) => { + static_auth_secret = Some(secret.clone()); + } + AuthMethod::Hooks(uri) => { + api.hooks = Some(uri.clone()); + } + }; + + RUNTIME.spawn(async move { + startup(Arc::new(Config { + auth: Auth { + static_credentials, + static_auth_secret, + }, api: Api::default(), log: Log::default(), turn: Turn { realm: REALM.to_string(), interfaces: vec![Interface { transport: config::Transport::UDP, - bind: BIND_ADDR, - external: BIND_ADDR, + external: bind, + bind, }], }, })) .await .unwrap(); }); + + sleep(Duration::from_secs(2)); } -// Create a udp connection and connect to the turn-server, and then start -// the corresponding session process checks in sequence. It should be noted -// that the order of request responses is relatively strict, and should not -// be changed under normal circumstances. -pub async fn create_client() -> UdpSocket { - let socket = UdpSocket::bind(SocketAddr::new(BIND_IP, 0)).await.unwrap(); - socket.connect(BIND_ADDR).await.unwrap(); - socket +pub struct TurnClient { + key_buf: [u8; 16], + decoder: Decoder, + client: UdpSocket, + token_buf: [u8; 12], + recv_buf: [u8; 1500], + send_buf: BytesMut, + bind_request_buf: BytesMut, + base_allocate_request_buf: BytesMut, + allocate_request_buf: BytesMut, + bind: SocketAddr, } -static BIND_REQUEST_BUF: Lazy = Lazy::new(|| { - let mut buf = BytesMut::with_capacity(1500); - let mut msg = MessageWriter::new(Method::Binding(Kind::Request), &TOKEN_BUF, &mut buf); +impl TurnClient { + pub fn new(auth_method: &AuthMethod, bind: SocketAddr) -> Self { + let client = RUNTIME + .block_on(UdpSocket::bind(SocketAddr::new(BIND_IP, 0))) + .unwrap(); + + RUNTIME.block_on(client.connect(bind)).unwrap(); + + let key = match &auth_method { + AuthMethod::Static => PASSWORD.to_string(), + AuthMethod::Secret(secret) => Self::encode_password(secret, USERNAME).unwrap(), + AuthMethod::Hooks(_) => PASSWORD.to_string(), + }; + + let key_buf = stun::util::long_key(USERNAME, &key, REALM); + + let token_buf = { + let mut rng = rand::thread_rng(); + let mut token = [0u8; 12]; + token.shuffle(&mut rng); + token + }; + + let bind_request_buf = { + let mut buf = BytesMut::with_capacity(1500); + let mut msg = MessageWriter::new(Method::Binding(Kind::Request), &token_buf, &mut buf); + + msg.flush(None).unwrap(); + buf + }; + + let base_allocate_request_buf = { + let mut buf = BytesMut::with_capacity(1500); + let mut msg = MessageWriter::new(Method::Allocate(Kind::Request), &token_buf, &mut buf); + + msg.append::(Transport::UDP); + msg.flush(None).unwrap(); + buf + }; + + let allocate_request_buf = { + let mut buf = BytesMut::with_capacity(1500); + let mut msg = MessageWriter::new(Method::Allocate(Kind::Request), &token_buf, &mut buf); + + msg.append::(Transport::UDP); + msg.append::(USERNAME); + msg.append::(REALM); + msg.flush(Some(&key_buf)).unwrap(); + buf + }; + + Self { + key_buf, + bind_request_buf, + base_allocate_request_buf, + allocate_request_buf, + send_buf: BytesMut::with_capacity(2048), + recv_buf: [0u8; 1500], + decoder: Decoder::new(), + token_buf, + client, + bind, + } + } - msg.flush(None).unwrap(); - buf -}); + pub fn binding_request(&mut self) { + RUNTIME + .block_on(self.client.send(&self.bind_request_buf)) + .unwrap(); -pub async fn binding_request(socket: &UdpSocket) { - socket.send(&BIND_REQUEST_BUF).await.unwrap(); - let size = socket.recv(unsafe { &mut RECV_BUF }).await.unwrap(); + let size = RUNTIME + .block_on(self.client.recv(&mut self.recv_buf)) + .unwrap(); - let decoder = unsafe { &mut DECODER }; - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); - assert_eq!(ret.method, Method::Binding(Kind::Response)); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); + assert_eq!(ret.method, Method::Binding(Kind::Response)); + assert_eq!(ret.token, self.token_buf.as_slice()); - let value = ret.get::().unwrap(); - assert_eq!(value, socket.local_addr().unwrap()); + let value = ret.get::().unwrap(); + assert_eq!(value, self.client.local_addr().unwrap()); - let value = ret.get::().unwrap(); - assert_eq!(value, socket.local_addr().unwrap()); + let value = ret.get::().unwrap(); + assert_eq!(value, self.client.local_addr().unwrap()); - let value = ret.get::().unwrap(); - assert_eq!(value, BIND_ADDR); -} + let value = ret.get::().unwrap(); + assert_eq!(value, self.bind); + } -static BASE_ALLOCATE_REQUEST_BUF: Lazy = Lazy::new(|| { - let mut buf = BytesMut::with_capacity(1500); - let mut msg = MessageWriter::new(Method::Allocate(Kind::Request), &TOKEN_BUF, &mut buf); + pub fn base_allocate_request(&mut self) { + RUNTIME + .block_on(self.client.send(&self.base_allocate_request_buf)) + .unwrap(); - msg.append::(Transport::UDP); - msg.flush(None).unwrap(); - buf -}); + let size = RUNTIME + .block_on(self.client.recv(&mut self.recv_buf)) + .unwrap(); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); -pub async fn base_allocate_request(socket: &UdpSocket) { - socket.send(&BASE_ALLOCATE_REQUEST_BUF).await.unwrap(); + assert_eq!(ret.method, Method::Allocate(Kind::Error)); + assert_eq!(ret.token, self.token_buf.as_slice()); - let decoder = unsafe { &mut DECODER }; - let size = socket.recv(unsafe { &mut RECV_BUF }).await.unwrap(); - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); + let value = ret.get::().unwrap(); + assert_eq!(value.code, ErrKind::Unauthorized as u16); - assert_eq!(ret.method, Method::Allocate(Kind::Error)); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); + let value = ret.get::().unwrap(); + assert_eq!(value, REALM); + } - let value = ret.get::().unwrap(); - assert_eq!(value.code, ErrKind::Unauthorized as u16); + pub fn allocate_request(&mut self) -> u16 { + RUNTIME + .block_on(self.client.send(&self.allocate_request_buf)) + .unwrap(); - let value = ret.get::().unwrap(); - assert_eq!(value, REALM); -} + let size = RUNTIME + .block_on(self.client.recv(&mut self.recv_buf)) + .unwrap(); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); -static ALLOCATE_REQUEST_BUF: Lazy = Lazy::new(|| { - let mut buf = BytesMut::with_capacity(1500); - let mut msg = MessageWriter::new(Method::Allocate(Kind::Request), &TOKEN_BUF, &mut buf); + assert_eq!(ret.method, Method::Allocate(Kind::Response)); + assert_eq!(ret.token, self.token_buf.as_slice()); + ret.integrity(&self.key_buf).unwrap(); - msg.append::(Transport::UDP); - msg.append::(USERNAME); - msg.append::(REALM); - msg.flush(Some(&KEY_BUF)).unwrap(); - buf -}); + let relay = ret.get::().unwrap(); + assert_eq!(relay.ip(), BIND_IP); -pub async fn allocate_request(socket: &UdpSocket) -> u16 { - socket.send(&ALLOCATE_REQUEST_BUF).await.unwrap(); + let value = ret.get::().unwrap(); + assert_eq!(value, self.client.local_addr().unwrap()); - let decoder = unsafe { &mut DECODER }; - let size = socket.recv(unsafe { &mut RECV_BUF }).await.unwrap(); - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); + let value = ret.get::().unwrap(); + assert_eq!(value, 600); - assert_eq!(ret.method, Method::Allocate(Kind::Response)); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); - ret.integrity(&KEY_BUF).unwrap(); + relay.port() + } - let relay = ret.get::().unwrap(); - assert_eq!(relay.ip(), BIND_IP); + pub fn create_permission_request(&mut self, port: u16) { + let mut msg = MessageWriter::new( + Method::CreatePermission(Kind::Request), + &self.token_buf, + &mut self.send_buf, + ); + + msg.append::(SocketAddr::new(BIND_IP, port)); + msg.append::(USERNAME); + msg.append::(REALM); + msg.flush(Some(&self.key_buf)).unwrap(); + RUNTIME.block_on(self.client.send(&self.send_buf)).unwrap(); + + let size = RUNTIME + .block_on(self.client.recv(&mut self.recv_buf)) + .unwrap(); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); + + assert_eq!(ret.method, Method::CreatePermission(Kind::Response)); + assert_eq!(ret.token, self.token_buf.as_slice()); + ret.integrity(&self.key_buf).unwrap(); + } - let value = ret.get::().unwrap(); - assert_eq!(value, socket.local_addr().unwrap()); + pub fn channel_bind_request(&mut self, port: u16) { + let mut msg = MessageWriter::new( + Method::ChannelBind(Kind::Request), + &self.token_buf, + &mut self.send_buf, + ); + + msg.append::(0x4000); + msg.append::(SocketAddr::new(BIND_IP, port)); + msg.append::(USERNAME); + msg.append::(REALM); + msg.flush(Some(&self.key_buf)).unwrap(); + RUNTIME.block_on(self.client.send(&self.send_buf)).unwrap(); + + let size = RUNTIME + .block_on(self.client.recv(&mut self.recv_buf)) + .unwrap(); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); + + assert_eq!(ret.method, Method::ChannelBind(Kind::Response)); + assert_eq!(ret.token, self.token_buf.as_slice()); + ret.integrity(&self.key_buf).unwrap(); + } - let value = ret.get::().unwrap(); - assert_eq!(value, 600); + pub fn refresh_request(&mut self) { + let mut msg = MessageWriter::new( + Method::Refresh(Kind::Request), + &self.token_buf, + &mut self.send_buf, + ); + + msg.append::(0); + msg.append::(USERNAME); + msg.append::(REALM); + msg.flush(Some(&self.key_buf)).unwrap(); + RUNTIME.block_on(self.client.send(&self.send_buf)).unwrap(); + + let size = RUNTIME + .block_on(self.client.recv(&mut self.recv_buf)) + .unwrap(); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); + + assert_eq!(ret.method, Method::Refresh(Kind::Response)); + assert_eq!(ret.token, self.token_buf.as_slice()); + ret.integrity(&self.key_buf).unwrap(); + + let value = ret.get::().unwrap(); + assert_eq!(value, 0); + } - relay.port() -} + pub fn indication(&mut self, peer: &Self, port: u16) { + let mut msg = + MessageWriter::new(Method::SendIndication, &self.token_buf, &mut self.send_buf); -pub async fn create_permission_request(socket: &UdpSocket, port: u16) { - let mut msg = MessageWriter::new( - Method::CreatePermission(Kind::Request), - &TOKEN_BUF, - unsafe { &mut SEND_BUF }, - ); - - msg.append::(SocketAddr::new(BIND_IP, port)); - msg.append::(USERNAME); - msg.append::(REALM); - msg.flush(Some(&KEY_BUF)).unwrap(); - socket.send(unsafe { &SEND_BUF }).await.unwrap(); - - let decoder = unsafe { &mut DECODER }; - let size = socket.recv(unsafe { &mut RECV_BUF }).await.unwrap(); - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); - - assert_eq!(ret.method, Method::CreatePermission(Kind::Response)); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); - ret.integrity(&KEY_BUF).unwrap(); -} + msg.append::(SocketAddr::new(BIND_IP, port)); + msg.append::(self.token_buf.as_slice()); + msg.flush(None).unwrap(); + RUNTIME.block_on(self.client.send(&self.send_buf)).unwrap(); -pub async fn channel_bind_request(socket: &UdpSocket, port: u16) { - let mut msg = MessageWriter::new(Method::ChannelBind(Kind::Request), &TOKEN_BUF, unsafe { - &mut SEND_BUF - }); + let size = RUNTIME + .block_on(peer.client.recv(&mut self.recv_buf)) + .unwrap(); + let ret = self.decoder.decode(&mut self.recv_buf[..size]).unwrap(); + let ret = Self::get_message_from_payload(ret); - msg.append::(0x4000); - msg.append::(SocketAddr::new(BIND_IP, port)); - msg.append::(USERNAME); - msg.append::(REALM); - msg.flush(Some(&KEY_BUF)).unwrap(); - socket.send(unsafe { &SEND_BUF }).await.unwrap(); - - let decoder = unsafe { &mut DECODER }; - let size = socket.recv(unsafe { &mut RECV_BUF }).await.unwrap(); - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); - - assert_eq!(ret.method, Method::ChannelBind(Kind::Response)); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); - ret.integrity(&KEY_BUF).unwrap(); -} + assert_eq!(ret.method, Method::DataIndication); + assert_eq!(ret.token, self.token_buf.as_slice()); -pub async fn refresh_request(socket: &UdpSocket) { - let mut msg = MessageWriter::new(Method::Refresh(Kind::Request), &TOKEN_BUF, unsafe { - &mut SEND_BUF - }); + let value = ret.get::().unwrap(); + assert_eq!(value, self.token_buf.as_slice()); + } + + fn get_message_from_payload<'a, 'b>(payload: Payload<'a, 'b>) -> MessageReader<'a, 'b> { + if let Payload::Message(m) = payload { + m + } else { + panic!("get message from payload failed!") + } + } + + fn encode_password(key: &str, username: &str) -> Option { + Some( + BASE64_STANDARD.encode( + stun::util::hmac_sha1(key.as_bytes(), &[username.as_bytes()]) + .ok()? + .into_bytes() + .as_slice(), + ), + ) + } +} - msg.append::(0); - msg.append::(USERNAME); - msg.append::(REALM); - msg.flush(Some(&KEY_BUF)).unwrap(); - socket.send(unsafe { &SEND_BUF }).await.unwrap(); +#[cfg(test)] +mod tests { + use std::net::SocketAddr; - let decoder = unsafe { &mut DECODER }; - let size = socket.recv(unsafe { &mut RECV_BUF }).await.unwrap(); - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); + use crate::{create_turn_server, AuthMethod, TurnClient, BIND_IP, PASSWORD}; - assert_eq!(ret.method, Method::Refresh(Kind::Response)); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); - ret.integrity(&KEY_BUF).unwrap(); + #[test] + fn static_auth_testing() { + let bind = SocketAddr::new(BIND_IP, 4478); + let auth = AuthMethod::Static; - let value = ret.get::().unwrap(); - assert_eq!(value, 0); -} + create_turn_server(&auth, bind); -pub async fn indication(local: &UdpSocket, peer: &UdpSocket, port: u16) { - let mut msg = MessageWriter::new(Method::SendIndication, &TOKEN_BUF, unsafe { &mut SEND_BUF }); + let mut local = TurnClient::new(&auth, bind); + local.binding_request(); + local.base_allocate_request(); - msg.append::(SocketAddr::new(BIND_IP, port)); - msg.append::(TOKEN_BUF.as_slice()); - msg.flush(None).unwrap(); - local.send(unsafe { &SEND_BUF }).await.unwrap(); + let port = local.allocate_request(); + local.create_permission_request(port); + local.channel_bind_request(port); + local.refresh_request(); + } - let decoder = unsafe { &mut DECODER }; - let size = peer.recv(unsafe { &mut RECV_BUF }).await.unwrap(); - let ret = decoder.decode(unsafe { &RECV_BUF[..size] }).unwrap(); - let ret = get_message_from_payload(ret); + #[test] + fn turn_rest_testing() { + let bind = SocketAddr::new(BIND_IP, 4479); + let auth = AuthMethod::Secret(PASSWORD.to_string()); - assert_eq!(ret.method, Method::DataIndication); - assert_eq!(ret.token, TOKEN_BUF.as_slice()); + create_turn_server(&auth, bind); - let value = ret.get::().unwrap(); - assert_eq!(value, TOKEN_BUF.as_slice()); -} + let mut local = TurnClient::new(&auth, bind); + local.binding_request(); + local.base_allocate_request(); -#[cfg(test)] -mod tests { - #[tokio::test] - async fn integration_testing() { - crate::create_turn().await; - let socket = crate::create_client().await; - crate::binding_request(&socket).await; - crate::base_allocate_request(&socket).await; - let port = crate::allocate_request(&socket).await; - crate::create_permission_request(&socket, port).await; - crate::channel_bind_request(&socket, port).await; - crate::refresh_request(&socket).await; + let port = local.allocate_request(); + local.create_permission_request(port); + local.channel_bind_request(port); + local.refresh_request(); } } diff --git a/turn-server.toml b/turn-server.toml index e96fa1b..fb26df6 100644 --- a/turn-server.toml +++ b/turn-server.toml @@ -40,7 +40,7 @@ external = "127.0.0.1:3478" # environment. bind = "127.0.0.1:3000" -# web hooks url +# hooks url # # This option is used to specify the http address of the hooks service. # @@ -57,6 +57,15 @@ bind = "127.0.0.1:3000" # An enum representing the available verbosity levels of the logger. level = "info" +[auth] +# Static authentication key value (string) that applies only to the TURN +# REST API. +# +# If set, the turn server will not request external services via the HTTP +# Hooks API to obtain the key. +# +# static_auth_secret = "" + # static user password # # This option can be used to specify the @@ -64,6 +73,6 @@ level = "info" # verification. Note: this is a high-priority authentication method, turn # The server will try to use static authentication first, and then use # external control service authentication. -[auth] -user1 = "test" -user2 = "test" +[auth.static_credentials] +# user1 = "test" +# user2 = "test" diff --git a/turn-server/Cargo.toml b/turn-server/Cargo.toml index 408423d..b34b9fd 100644 --- a/turn-server/Cargo.toml +++ b/turn-server/Cargo.toml @@ -25,6 +25,7 @@ ahash = "0.8.3" async-trait = "0.1" anyhow = "1.0" axum = "0.7.5" +base64 = "0.22.1" bytes = "1.4.0" clap = { version = "4", features = ["derive"] } log = "0.4" diff --git a/turn-server/src/config.rs b/turn-server/src/config.rs index 3053536..87dcf4b 100644 --- a/turn-server/src/config.rs +++ b/turn-server/src/config.rs @@ -72,7 +72,7 @@ impl Default for Turn { #[derive(Deserialize, Debug)] pub struct Api { - /// Api bind + /// api bind /// /// This option specifies the http server binding address used to control /// the turn server. @@ -98,17 +98,13 @@ impl Api { fn bind() -> SocketAddr { "127.0.0.1:3000".parse().unwrap() } - - fn hooks() -> Option { - None - } } impl Default for Api { fn default() -> Self { Self { + hooks: None, bind: Self::bind(), - hooks: Self::hooks(), } } } @@ -150,6 +146,25 @@ pub struct Log { pub level: LogLevel, } +#[derive(Deserialize, Debug, Default)] +pub struct Auth { + /// static user password + /// + /// This option can be used to specify the + /// static identity authentication information used by the turn server for + /// verification. Note: this is a high-priority authentication method, turn + /// The server will try to use static authentication first, and then use + /// external control service authentication. + #[serde(default)] + pub static_credentials: HashMap, + /// Static authentication key value (string) that applies only to the TURN + /// REST API. + /// + /// If set, the turn server will not request external services via the HTTP + /// Hooks API to obtain the key. + pub static_auth_secret: Option, +} + #[derive(Deserialize, Debug)] pub struct Config { #[serde(default)] @@ -158,16 +173,8 @@ pub struct Config { pub api: Api, #[serde(default)] pub log: Log, - - /// static user password - /// - /// This option can be used to specify the - /// static identity authentication information used by the turn server for - /// verification. Note: this is a high-priority authentication method, turn - /// The server will try to use static authentication first, and then use - /// external control service authentication. #[serde(default)] - pub auth: HashMap, + pub auth: Auth, } #[derive(Parser)] diff --git a/turn-server/src/lib.rs b/turn-server/src/lib.rs index 3e3a4af..3f69025 100644 --- a/turn-server/src/lib.rs +++ b/turn-server/src/lib.rs @@ -1,6 +1,6 @@ -pub mod api; pub mod config; pub mod observer; +pub mod publicly; pub mod router; pub mod server; pub mod statistics; @@ -14,12 +14,16 @@ use self::{config::Config, observer::Observer, statistics::Statistics}; /// In order to let the integration test directly use the turn-server crate and /// start the server, a function is opened to replace the main function to /// directly start the server. -pub async fn server_main(config: Arc) -> anyhow::Result<()> { +pub async fn startup(config: Arc) -> anyhow::Result<()> { let statistics = Statistics::default(); - let observer = Observer::new(config.clone(), statistics.clone()).await?; - let externals = config.turn.get_externals(); - let service = Service::new(config.turn.realm.clone(), externals, observer); + let service = Service::new( + config.turn.realm.clone(), + config.turn.get_externals(), + Observer::new(config.clone(), statistics.clone()).await?, + ); + server::run(config.clone(), statistics.clone(), &service).await?; - api::start_server(config, service, statistics).await?; + publicly::start_server(config, service, statistics).await?; + Ok(()) } diff --git a/turn-server/src/main.rs b/turn-server/src/main.rs index 13a1404..861c098 100644 --- a/turn-server/src/main.rs +++ b/turn-server/src/main.rs @@ -2,11 +2,12 @@ static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; use std::sync::Arc; + use turn_server::config::Config; #[tokio::main] async fn main() -> anyhow::Result<()> { let config = Arc::new(Config::load()?); simple_logger::init_with_level(config.log.level.as_level())?; - turn_server::server_main(config).await + turn_server::startup(config).await } diff --git a/turn-server/src/observer.rs b/turn-server/src/observer.rs index 69ea9a0..9e71756 100644 --- a/turn-server/src/observer.rs +++ b/turn-server/src/observer.rs @@ -1,6 +1,6 @@ use std::{net::SocketAddr, sync::Arc}; -use crate::{api::HooksService, config::Config, statistics::Statistics}; +use crate::{config::Config, publicly::HooksService, statistics::Statistics}; use anyhow::Result; use async_trait::async_trait; @@ -12,9 +12,9 @@ pub struct Observer { } impl Observer { - pub async fn new(cfg: Arc, statistics: Statistics) -> Result { + pub async fn new(config: Arc, statistics: Statistics) -> Result { Ok(Self { - hooks: HooksService::new(cfg)?, + hooks: HooksService::new(config)?, statistics, }) } diff --git a/turn-server/src/api.rs b/turn-server/src/publicly.rs similarity index 87% rename from turn-server/src/api.rs rename to turn-server/src/publicly.rs index dc30d59..b5566ce 100644 --- a/turn-server/src/api.rs +++ b/turn-server/src/publicly.rs @@ -1,3 +1,4 @@ +use core::str; use std::{ net::SocketAddr, sync::Arc, @@ -15,6 +16,7 @@ use axum::{ Json, Router, }; +use base64::prelude::*; use once_cell::sync::Lazy; use rand::{distributions::Alphanumeric, thread_rng, Rng}; use reqwest::{ @@ -31,14 +33,7 @@ use tokio::{ use turn::Service; -static RID: Lazy = Lazy::new(|| { - let mut rng = thread_rng(); - std::iter::repeat(()) - .map(|_| rng.sample(Alphanumeric) as char) - .take(16) - .collect::() - .to_lowercase() -}); +static RID: Lazy = Lazy::new(|| random_string(16)); struct AppState { config: Arc, @@ -189,13 +184,13 @@ pub async fn start_server( pub struct HooksService { client: Arc, tx: UnboundedSender, - cfg: Arc, + config: Arc, } impl HooksService { - pub fn new(cfg: Arc) -> anyhow::Result { + pub fn new(config: Arc) -> anyhow::Result { let mut headers = HeaderMap::new(); - headers.insert("Realm", HeaderValue::from_str(&cfg.turn.realm)?); + headers.insert("Realm", HeaderValue::from_str(&config.turn.realm)?); headers.insert("Rid", HeaderValue::from_str(&RID)?); let client = Arc::new( @@ -205,11 +200,11 @@ impl HooksService { .build()?, ); - let cfg_ = cfg.clone(); + let config_ = config.clone(); let client_ = client.clone(); let (tx, mut rx) = unbounded_channel::(); tokio::spawn(async move { - if let Some(server) = &cfg_.api.hooks { + if let Some(server) = &config_.api.hooks { let uri = format!("{}/events", server); while let Some(signal) = rx.recv().await { @@ -220,15 +215,19 @@ impl HooksService { } }); - Ok(Self { client, cfg, tx }) + Ok(Self { client, config, tx }) } pub async fn get_password(&self, addr: &SocketAddr, name: &str) -> Option { - if let Some(pwd) = self.cfg.auth.get(name) { + if let Some(pwd) = self.config.auth.static_credentials.get(name) { return Some(pwd.clone()); } - if let Some(server) = &self.cfg.api.hooks { + if let Some(secret) = &self.config.auth.static_auth_secret { + return encode_password(secret, name); + } + + if let Some(server) = &self.config.api.hooks { if let Ok(res) = self .client .get(format!("{}/password?addr={}&name={}", server, addr, name)) @@ -245,10 +244,31 @@ impl HooksService { } pub fn send_event(&self, event: Value) { - if self.cfg.api.hooks.is_some() { + if self.config.api.hooks.is_some() { if let Err(e) = self.tx.send(event) { log::error!("failed to send event, err={}", e) } } } } + +fn random_string(len: usize) -> String { + let mut rng = thread_rng(); + std::iter::repeat(()) + .map(|_| rng.sample(Alphanumeric) as char) + .take(len) + .collect::() + .to_lowercase() +} + +// https://datatracker.ietf.org/doc/html/draft-uberti-behave-turn-rest-00#section-2.2 +fn encode_password(key: &str, username: &str) -> Option { + Some( + BASE64_STANDARD.encode( + stun::util::hmac_sha1(key.as_bytes(), &[username.as_bytes()]) + .ok()? + .into_bytes() + .as_slice(), + ), + ) +} diff --git a/turn-server/src/statistics.rs b/turn-server/src/statistics.rs index 2f36022..7d0e8a3 100644 --- a/turn-server/src/statistics.rs +++ b/turn-server/src/statistics.rs @@ -4,11 +4,11 @@ use std::{ atomic::{AtomicUsize, Ordering}, Arc, RwLock, }, + thread::{self, sleep}, time::Duration, }; use ahash::AHashMap; -use tokio::time::sleep; #[derive(Debug, Clone, Copy)] pub struct NodeCounts { @@ -79,10 +79,10 @@ impl Default for Statistics { fn default() -> Self { let map: Arc>> = Default::default(); let map_ = Arc::downgrade(&map); - tokio::spawn(async move { + thread::spawn(move || { while let Some(map) = map_.upgrade() { let _ = map.read().unwrap().iter().for_each(|(_, it)| it.clear()); - sleep(Duration::from_secs(1)).await; + sleep(Duration::from_secs(1)); } }); diff --git a/turn/src/router/nodes.rs b/turn/src/router/nodes.rs index 26ae37e..49998e1 100644 --- a/turn/src/router/nodes.rs +++ b/turn/src/router/nodes.rs @@ -269,7 +269,7 @@ impl Nodes { password: &str, ) -> Option> { let node = Node::new(realm, username, password); - let pwd = node.get_secret(); + let secret = node.get_secret(); let mut addrs = self.addrs.write().unwrap(); self.map.write().unwrap().insert(*addr, node); @@ -277,7 +277,7 @@ impl Nodes { .entry(username.to_string()) .or_insert_with(|| AHashSet::with_capacity(5)) .insert(*addr); - Some(pwd) + Some(secret) } /// push port to node. diff --git a/turn/src/router/nonces.rs b/turn/src/router/nonces.rs index 36e7f1b..c0cb0f6 100644 --- a/turn/src/router/nonces.rs +++ b/turn/src/router/nonces.rs @@ -22,7 +22,7 @@ use rand::{distributions::Alphanumeric, thread_rng, Rng}; /// for guidance on selection of nonce values in a server. pub struct Nonce { raw: Arc, - timer: Instant, + lifetime: Instant, } impl Default for Nonce { @@ -35,7 +35,7 @@ impl Nonce { pub fn new() -> Self { Self { raw: Arc::new(Self::create_nonce()), - timer: Instant::now(), + lifetime: Instant::now(), } } @@ -50,7 +50,7 @@ impl Nonce { /// assert!(!nonce.is_death()); /// ``` pub fn is_death(&self) -> bool { - self.timer.elapsed().as_secs() >= 3600 + self.lifetime.elapsed().as_secs() >= 3600 } /// unwind nonce random string.