diff --git a/Cargo.toml b/Cargo.toml index d12d29562..7a9ff16d7 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,23 @@ bytes = "^1.1" coap-lite = "0.11.3" lru_time_cache = "0.11.11" mio = "=0.8.8" # fix windows broken, remove it after mio updated +async-trait = "0.1.74" + +# dependencies for dtls +webrtc-dtls = {version = "0.8.0", optional = true} +webrtc-util = {version = "0.8.0", optional = true} +rustls = {version = "^0.21.1", optional = true} +rustls-pemfile = {version = "2.0.0", optional = true} +rcgen = {version = "^0.11.0", optional = true} +pkcs8 = {version = "0.10.2", optional = true} +sec1 = { version = "0.7.3", features = ["pem", "pkcs8", "std"], optional = true} + +[features] +default = ["dtls"] +dtls = ["dep:webrtc-dtls", "dep:webrtc-util", "dep:rustls", "dep:rustls-pemfile", "dep:rcgen", "dep:pkcs8", "dep:sec1"] + [dev-dependencies] quickcheck = "0.8.2" + + diff --git a/README.md b/README.md index 51c3068d1..a1f81d028 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,8 @@ Features: - CoAP Observe option [RFC 7641](https://tools.ietf.org/rfc/rfc7641.txt) - *Too Many Requests* Response Code [RFC 8516](https://tools.ietf.org/html/rfc8516) - Block-Wise Transfers [RFC 7959](https://tools.ietf.org/html/rfc7959) +- DTLS support via [webrtc-rs](https://github.com/webrtc-rs/webrtc) +- Option to provide custom transports for client and server [Documentation](https://docs.rs/coap/) @@ -31,48 +33,50 @@ tokio = { version = "1", features = ["full"] } ### Server: ```rust -use coap_lite::{RequestType as Method}; -use coap::Server; -use tokio::runtime::Runtime; + use coap_lite::{RequestType as Method, CoapRequest}; + use coap::Server; + use tokio::runtime::Runtime; + use std::net::SocketAddr; + fn main() { + let addr = "127.0.0.1:5683"; + Runtime::new().unwrap().block_on(async move { + let mut server = Server::new_udp(addr).unwrap(); + println!("Server up on {}", addr); -fn main() { - let addr = "127.0.0.1:5683"; + server.run(|mut request: Box>| async { + match request.get_method() { + &Method::Get => println!("request by get {}", request.get_path()), + &Method::Post => println!("request by post {}", String::from_utf8(request.message.payload.clone()).unwrap()), + &Method::Put => println!("request by put {}", String::from_utf8(request.message.payload.clone()).unwrap()), + _ => println!("request by other method"), + }; - Runtime::new().unwrap().block_on(async move { - let mut server = Server::new(addr).unwrap(); - println!("Server up on {}", addr); - - server.run(|request| async { - match request.get_method() { - &Method::Get => println!("request by get {}", request.get_path()), - &Method::Post => println!("request by post {}", String::from_utf8(request.message.payload).unwrap()), - &Method::Put => println!("request by put {}", String::from_utf8(request.message.payload).unwrap()), - _ => println!("request by other method"), - }; - - return match request.response { - Some(mut message) => { - message.message.payload = b"OK".to_vec(); - Some(message) - }, - _ => None - }; - }).await.unwrap(); - }); -} + match request.response { + Some(ref mut message) => { + message.message.payload = b"OK".to_vec(); + + }, + _ => {} + }; + return request + }).await.unwrap(); + }); + } ``` ### Client: ```rust -use coap::CoAPClient; - -fn main() { - let url = "coap://127.0.0.1:5683/Rust"; - println!("Client request: {}", url); + use coap_lite::{RequestType as Method, CoapRequest}; + use coap::{UdpCoAPClient}; + use tokio::main; + #[tokio::main] + async fn main() { + let url = "coap://127.0.0.1:5683/Rust"; + println!("Client request: {}", url); - let response = CoAPClient::get(url).unwrap(); - println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap()); -} + let response = UdpCoAPClient::get(url).await.unwrap(); + println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap()); + } ``` ## Benchmark diff --git a/appveyor.yml b/appveyor.yml index 7f6b7217a..83ff1478e 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,6 +1,6 @@ install: - appveyor DownloadFile https://win.rustup.rs/ -FileName rustup-init.exe - - rustup-init -yv --default-toolchain nightly --default-host x86_64-pc-windows-gnu + - rustup-init -yv --default-toolchain nightly --default-host x86_64-pc-windows-msvc - set PATH=%PATH%;%USERPROFILE%\.cargo\bin - rustc -vV - cargo -vV diff --git a/benches/server.rs b/benches/server.rs index 07b047263..305fdd9de 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -1,41 +1,47 @@ #![feature(test, async_closure)] - extern crate test; -use coap::{CoAPClient, Server}; +use std::net::SocketAddr; + +use coap::{server::UdpCoapListener, Server, UdpCoAPClient}; use coap_lite::{CoapOption, CoapRequest, MessageType}; -use std::{sync::mpsc, thread}; -use tokio::runtime::Runtime; +use tokio::{net::UdpSocket, runtime::Runtime}; #[bench] fn bench_server_with_request(b: &mut test::Bencher) { - let (tx, rx) = mpsc::channel(); - - thread::spawn(move || { - Runtime::new().unwrap().block_on(async move { - let mut server = Server::new("127.0.0.1:0").unwrap(); - - tx.send(server.socket_addr().unwrap().port()).unwrap(); - - server - .run(async move |request| { - let uri_path = request.get_path().to_string(); - - return match request.response { - Some(mut response) => { - response.message.payload = uri_path.as_bytes().to_vec(); - Some(response) - } - _ => None, - }; - }) - .await - .unwrap(); - }); + let (tx, mut rx) = tokio::sync::mpsc::unbounded_channel(); + + let rt = Runtime::new().unwrap(); + rt.spawn(async move { + let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + let addr = sock.local_addr().unwrap(); + let listener = Box::new(UdpCoapListener::from_socket(sock)); + let server = Server::from_listeners(vec![listener]); + + tx.send(addr.port()).unwrap(); + + server + .run(move |mut request: Box>| async { + let uri_path = request.get_path().to_string(); + + match request.response { + Some(ref mut response) => { + response.message.payload = uri_path.as_bytes().to_vec(); + } + _ => {} + }; + return request; + }) + .await + .unwrap(); }); - let server_port = rx.recv().unwrap(); - let client = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); + let server_port = rx.blocking_recv().unwrap(); + let client = rt.block_on(async { + UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap() + }); let mut request = CoapRequest::new(); request.message.header.set_version(1); @@ -48,8 +54,10 @@ fn bench_server_with_request(b: &mut test::Bencher) { .add_option(CoapOption::UriPath, "test".to_string().into_bytes()); b.iter(|| { - client.send(&request).unwrap(); - let recv_packet = client.receive().unwrap(); - assert_eq!(recv_packet.message.payload, b"test".to_vec()); + rt.block_on(async { + client.send(&request).await.unwrap(); + let recv_packet = client.receive().await.unwrap(); + assert_eq!(recv_packet.message.payload, b"test".to_vec()); + }); }); } diff --git a/examples/client.rs b/examples/client.rs index df84b7049..8860e58ac 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -1,31 +1,33 @@ extern crate coap; -use coap::CoAPClient; +use coap::client::ObserveMessage; +use coap::UdpCoAPClient; use std::io; use std::io::ErrorKind; -fn main() { +#[tokio::main] +async fn main() { println!("GET url:"); - example_get(); + example_get().await; println!("POST data to url:"); - example_post(); + example_post().await; println!("PUT data to url:"); - example_put(); + example_put().await; println!("DELETE url:"); - example_delete(); + example_delete().await; println!("Observing:"); - example_observe(); + example_observe().await; } -fn example_get() { +async fn example_get() { let url = "coap://127.0.0.1:5683/hello/get"; println!("Client request: {}", url); - match CoAPClient::get(url) { + match UdpCoAPClient::get(url).await { Ok(response) => { println!( "Server reply: {}", @@ -42,12 +44,12 @@ fn example_get() { } } -fn example_post() { +async fn example_post() { let url = "coap://127.0.0.1:5683/hello/post"; let data = b"data".to_vec(); println!("Client request: {}", url); - match CoAPClient::post(url, data) { + match UdpCoAPClient::post(url, data).await { Ok(response) => { println!( "Server reply: {}", @@ -64,12 +66,12 @@ fn example_post() { } } -fn example_put() { +async fn example_put() { let url = "coap://127.0.0.1:5683/hello/put"; let data = b"data".to_vec(); println!("Client request: {}", url); - match CoAPClient::put(url, data) { + match UdpCoAPClient::put(url, data).await { Ok(response) => { println!( "Server reply: {}", @@ -86,11 +88,11 @@ fn example_put() { } } -fn example_delete() { +async fn example_delete() { let url = "coap://127.0.0.1:5683/hello/delete"; println!("Client request: {}", url); - match CoAPClient::delete(url) { + match UdpCoAPClient::delete(url).await { Ok(response) => { println!( "Server reply: {}", @@ -107,18 +109,20 @@ fn example_delete() { } } -fn example_observe() { - let mut client = CoAPClient::new("127.0.0.1:5683").unwrap(); - client +async fn example_observe() { + let client = UdpCoAPClient::new_udp("127.0.0.1:5683").await.unwrap(); + let observe_channel = client .observe("/hello/put", |msg| { println!( "resource changed {}", String::from_utf8(msg.payload).unwrap() ); }) + .await .unwrap(); - println!("Press any key to stop..."); + println!("Enter any key to stop..."); io::stdin().read_line(&mut String::new()).unwrap(); + observe_channel.send(ObserveMessage::Terminate).unwrap(); } diff --git a/examples/echo.rs b/examples/echo.rs index 98e0a25b7..db43ff72c 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,36 +1,34 @@ extern crate coap; -use coap::{CoAPClient, Server}; -use std::thread; -use tokio::runtime::Runtime; +use std::net::SocketAddr; -fn main() { - thread::spawn(move || { - Runtime::new().unwrap().block_on(async move { - let mut server = Server::new("127.0.0.1:5683").unwrap(); +use coap::{Server, UdpCoAPClient}; +use coap_lite::CoapRequest; +#[tokio::main] +async fn main() { + let _server_task = tokio::spawn(async move { + let server = Server::new_udp("127.0.0.1:5683").unwrap(); + server + .run(|mut request: Box>| async { + let uri_path = request.get_path().to_string(); - server - .run(|request| async { - let uri_path = request.get_path().to_string(); - - return match request.response { - Some(mut response) => { - response.message.payload = uri_path.as_bytes().to_vec(); - Some(response) - } - _ => None, - }; - }) - .await - .unwrap(); - }); + match request.response { + Some(ref mut response) => { + response.message.payload = uri_path.as_bytes().to_vec(); + } + _ => {} + }; + return request; + }) + .await + .unwrap(); }); let url = "coap://127.0.0.1:5683/Rust"; println!("Client request: {}", url); // Maybe need sleep seconds before start client on some OS: https://github.com/Covertness/coap-rs/issues/75 - let response = CoAPClient::get(url).unwrap(); + let response = UdpCoAPClient::get(url).await.unwrap(); println!( "Server reply: {}", String::from_utf8(response.message.payload).unwrap() diff --git a/examples/echo_with_dtls.rs b/examples/echo_with_dtls.rs new file mode 100644 index 000000000..093c16569 --- /dev/null +++ b/examples/echo_with_dtls.rs @@ -0,0 +1,84 @@ +/// This example shows how to use DTLS with coap-rs. If you want to use PKI, please take +/// a look at the test in dtls.rs +extern crate coap; +use coap::client::CoAPClient; +use coap::dtls::DtlsConfig; +use coap::Server; +use coap_lite::{CoapRequest, RequestType as Method}; +use std::future::Future; +use std::net::{SocketAddr, ToSocketAddrs}; +use std::sync::Arc; +use tokio::sync::mpsc; +use webrtc_dtls::cipher_suite::CipherSuiteId; +use webrtc_dtls::config::Config; +use webrtc_dtls::listener::listen; +use webrtc_util::conn::Listener as WebRtcListener; + +pub fn spawn_dtls_server< + F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, + HandlerRet, +>( + ip: &'static str, + request_handler: F, + config: webrtc_dtls::config::Config, +) -> mpsc::UnboundedReceiver +where + HandlerRet: Future>> + Send, +{ + let (tx, rx) = mpsc::unbounded_channel(); + tokio::spawn(async move { + let listener = listen(ip, config).await.unwrap(); + let listen_port = listener.addr().await.unwrap().port(); + let listener = Box::new(listener); + let server = Server::from_listeners(vec![listener]); + tx.send(listen_port).unwrap(); + server.run(request_handler).await.unwrap(); + }); + + rx +} + +#[tokio::main] +async fn main() { + let config = Config { + psk: Some(Arc::new(|_| Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))), + psk_identity_hint: Some("webrtc-rs DTLS Server".as_bytes().to_vec()), + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], + server_name: "localhost".to_string(), + ..Default::default() + }; + let server_port = spawn_dtls_server( + "127.0.0.1:0", + |mut req| async { + req.response.as_mut().unwrap().message.payload = b"hello dtls".to_vec(); + req + }, + config.clone(), + ) + .recv() + .await + .unwrap(); + + let dtls_config = DtlsConfig { + config, + dest_addr: ("127.0.0.1", server_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + }; + + let mut client = CoAPClient::from_dtls_config(dtls_config) + .await + .expect("could not create client"); + let domain = format!("127.0.0.1:{}", server_port); + let resp = client + .request_path("/hello", Method::Get, None, None, Some(domain.to_string())) + .await + .unwrap(); + println!( + "receive on client: {}", + std::str::from_utf8(&resp.message.payload).unwrap() + ); + assert_eq!(resp.message.payload, b"hello dtls".to_vec()); +} diff --git a/examples/server.rs b/examples/server.rs index b4fc7c6b6..024b20635 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,6 +1,9 @@ extern crate coap; +use std::net::SocketAddr; + use coap::Server; +use coap_lite::CoapRequest; use coap_lite::RequestType as Method; use tokio::runtime::Runtime; @@ -8,31 +11,31 @@ fn main() { let addr = "127.0.0.1:5683"; Runtime::new().unwrap().block_on(async move { - let mut server = Server::new(addr).unwrap(); + let server = Server::new_udp(addr).unwrap(); println!("Server up on {}", addr); server - .run(|request| async { + .run(|mut request: Box>| async move { match request.get_method() { &Method::Get => println!("request by get {}", request.get_path()), &Method::Post => println!( "request by post {}", - String::from_utf8(request.message.payload).unwrap() + String::from_utf8(request.message.payload.clone()).unwrap() ), &Method::Put => println!( "request by put {}", - String::from_utf8(request.message.payload).unwrap() + String::from_utf8(request.message.payload.clone()).unwrap() ), _ => println!("request by other method"), }; - return match request.response { - Some(mut message) => { + match request.response { + Some(ref mut message) => { message.message.payload = b"OK".to_vec(); - Some(message) } - _ => None, + _ => {} }; + return request; }) .await .unwrap(); diff --git a/src/client.rs b/src/client.rs index 7e5f09f9b..b7408dbf2 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,3 +1,5 @@ +#[cfg(feature = "dtls")] +use crate::dtls::{DtlsConfig, DtlsConnection}; use alloc::string::String; use alloc::vec::Vec; use coap_lite::{ @@ -8,133 +10,228 @@ use coap_lite::{ }; use core::mem; use core::ops::Deref; +use futures::Future; use log::*; use lru_time_cache::LruCache; use regex::Regex; -use std::io::{Error, ErrorKind, Result}; -use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs, UdpSocket}; -use std::sync::mpsc; -use std::thread; +use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; use std::time::Duration; +use std::{ + io::{Error, ErrorKind, Result}, + pin::Pin, +}; +use tokio::net::{lookup_host, ToSocketAddrs, UdpSocket}; +use tokio::sync::oneshot; +use tokio::time::timeout; use url::Url; +const DEFAULT_RECEIVE_TIMEOUT_SECONDS: u64 = 2; // 2s -const DEFAULT_RECEIVE_TIMEOUT: u64 = 1; // 1s - -enum ObserveMessage { +#[derive(Debug)] +pub enum ObserveMessage { Terminate, } +use async_trait::async_trait; + +#[async_trait] +pub trait Transport: Send { + async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, SocketAddr)>; + async fn send(&self, buf: &[u8]) -> std::io::Result; +} -pub struct CoAPClient { - socket: UdpSocket, - peer_addr: SocketAddr, - observe_sender: Option>, - observe_thread: Option>, +pub struct UdpTransport { + pub socket: UdpSocket, + pub peer_addr: SocketAddr, +} +#[async_trait] +impl Transport for UdpTransport { + async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, SocketAddr)> { + self.socket.recv_from(buf).await + } + async fn send(&self, buf: &[u8]) -> std::io::Result { + self.socket.send_to(buf, self.peer_addr).await + } +} + +/// A CoAP client over UDP. This client can send multicast and broadcasts +pub type UdpCoAPClient = CoAPClient; + +pub struct CoAPClient { + transport: T, block2_states: LruCache, BlockState>, block1_size: usize, message_id: u16, + read_timeout: Option, } -impl CoAPClient { - /// Create a CoAP client with the specific source and peer address. - pub fn new_with_specific_source( +impl UdpCoAPClient { + pub async fn new_with_specific_source( bind_addr: A, peer_addr: B, - ) -> Result { - const MAX_PAYLOAD_BLOCK: usize = 1024; - peer_addr - .to_socket_addrs() - .and_then(|mut iter| match iter.next() { - Some(paddr) => UdpSocket::bind(bind_addr).and_then(|s| { - s.set_read_timeout(Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0))) - .and_then(|_| { - Ok(CoAPClient { - socket: s, - peer_addr: paddr, - observe_sender: None, - observe_thread: None, - block2_states: LruCache::with_expiry_duration(Duration::from_secs( - 120, - )), - block1_size: MAX_PAYLOAD_BLOCK, - message_id: 0, - }) - }) - }), - None => Err(Error::new(ErrorKind::Other, "no address")), - }) - } - - /// Create a CoAP client with the peer address. - pub fn new(addr: A) -> Result { - addr.to_socket_addrs() - .and_then(|mut iter| match iter.next() { - Some(SocketAddr::V4(_)) => Self::new_with_specific_source("0.0.0.0:0", addr), - Some(SocketAddr::V6(_)) => Self::new_with_specific_source(":::0", addr), - None => Err(Error::new(ErrorKind::Other, "no address")), - }) + ) -> Result { + let peer_addr = lookup_host(peer_addr).await?.next().ok_or(Error::new( + ErrorKind::InvalidInput, + "could not get socket address", + ))?; + let socket = UdpSocket::bind(bind_addr).await?; + let transport = UdpTransport { socket, peer_addr }; + return Ok(UdpCoAPClient::from_transport(transport)); + } + + pub async fn new_udp(addr: A) -> Result { + let sock_addr = lookup_host(addr).await?.next().ok_or(Error::new( + ErrorKind::InvalidInput, + "could not get socket address", + ))?; + Ok(match &sock_addr { + SocketAddr::V4(_) => Self::new_with_specific_source("0.0.0.0:0", sock_addr).await?, + SocketAddr::V6(_) => Self::new_with_specific_source(":::0", sock_addr).await?, + }) + } + /// Send a request to all CoAP devices. + /// - IPv4 AllCoAP multicast address is '224.0.1.187' + /// - IPv6 AllCoAp multicast addresses are 'ff0?::fd' + /// Parameter segment is used with IPv6 to determine the first octet. + /// It's value can be between 0x0 and 0xf. To address multiple segments, + /// you have to call send_all_coap for each of the segments. + pub async fn send_all_coap( + &self, + request: &CoapRequest, + segment: u8, + ) -> Result<()> { + assert!(segment <= 0xf); + let addr = match self.transport.peer_addr { + SocketAddr::V4(val) => { + SocketAddr::new(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 187)), val.port()) + } + SocketAddr::V6(val) => SocketAddr::new( + IpAddr::V6(Ipv6Addr::new( + 0xff00 + segment as u16, + 0, + 0, + 0, + 0, + 0, + 0, + 0xfd, + )), + val.port(), + ), + }; + + match request.message.to_bytes() { + Ok(bytes) => { + let size = self.transport.socket.send_to(&bytes[..], addr).await?; + if size == bytes.len() { + Ok(()) + } else { + Err(Error::new(ErrorKind::Other, "send length error")) + } + } + Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")), + } } + pub fn set_broadcast(&self, value: bool) -> Result<()> { + self.transport.socket.set_broadcast(value) + } +} + +#[cfg(feature = "dtls")] +impl CoAPClient { + pub async fn from_dtls_config(config: DtlsConfig) -> Result { + Ok(CoAPClient::from_transport( + DtlsConnection::try_new(config).await?, + )) + } +} + +impl CoAPClient { + const MAX_PAYLOAD_BLOCK: usize = 1024; + /// Create a CoAP client with a chosen transport type + + pub fn from_transport(transport: T) -> Self { + CoAPClient { + transport, + block2_states: LruCache::with_expiry_duration(Duration::from_secs(120)), + block1_size: Self::MAX_PAYLOAD_BLOCK, + message_id: 0, + read_timeout: Some(Duration::new(DEFAULT_RECEIVE_TIMEOUT_SECONDS, 0)), + } + } /// Execute a single get request with a coap url - pub fn get(url: &str) -> Result { - Self::request(url, Method::Get, None) + pub async fn get(url: &str) -> Result { + Self::request(url, Method::Get, None).await } /// Execute a single get request with a coap url and a specific timeout. - pub fn get_with_timeout(url: &str, timeout: Duration) -> Result { - Self::request_with_timeout(url, Method::Get, None, timeout) + pub async fn get_with_timeout(url: &str, timeout: Duration) -> Result { + Self::request_with_timeout(url, Method::Get, None, timeout).await } - /// Execute a single post request with a coap url - pub fn post(url: &str, data: Vec) -> Result { - Self::request(url, Method::Post, Some(data)) + /// Execute a single post request with a coap url using udp + pub async fn post(url: &str, data: Vec) -> Result { + Self::request(url, Method::Post, Some(data)).await } - /// Execute a single post request with a coap url - pub fn post_with_timeout(url: &str, data: Vec, timeout: Duration) -> Result { - Self::request_with_timeout(url, Method::Post, Some(data), timeout) + /// Execute a single post request with a coap url using udp + pub async fn post_with_timeout( + url: &str, + data: Vec, + timeout: Duration, + ) -> Result { + Self::request_with_timeout(url, Method::Post, Some(data), timeout).await } - /// Execute a put request with a coap url - pub fn put(url: &str, data: Vec) -> Result { - Self::request(url, Method::Put, Some(data)) + /// Execute a put request with a coap url using udp + pub async fn put(url: &str, data: Vec) -> Result { + Self::request(url, Method::Put, Some(data)).await } - /// Execute a single put request with a coap url - pub fn put_with_timeout(url: &str, data: Vec, timeout: Duration) -> Result { - Self::request_with_timeout(url, Method::Put, Some(data), timeout) + /// Execute a single put request with a coap url using udp + pub async fn put_with_timeout( + url: &str, + data: Vec, + timeout: Duration, + ) -> Result { + Self::request_with_timeout(url, Method::Put, Some(data), timeout).await } - /// Execute a single delete request with a coap url - pub fn delete(url: &str) -> Result { - Self::request(url, Method::Delete, None) + /// Execute a single delete request with a coap url using udp + pub async fn delete(url: &str) -> Result { + Self::request(url, Method::Delete, None).await } - /// Execute a single delete request with a coap url - pub fn delete_with_timeout(url: &str, timeout: Duration) -> Result { - Self::request_with_timeout(url, Method::Delete, None, timeout) + /// Execute a single delete request with a coap url using udp + pub async fn delete_with_timeout(url: &str, timeout: Duration) -> Result { + Self::request_with_timeout(url, Method::Delete, None, timeout).await } - /// Execute a single request (GET, POST, PUT, DELETE) with a coap url - pub fn request(url: &str, method: Method, data: Option>) -> Result { + /// Execute a single request (GET, POST, PUT, DELETE) with a coap url using udp + pub async fn request(url: &str, method: Method, data: Option>) -> Result { let (domain, port, path, queries) = Self::parse_coap_url(url)?; - let mut client = Self::new((domain.as_str(), port))?; - client.request_path(&path, method, data, queries, Some(domain)) + let mut client = UdpCoAPClient::new_udp((domain.as_str(), port)).await?; + client + .request_path(&path, method, data, queries, Some(domain)) + .await } /// Execute a single request (GET, POST, PUT, DELETE) with a coap url and a specfic timeout - pub fn request_with_timeout( + /// using udp + pub async fn request_with_timeout( url: &str, method: Method, data: Option>, timeout: Duration, ) -> Result { let (domain, port, path, queries) = Self::parse_coap_url(url)?; - let mut client = Self::new((domain.as_str(), port))?; - client.request_path_with_timeout(&path, method, data, queries, Some(domain), timeout) + let mut client = UdpCoAPClient::new_udp((domain.as_str(), port)).await?; + client + .request_path_with_timeout(&path, method, data, queries, Some(domain), timeout) + .await } - /// Execute a request (GET, POST, PUT, DELETE) - pub fn request_path( + /// Execute a request (GET, POST, PUT, DELETE) using the given transport + pub async fn request_path( &mut self, path: &str, method: Method, @@ -148,13 +245,14 @@ impl CoAPClient { data, queries, domain, - Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0), + Duration::new(DEFAULT_RECEIVE_TIMEOUT_SECONDS, 0), ) + .await } - /// Execute a request (GET, POST, PUT, DELETE) with a specfic timeout. This method will + /// Execute a request (GET, POST, PUT, DELETE) with a specfic timeout using the given transport. This method will /// try to use block1 requests with a block size of 1024 by default. - pub fn request_path_with_timeout( + pub async fn request_path_with_timeout( &mut self, path: &str, method: Method, @@ -182,131 +280,144 @@ impl CoAPClient { } self.set_receive_timeout(Some(timeout))?; - self.send2(&mut request)?; - self.receive2(&mut request) - } - pub fn set_broadcast(&self, value: bool) -> Result<()> { - self.socket.set_broadcast(value) + self.send2(&mut request).await?; + self.receive2(&mut request).await } - pub fn observe( - &mut self, + pub async fn observe( + self, resource_path: &str, handler: H, - ) -> Result<()> { + ) -> Result> + where + T: 'static + Send + Sync, + { self.observe_with_timeout( resource_path, handler, - Duration::new(DEFAULT_RECEIVE_TIMEOUT, 0), + Duration::new(DEFAULT_RECEIVE_TIMEOUT_SECONDS, 0), ) + .await } - /// Observe a resource with the handler and specified timeout - pub fn observe_with_timeout( - &mut self, + /// Observe a resource with the handler and specified timeout using the given transport. + /// Use the oneshot sender to cancel observation. If this sender is dropped without explicitly + /// cancelling it, the observation will continue forever. + pub async fn observe_with_timeout( + mut self, resource_path: &str, mut handler: H, timeout: Duration, - ) -> Result<()> { + ) -> Result> + where + T: 'static + Send + Sync, + { // TODO: support observe multi resources at the same time let mut message_id = self.message_id; let mut register_packet = CoapRequest::new(); register_packet.set_observe_flag(ObserveOption::Register); register_packet.message.header.message_id = Self::gen_message_id(&mut message_id); - register_packet.set_path(resource_path); + register_packet.set_path(&resource_path); - self.send(®ister_packet)?; + self.send(®ister_packet).await?; self.set_receive_timeout(Some(timeout))?; - let response = self.receive()?; + let response = self.receive().await?; if *response.get_status() != Status::Content { return Err(Error::new(ErrorKind::NotFound, "the resource not found")); } handler(response.message); - let socket; - match self.socket.try_clone() { - Ok(good_socket) => socket = good_socket, - Err(_) => return Err(Error::new(ErrorKind::Other, "network error")), - } - let peer_addr = self.peer_addr.clone(); - let (observe_sender, observe_receiver) = mpsc::channel(); + let (tx, rx) = oneshot::channel(); let observe_path = String::from(resource_path); - let observe_thread = thread::spawn(move || loop { - match Self::receive_from_socket(&socket) { - Ok((packet, _src)) => { - let receive_packet = CoapRequest::from_packet(packet, &peer_addr); - - handler(receive_packet.message); - - if let Some(response) = receive_packet.response { - let mut packet = Packet::new(); - packet.header.set_type(response.message.header.get_type()); - packet.header.message_id = response.message.header.message_id; - packet.set_token(response.message.get_token().into()); - - match Self::send_with_socket(&socket, &peer_addr, &packet) { - Ok(_) => (), - Err(e) => { - warn!("reply ack failed {}", e) + tokio::spawn(async move { + let mut rx_pinned: Pin< + Box< + dyn Future< + Output = std::result::Result, + > + Send, + >, + > = Box::pin(rx); + loop { + tokio::select! { + sock_rx = self.receive_from_socket() => { + self.receive_and_handle_message(sock_rx, &mut handler).await; + } + observe = &mut rx_pinned => { + match observe { + Ok(ObserveMessage::Terminate) => { + self.terminate_observe(&observe_path, &mut message_id).await; + break; } + // if the receiver is dropped, we change the future to wait forever + Err(_) => { + debug!("observe continuing forever"); + rx_pinned = Box::pin(futures::future::pending()) + }, } } + } - Err(e) => match e.kind() { - ErrorKind::WouldBlock => { - info!("Observe timeout"); - } - _ => warn!("observe failed {:?}", e), - }, - }; - - match observe_receiver.try_recv() { - Ok(ObserveMessage::Terminate) => { - let mut deregister_packet = CoapRequest::::new(); - deregister_packet.message.header.message_id = - Self::gen_message_id(&mut message_id); - deregister_packet.set_observe_flag(ObserveOption::Deregister); - deregister_packet.set_path(observe_path.as_str()); - - Self::send_with_socket(&socket, &peer_addr, &deregister_packet.message) - .unwrap(); - Self::receive_from_socket(&socket).unwrap(); - break; - } - _ => continue, } }); - self.observe_sender = Some(observe_sender); - self.observe_thread = Some(observe_thread); - - return Ok(()); - } - - /// Stop observing - pub fn unobserve(&mut self) { - match self.observe_sender.take() { - Some(ref sender) => { - sender.send(ObserveMessage::Terminate).unwrap(); - - self.observe_thread.take().map(|g| g.join().unwrap()); + return Ok(tx); + } + + async fn terminate_observe(&mut self, observe_path: &str, message_id: &mut u16) { + let mut deregister_packet = CoapRequest::::new(); + deregister_packet.message.header.message_id = Self::gen_message_id(message_id); + deregister_packet.set_observe_flag(ObserveOption::Deregister); + deregister_packet.set_path(observe_path); + let _ = Self::send_with_socket(&self.transport, &deregister_packet.message).await; + let _ = self.receive_from_socket().await; + } + + async fn receive_and_handle_message( + &self, + socket_result: Result<(Packet, SocketAddr)>, + handler: &mut H, + ) { + match socket_result { + Ok((packet, src)) => { + let receive_packet = CoapRequest::from_packet(packet, src); + debug!("received a packet: {:?}", &receive_packet); + handler(receive_packet.message); + + if let Some(response) = receive_packet.response { + let mut packet = Packet::new(); + packet.header.set_type(response.message.header.get_type()); + packet.header.message_id = response.message.header.message_id; + packet.set_token(response.message.get_token().into()); + + match Self::send_with_socket(&self.transport, &packet).await { + Ok(_) => (), + Err(e) => { + warn!("reply ack failed {}", e) + } + } + } } - _ => {} - } + Err(e) => match e.kind() { + ErrorKind::WouldBlock => { + info!("Observe timeout"); + } + _ => warn!("observe failed {:?}", e), + }, + }; } /// Execute a request. - pub fn send(&self, request: &CoapRequest) -> Result<()> { - Self::send_with_socket(&self.socket, &self.peer_addr, &request.message) + pub async fn send(&self, request: &CoapRequest) -> Result<()> { + Self::send_with_socket(&self.transport, &request.message).await } /// send a request supporting block1 option based on the block size set in the client - pub fn send2(&mut self, request: &mut CoapRequest) -> Result<()> { + pub async fn send2(&mut self, request: &mut CoapRequest) -> Result<()> { let request_length = request.message.payload.len(); if request_length <= self.block1_size { - return self.send(request); + return self.send(request).await; } let payload = std::mem::take(&mut request.message.payload); let mut it = payload.chunks(self.block1_size).enumerate().peekable(); @@ -322,10 +433,10 @@ impl CoAPClient { request.message.payload = elem.to_vec(); request.message.header.message_id = Self::gen_message_id(&mut self.message_id); - self.send(request)?; + self.send(request).await?; // continue receiving responses until last element if it.peek().is_some() { - let resp = self.receive()?; + let resp = self.receive().await?; let maybe_block1 = resp .message .get_first_option_as::(CoapOption::Block1) @@ -350,56 +461,19 @@ impl CoAPClient { } Ok(()) } - /// Send a request to all CoAP devices. - /// - IPv4 AllCoAP multicast address is '224.0.1.187' - /// - IPv6 AllCoAp multicast addresses are 'ff0?::fd' - /// Parameter segment is used with IPv6 to determine the first octet. - /// It's value can be between 0x0 and 0xf. To address multiple segments, - /// you have to call send_all_coap for each of the segments. - pub fn send_all_coap(&self, request: &CoapRequest, segment: u8) -> Result<()> { - assert!(segment <= 0xf); - let addr = match self.peer_addr { - SocketAddr::V4(val) => { - SocketAddr::new(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 187)), val.port()) - } - SocketAddr::V6(val) => SocketAddr::new( - IpAddr::V6(Ipv6Addr::new( - 0xff00 + segment as u16, - 0, - 0, - 0, - 0, - 0, - 0, - 0xfd, - )), - val.port(), - ), - }; - - match request.message.to_bytes() { - Ok(bytes) => { - let size = self.socket.send_to(&bytes[..], addr)?; - if size == bytes.len() { - Ok(()) - } else { - Err(Error::new(ErrorKind::Other, "send length error")) - } - } - Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")), - } - } - /// Receive a response. - pub fn receive(&self) -> Result { - let (packet, _src) = Self::receive_from_socket(&self.socket)?; + pub async fn receive(&self) -> Result { + let (packet, _src) = self.receive_from_socket().await?; Ok(CoapResponse { message: packet }) } /// Receive a response support block-wise. - pub fn receive2(&mut self, request: &mut CoapRequest) -> Result { + pub async fn receive2( + &mut self, + request: &mut CoapRequest, + ) -> Result { loop { - let (packet, _src) = Self::receive_from_socket(&self.socket)?; + let (packet, _src) = self.receive_from_socket().await?; request.response = CoapResponse::new(&request.message); let response = request .response @@ -408,7 +482,7 @@ impl CoAPClient { response.message = packet; match self.intercept_response(request) { Ok(true) => { - self.send(request)?; + self.send(request).await?; } Err(err) => { error!("intercept response error: {:?}", err); @@ -425,14 +499,15 @@ impl CoAPClient { } /// Receive a response. - pub fn receive_from(&self) -> Result<(CoapResponse, SocketAddr)> { - let (packet, src) = Self::receive_from_socket(&self.socket)?; + pub async fn receive_from(&self) -> Result<(CoapResponse, SocketAddr)> { + let (packet, src) = self.receive_from_socket().await?; Ok((CoapResponse { message: packet }, src)) } /// Set the receive timeout. - pub fn set_receive_timeout(&self, dur: Option) -> Result<()> { - self.socket.set_read_timeout(dur) + pub fn set_receive_timeout(&mut self, dur: Option) -> Result<()> { + self.read_timeout = dur; + Ok(()) } /// Set the maximum size for a block1 request. Default is 1024 bytes @@ -440,15 +515,12 @@ impl CoAPClient { self.block1_size = block1_max_bytes; } - fn send_with_socket( - socket: &UdpSocket, - peer_addr: &SocketAddr, - message: &Packet, - ) -> Result<()> { + async fn send_with_socket(socket: &T, message: &Packet) -> Result<()> { let message_bytes = message .to_bytes() .map_err(|_| Error::new(ErrorKind::InvalidInput, "packet error"))?; - let size = socket.send_to(&message_bytes[..], peer_addr)?; + // TODO: figure out what to do with peer address + let size = socket.send(&message_bytes[..]).await?; if size == message_bytes.len() { Ok(()) } else { @@ -456,10 +528,12 @@ impl CoAPClient { } } - fn receive_from_socket(socket: &UdpSocket) -> Result<(Packet, SocketAddr)> { + async fn receive_from_socket(&self) -> Result<(Packet, SocketAddr)> { let mut buf = [0; 1500]; - - let (nread, src) = socket.recv_from(&mut buf)?; + let (nread, src) = match self.read_timeout { + Some(dur) => timeout(dur, self.transport.recv(&mut buf)).await??, + None => self.transport.recv(&mut buf).await?, + }; match Packet::from_bytes(&buf[..nread]) { Ok(packet) => Ok((packet, src)), Err(_) => Err(Error::new(ErrorKind::InvalidInput, "packet error")), @@ -559,12 +633,6 @@ impl CoAPClient { } } -impl Drop for CoAPClient { - fn drop(&mut self) { - self.unobserve(); - } -} - #[derive(Debug, Clone, Default)] pub struct BlockState { cached_payload: Option>, @@ -577,34 +645,34 @@ mod test { use super::*; use std::io::ErrorKind; use std::time::Duration; - #[test] fn test_parse_coap_url_good_url() { - assert!(CoAPClient::parse_coap_url("coap://127.0.0.1").is_ok()); - assert!(CoAPClient::parse_coap_url("coap://127.0.0.1:5683").is_ok()); - assert!(CoAPClient::parse_coap_url("coap://[::1]").is_ok()); - assert!(CoAPClient::parse_coap_url("coap://[::1]:5683").is_ok()); - assert!(CoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]").is_ok()); - assert!(CoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]:5683").is_ok()); - assert!(CoAPClient::parse_coap_url("coap://127.0.0.1/?hello=world").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1:5683").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://[::1]").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://[::1]:5683").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://[bbbb::9329:f033:f558:7418]:5683").is_ok()); + assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1/?hello=world").is_ok()); } #[test] fn test_parse_coap_url_bad_url() { - assert!(CoAPClient::parse_coap_url("coap://127.0.0.1:65536").is_err()); - assert!(CoAPClient::parse_coap_url("coap://").is_err()); - assert!(CoAPClient::parse_coap_url("coap://:5683").is_err()); - assert!(CoAPClient::parse_coap_url("127.0.0.1").is_err()); + assert!(UdpCoAPClient::parse_coap_url("coap://127.0.0.1:65536").is_err()); + assert!(UdpCoAPClient::parse_coap_url("coap://").is_err()); + assert!(UdpCoAPClient::parse_coap_url("coap://:5683").is_err()); + assert!(UdpCoAPClient::parse_coap_url("127.0.0.1").is_err()); } - async fn request_handler(_: CoapRequest) -> Option { - None + async fn request_handler(req: Box>) -> Box> { + tokio::time::sleep(Duration::from_secs(1)).await; + req } #[test] fn test_parse_queries() { if let Ok((_, _, _, Some(queries))) = - CoAPClient::parse_coap_url("coap://127.0.0.1/?hello=world&test1=test2") + UdpCoAPClient::parse_coap_url("coap://127.0.0.1/?hello=world&test1=test2") { assert_eq!("hello=world&test1=test2".as_bytes().to_vec(), queries); } else { @@ -612,51 +680,56 @@ mod test { } } - #[test] - fn test_get_url() { - let resp = CoAPClient::get("coap://coap.me:5683/hello").unwrap(); + #[tokio::test] + async fn test_get_url() { + let resp = UdpCoAPClient::get("coap://coap.me:5683/hello") + .await + .unwrap(); assert_eq!(resp.message.payload, b"world".to_vec()); } - #[test] - fn test_get_url_timeout() { + #[tokio::test] + async fn test_get_url_timeout() { let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) .recv() + .await .unwrap(); - let error = CoAPClient::get_with_timeout( + let error = UdpCoAPClient::get_with_timeout( &format!("coap://127.0.0.1:{}/Rust", server_port), - Duration::new(1, 0), + Duration::new(0, 0), ) + .await .unwrap_err(); - if cfg!(windows) { - assert_eq!(error.kind(), ErrorKind::TimedOut); - } else { - assert_eq!(error.kind(), ErrorKind::WouldBlock); - } + assert_eq!(error.kind(), ErrorKind::TimedOut); } - #[test] - fn test_get() { + #[tokio::test] + async fn test_get() { let domain = "coap.me"; - let mut client = CoAPClient::new((domain, 5683)).unwrap(); + let mut client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); let resp = client .request_path("/hello", Method::Get, None, None, Some(domain.to_string())) + .await .unwrap(); assert_eq!(resp.message.payload, b"world".to_vec()); } - #[test] - fn test_post_url() { - let resp = CoAPClient::post("coap://coap.me:5683/validate", b"world".to_vec()).unwrap(); + #[tokio::test] + async fn test_post_url() { + let resp = UdpCoAPClient::post("coap://coap.me:5683/validate", b"world".to_vec()) + .await + .unwrap(); assert_eq!(resp.message.payload, b"POST OK".to_vec()); - let resp = CoAPClient::post("coap://coap.me:5683/validate", b"test".to_vec()).unwrap(); + let resp = UdpCoAPClient::post("coap://coap.me:5683/validate", b"test".to_vec()) + .await + .unwrap(); assert_eq!(resp.message.payload, b"POST OK".to_vec()); } - #[test] - fn test_post() { + #[tokio::test] + async fn test_post() { let domain = "coap.me"; - let mut client = CoAPClient::new((domain, 5683)).unwrap(); + let mut client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); let resp = client .request_path( "/validate", @@ -665,22 +738,27 @@ mod test { None, Some(domain.to_string()), ) + .await .unwrap(); assert_eq!(resp.message.payload, b"POST OK".to_vec()); } - #[test] - fn test_put_url() { - let resp = CoAPClient::put("coap://coap.me:5683/create1", b"world".to_vec()).unwrap(); + #[tokio::test] + async fn test_put_url() { + let resp = UdpCoAPClient::put("coap://coap.me:5683/create1", b"world".to_vec()) + .await + .unwrap(); assert_eq!(resp.message.payload, b"Created".to_vec()); - let resp = CoAPClient::put("coap://coap.me:5683/create1", b"test".to_vec()).unwrap(); + let resp = UdpCoAPClient::put("coap://coap.me:5683/create1", b"test".to_vec()) + .await + .unwrap(); assert_eq!(resp.message.payload, b"Created".to_vec()); } - #[test] - fn test_put() { + #[tokio::test] + async fn test_put() { let domain = "coap.me"; - let mut client = CoAPClient::new((domain, 5683)).unwrap(); + let mut client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); let resp = client .request_path( "/create1", @@ -689,22 +767,27 @@ mod test { None, Some(domain.to_string()), ) + .await .unwrap(); assert_eq!(resp.message.payload, b"Created".to_vec()); } - #[test] - fn test_delete_url() { - let resp = CoAPClient::delete("coap://coap.me:5683/validate").unwrap(); + #[tokio::test] + async fn test_delete_url() { + let resp = UdpCoAPClient::delete("coap://coap.me:5683/validate") + .await + .unwrap(); assert_eq!(resp.message.payload, b"DELETE OK".to_vec()); - let resp = CoAPClient::delete("coap://coap.me:5683/validate").unwrap(); + let resp = UdpCoAPClient::delete("coap://coap.me:5683/validate") + .await + .unwrap(); assert_eq!(resp.message.payload, b"DELETE OK".to_vec()); } - #[test] - fn test_delete() { + #[tokio::test] + async fn test_delete() { let domain = "coap.me"; - let mut client = CoAPClient::new((domain, 5683)).unwrap(); + let mut client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); let resp = client .request_path( "/validate", @@ -713,27 +796,28 @@ mod test { None, Some(domain.to_string()), ) + .await .unwrap(); assert_eq!(resp.message.payload, b"DELETE OK".to_vec()); } - #[test] - fn test_set_broadcast() { - let client = CoAPClient::new(("127.0.0.1", 5683)).unwrap(); + #[tokio::test] + async fn test_set_broadcast() { + let client = UdpCoAPClient::new_udp(("127.0.0.1", 5683)).await.unwrap(); assert!(client.set_broadcast(true).is_ok()); assert!(client.set_broadcast(false).is_ok()); } - #[test] + #[tokio::test] #[ignore] - fn test_set_broadcast_v6() { - let client = CoAPClient::new(("::1", 5683)).unwrap(); + async fn test_set_broadcast_v6() { + let client = UdpCoAPClient::new_udp(("::1", 5683)).await.unwrap(); assert!(client.set_broadcast(true).is_ok()); assert!(client.set_broadcast(false).is_ok()); } - #[test] - fn test_send_all_coap() { + #[tokio::test] + async fn test_send_all_coap() { // prepare the Non-confirmable request with the broadcast message let mut request: CoapRequest = CoapRequest::new(); request.set_method(Method::Get); @@ -744,12 +828,11 @@ mod test { .set_type(coap_lite::MessageType::NonConfirmable); request.message.payload = b"Discovery".to_vec(); - let client = CoAPClient::new(("127.0.0.1", 5683)).unwrap(); - assert!(client.send_all_coap(&request, 0).is_ok()); + let client = UdpCoAPClient::new_udp(("127.0.0.1", 5683)).await.unwrap(); + client.send_all_coap(&request, 0).await.unwrap(); } - - #[test] - fn test_change_block_option() { + #[tokio::test] + async fn test_change_block_option() { // this test is a little finnicky because it relies on the configuration // of the reception endpoint. It tries to send a payload larger than the // default using a block option, this request is expected to fail because @@ -761,14 +844,16 @@ mod test { large_payload.extend_from_slice(PAYLOAD_STR.as_bytes()); } let domain = "coap.me"; - let mut client = CoAPClient::new((domain, 5683)).unwrap(); - let resp = client.request_path( - "/large-create", - Method::Put, - Some(large_payload.clone()), - None, - Some(domain.to_string()), - ); + let mut client = UdpCoAPClient::new_udp((domain, 5683)).await.unwrap(); + let resp = client + .request_path( + "/large-create", + Method::Put, + Some(large_payload.clone()), + None, + Some(domain.to_string()), + ) + .await; let err = resp.unwrap_err(); assert_eq!(err.kind(), ErrorKind::Unsupported); //we now set the block size to make sure it is sent in a single request @@ -782,12 +867,13 @@ mod test { None, Some(domain.to_string()), ) + .await .unwrap(); assert_eq!(*resp.get_status(), Status::Created); } - #[test] + #[tokio::test] #[ignore] - fn test_send_all_coap_v6() { + async fn test_send_all_coap_v6() { // prepare the Non-confirmable request with the broadcast message let mut request: CoapRequest = CoapRequest::new(); request.set_method(Method::Get); @@ -798,7 +884,7 @@ mod test { .set_type(coap_lite::MessageType::NonConfirmable); request.message.payload = b"Discovery".to_vec(); - let client = CoAPClient::new(("::1", 5683)).unwrap(); - assert!(client.send_all_coap(&request, 0x4).is_ok()); + let client = UdpCoAPClient::new_udp(("::1", 5683)).await.unwrap(); + client.send_all_coap(&request, 0x4).await.unwrap(); } } diff --git a/src/dtls.rs b/src/dtls.rs new file mode 100644 index 000000000..d3f7acf20 --- /dev/null +++ b/src/dtls.rs @@ -0,0 +1,470 @@ +//! this file is included by enabling the "dtls" feature. It provides a default DTLS backend using +//! webrtc-rs's dtls implementation. +use crate::client::Transport; +use crate::server::{Listener, Responder, TransportRequestSender}; +use async_trait::async_trait; +use std::net::SocketAddr; +use std::time::Duration; +use std::{ + io::{Error, ErrorKind, Result}, + sync::Arc, +}; +use tokio::net::UdpSocket; +use tokio::task::JoinHandle; +use tokio::time::timeout; +use webrtc_dtls::conn::DTLSConn; +use webrtc_util::conn::{Conn, Listener as WebRtcListener}; + +#[async_trait] +impl Listener for L { + async fn listen( + self: Box, + sender: TransportRequestSender, + ) -> std::io::Result>> { + Ok(tokio::spawn(async move { + loop { + let res = self.accept().await; + if let Ok((dtls_conn, remote_addr)) = res { + tokio::spawn(spawn_webrtc_conn(dtls_conn, remote_addr, sender.clone())); + } else { + return Err(std::io::Error::new(ErrorKind::Other, "could not accept")); + } + } + })) + } +} + +pub struct DtlsResponse { + pub conn: Arc, + pub remote_addr: SocketAddr, +} + +#[async_trait] +impl Transport for DtlsConnection { + async fn recv(&self, buf: &mut [u8]) -> std::io::Result<(usize, SocketAddr)> { + let read = self + .conn + .read(buf, None) + .await + .map_err(|e| Error::new(ErrorKind::Other, e))?; + let from = self.peer_addr; + return Ok((read, from)); + } + + async fn send(&self, buf: &[u8]) -> std::io::Result { + self.conn + .write(buf, None) + .await + .map_err(|e| Error::new(ErrorKind::Other, e)) + } +} +#[async_trait] +impl Responder for DtlsResponse { + /// responds to a request by creating a new task. This ensures we do not + /// block the main server handler task + async fn respond(&self, response: Vec) { + let self_clone = self.conn.clone(); + tokio::spawn(async move { self_clone.send(&response).await }); + } + fn address(&self) -> SocketAddr { + self.remote_addr + } +} + +pub async fn spawn_webrtc_conn( + conn: Arc, + remote_addr: SocketAddr, + sender: TransportRequestSender, +) { + const VECTOR_LENGTH: usize = 1600; + loop { + let mut vec_buf = Vec::with_capacity(VECTOR_LENGTH); + unsafe { vec_buf.set_len(VECTOR_LENGTH) }; + let Ok(rx) = conn.recv(&mut vec_buf).await else { + break; + }; + if rx == 0 || rx > VECTOR_LENGTH { + break; + } + unsafe { vec_buf.set_len(rx) } + let response = Arc::new(DtlsResponse { + conn: conn.clone(), + remote_addr, + }); + let Ok(_) = sender.send((vec_buf, response)) else { + break; + }; + } +} + +pub struct DtlsConnection { + pub conn: DTLSConn, + pub peer_addr: SocketAddr, +} + +impl DtlsConnection { + pub async fn try_new(dtls_config: DtlsConfig) -> Result { + let conn = Arc::new( + UdpSocket::bind("0.0.0.0:0") + .await + .map_err(|e| Error::new(ErrorKind::Other, e))?, + ); + conn.connect(dtls_config.dest_addr) + .await + .map_err(|_| Error::new(ErrorKind::AddrNotAvailable, "address is in use"))?; + let dtls_conn = timeout( + Duration::new(30, 0), + DTLSConn::new(conn, dtls_config.config, true, None), + ) + .await + .map_err(|_| { + Error::new( + ErrorKind::TimedOut, + "Received no response on DTLS handshake", + ) + })? + .map_err(|e| Error::new(ErrorKind::Other, e))?; + return Ok(DtlsConnection { + conn: dtls_conn, + peer_addr: dtls_config.dest_addr, + }); + } +} +pub struct DtlsConfig { + pub config: webrtc_dtls::config::Config, + pub dest_addr: SocketAddr, +} + +#[cfg(test)] +mod test { + use super::*; + use crate::client::CoAPClient; + use crate::server::UdpCoapListener; + use crate::{Server, UdpCoAPClient}; + use coap_lite::{CoapOption, CoapRequest, RequestType as Method}; + use futures::Future; + use pkcs8::{LineEnding, SecretDocument}; + use rcgen::KeyPair; + use std::fs::File; + use std::io::{BufReader, Read}; + use std::net::{SocketAddr, ToSocketAddrs}; + use tokio::sync::mpsc; + use webrtc_dtls::cipher_suite::CipherSuiteId; + use webrtc_dtls::config::{ClientAuthType, Config, ExtendedMasterSecretType}; + use webrtc_dtls::crypto::{Certificate, CryptoPrivateKey}; + use webrtc_dtls::listener::listen; + + const SERVER_CERTIFICATE_PRIVATE_KEY: &'static str = "tests/test_certs/coap_server.pem"; + const SERVER_CERTIFICATE: &'static str = "tests/test_certs/coap_server.pub.pem"; + const CLIENT_CERTIFICATE_PRIVATE_KEY: &'static str = "tests/test_certs/coap_client.pem"; + const CLIENT_CERTIFICATE: &'static str = "tests/test_certs/coap_client.pub.pem"; + + async fn request_handler( + mut req: Box>, + ) -> Box> { + let uri_path_list = req.message.get_option(CoapOption::UriPath).unwrap().clone(); + assert_eq!(uri_path_list.len(), 1); + + match req.response { + Some(ref mut response) => { + response.message.payload = uri_path_list.front().unwrap().clone(); + } + _ => {} + } + return req; + } + pub fn spawn_dtls_server< + F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, + HandlerRet, + >( + ip: &'static str, + request_handler: F, + config: webrtc_dtls::config::Config, + ) -> mpsc::UnboundedReceiver + where + HandlerRet: Future>> + Send, + { + let (tx, rx) = mpsc::unbounded_channel(); + tokio::spawn(async move { + let listener = listen(ip, config).await.unwrap(); + let listen_port = listener.addr().await.unwrap().port(); + let listener = Box::new(listener); + let server = Server::from_listeners(vec![listener]); + tx.send(listen_port).unwrap(); + server.run(request_handler).await.unwrap(); + }); + + rx + } + + pub fn get_certificate(name: &str) -> rustls::Certificate { + let mut f = File::open(name).unwrap(); + let mut reader = BufReader::new(&mut f); + let mut cert_iter = rustls_pemfile::certs(&mut reader); + let cert = cert_iter + .next() + .unwrap() + .expect("could not get certificate"); + assert!( + cert_iter.next().is_none(), + "there should only be 1 certificate in this file" + ); + return rustls::Certificate(cert.to_vec()); + } + + pub fn server_certificate() -> rustls::Certificate { + return get_certificate(SERVER_CERTIFICATE); + } + + pub fn client_certificate() -> rustls::Certificate { + return get_certificate(CLIENT_CERTIFICATE); + } + pub fn convert_to_pkcs8(s: &str) -> String { + let pkdoc: SecretDocument = + sec1::DecodeEcPrivateKey::from_sec1_pem(s).expect("could not parse ec key"); + + let pkcs8_pem = pkdoc + .to_pem("PRIVATE_KEY", LineEnding::LF) + .expect("could not encode ec key to PEM"); + return pkcs8_pem.to_string(); + } + + pub fn get_private_key(name: &str) -> CryptoPrivateKey { + let f = File::open(name).unwrap(); + let mut reader = BufReader::new(f); + let mut buf = vec![]; + reader.read_to_end(&mut buf).unwrap(); + let s = String::from_utf8(buf).expect("utf8 of file"); + // convert key to pkcs8 + let s = convert_to_pkcs8(&s); + + let key_pair = KeyPair::from_pem(s.as_str()).expect("key pair in file"); + CryptoPrivateKey::from_key_pair(&key_pair).expect("crypto key pair") + } + + pub fn server_key() -> CryptoPrivateKey { + return get_private_key(SERVER_CERTIFICATE_PRIVATE_KEY); + } + + pub fn client_key() -> CryptoPrivateKey { + return get_private_key(CLIENT_CERTIFICATE_PRIVATE_KEY); + } + + pub fn get_psk_config() -> Config { + Config { + psk: Some(Arc::new(|_| Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 0]))), + psk_identity_hint: Some("webrtc-rs DTLS Server".as_bytes().to_vec()), + cipher_suites: vec![CipherSuiteId::Tls_Psk_With_Aes_128_Ccm_8], + server_name: "localhost".to_string(), + ..Default::default() + } + } + + #[tokio::test] + async fn test_dtls_pki() { + let server_cfg = { + let mut server_cert_pool = rustls::RootCertStore::empty(); + let server_cert = server_certificate(); + server_cert_pool + .add(&server_cert) + .expect("could not add certificate"); + + let server_private_key = server_key(); + let certificate = Certificate { + certificate: vec![server_cert], + private_key: server_private_key, + }; + + Config { + certificates: vec![certificate], + extended_master_secret: ExtendedMasterSecretType::Require, + client_auth: ClientAuthType::RequireAndVerifyClientCert, //RequireAnyClientCert, // + client_cas: server_cert_pool, + ..Default::default() + } + }; + + let client_cfg = { + let mut client_cert_pool = rustls::RootCertStore::empty(); + let client_cert = client_certificate(); + let server_cert = server_certificate(); + client_cert_pool + .add(&server_cert) + .expect("could not add certificate"); + + let client_private_key = client_key(); + let certificate = Certificate { + certificate: vec![client_cert], + private_key: client_private_key, + }; + + Config { + certificates: vec![certificate], + extended_master_secret: ExtendedMasterSecretType::Require, + roots_cas: client_cert_pool, + server_name: "coap.rs".to_owned(), + ..Default::default() + } + }; + + let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, server_cfg.clone()) + .recv() + .await + .unwrap(); + + let dtls_config = DtlsConfig { + config: client_cfg, + dest_addr: ("127.0.0.1", server_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + }; + + let mut client = CoAPClient::from_dtls_config(dtls_config) + .await + .expect("could not create client"); + let domain = format!("127.0.0.1:{}", server_port); + let resp = client + .request_path("/hello", Method::Get, None, None, Some(domain.to_string())) + .await + .unwrap(); + assert_eq!(resp.message.payload, b"hello".to_vec()); + } + #[tokio::test] + async fn test_dtls_psk() { + let config = get_psk_config(); + let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) + .recv() + .await + .unwrap(); + + let dtls_config = DtlsConfig { + config, + dest_addr: ("127.0.0.1", server_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + }; + + let mut client = CoAPClient::from_dtls_config(dtls_config) + .await + .expect("could not create client"); + let domain = format!("127.0.0.1:{}", server_port); + let resp = client + .request_path("/hello", Method::Get, None, None, Some(domain.to_string())) + .await + .unwrap(); + assert_eq!(resp.message.payload, b"hello".to_vec()); + } + #[tokio::test] + async fn test_dtls_false_psk() { + let mut config = get_psk_config(); + let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) + .recv() + .await + .unwrap(); + // make the psk fail + config.psk = Some(Arc::new(|_| Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 9]))); + + let dtls_config = DtlsConfig { + config, + dest_addr: ("127.0.0.1", server_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + }; + assert!( + CoAPClient::from_dtls_config(dtls_config).await.is_err(), + "should not have connected" + ); + } + + #[tokio::test] + async fn test_wrong_protocol() { + let config = get_psk_config(); + let server_port = spawn_dtls_server("127.0.0.1:0", request_handler, config.clone()) + .recv() + .await + .unwrap(); + // make the psk fail + + let get = UdpCoAPClient::get(&format!("coap://127.0.0.1:{}/hello", server_port)).await; + let get_error = get.unwrap_err(); + assert!(get_error.kind() == ErrorKind::TimedOut); + + let dtls_config = DtlsConfig { + config, + dest_addr: ("127.0.0.1", server_port) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + }; + + let mut client = CoAPClient::from_dtls_config(dtls_config) + .await + .expect("could not create client"); + let domain = format!("127.0.0.1:{}", server_port); + let resp = client + .request_path("/hello", Method::Get, None, None, Some(domain.to_string())) + .await + .unwrap(); + assert_eq!(resp.message.payload, b"hello".to_vec()); + } + + #[tokio::test] + async fn test_multiple_listeners() { + let config = get_psk_config(); + // spawn a server with 2 listeners + let (tx, mut rx) = mpsc::unbounded_channel(); + tokio::spawn(async move { + let config = get_psk_config(); + let listener_dtls = listen("127.0.0.1:0", config).await.unwrap(); + let port_dtls = listener_dtls.addr().await.unwrap().port(); + let listener_dtls = Box::new(listener_dtls); + + let sock_udp = UdpSocket::bind("127.0.0.1:0").await.unwrap(); + let port_udp = sock_udp.local_addr().unwrap().port(); + let listener_udp = Box::new(UdpCoapListener::from_socket(sock_udp)); + + let server = Server::from_listeners(vec![listener_dtls, listener_udp]); + tx.send((port_udp, port_dtls)).unwrap(); + server.run(request_handler).await.unwrap(); + }); + + let (udp, dtls) = rx.recv().await.unwrap(); + + let get = UdpCoAPClient::get(&format!("coap://127.0.0.1:{}/hello_udp", udp)) + .await + .unwrap(); + assert_eq!(get.message.payload, b"hello_udp".to_vec()); + + let dtls_config = DtlsConfig { + config, + dest_addr: ("127.0.0.1", dtls) + .to_socket_addrs() + .unwrap() + .next() + .unwrap(), + }; + + let mut client = CoAPClient::from_dtls_config(dtls_config) + .await + .expect("could not create client"); + let domain = format!("127.0.0.1:{}", dtls); + let resp = client + .request_path( + "/hello_dtls", + Method::Get, + None, + None, + Some(domain.to_string()), + ) + .await + .unwrap(); + assert_eq!(resp.message.payload, b"hello_dtls".to_vec()); + } +} diff --git a/src/lib.rs b/src/lib.rs index 0400d4fb6..dd76b5ccf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,11 @@ //! - CoAP Observe option [RFC 7641](https://tools.ietf.org/rfc/rfc7641.txt) //! - *Too Many Requests* Response Code [RFC 8516](https://tools.ietf.org/html/rfc8516) //! - Block-Wise Transfers [RFC 7959](https://tools.ietf.org/html/rfc7959) +//! - DTLS support via [webrtc-rs](https://github.com/webrtc-rs/webrtc) +//! - Option to provide custom transports for client and server +//! +//! Missing features: +//! - Client receiving piggybacked requests //! //! # Installation //! @@ -30,32 +35,32 @@ //! ```no_run //! extern crate coap; //! -//! use coap_lite::{RequestType as Method}; +//! use coap_lite::{RequestType as Method, CoapRequest}; //! use coap::Server; //! use tokio::runtime::Runtime; - +//! use std::net::SocketAddr; //! fn main() { //! let addr = "127.0.0.1:5683"; - //! Runtime::new().unwrap().block_on(async move { -//! let mut server = Server::new(addr).unwrap(); +//! let mut server = Server::new_udp(addr).unwrap(); //! println!("Server up on {}", addr); //! -//! server.run(|request| async { +//! server.run(|mut request: Box>| async { //! match request.get_method() { //! &Method::Get => println!("request by get {}", request.get_path()), -//! &Method::Post => println!("request by post {}", String::from_utf8(request.message.payload).unwrap()), -//! &Method::Put => println!("request by put {}", String::from_utf8(request.message.payload).unwrap()), +//! &Method::Post => println!("request by post {}", String::from_utf8(request.message.payload.clone()).unwrap()), +//! &Method::Put => println!("request by put {}", String::from_utf8(request.message.payload.clone()).unwrap()), //! _ => println!("request by other method"), //! }; -//! return match request.response { -//! Some(mut message) => { +//! match request.response { +//! Some(ref mut message) => { //! message.message.payload = b"OK".to_vec(); -//! Some(message) + //! }, -//! _ => None +//! _ => {} //! }; +//! return request //! }).await.unwrap(); //! }); //! } @@ -66,16 +71,16 @@ //! extern crate coap; //! //! use coap_lite::{RequestType as Method, CoapRequest}; -//! use coap::{CoAPClient}; -//! -//! fn main() { +//! use coap::{UdpCoAPClient}; +//! use tokio::main; +//! #[tokio::main] +//! async fn main() { //! let url = "coap://127.0.0.1:5683/Rust"; //! println!("Client request: {}", url); //! -//! let response = CoAPClient::get(url).unwrap(); +//! let response = UdpCoAPClient::get(url).await.unwrap(); //! println!("Server reply: {}", String::from_utf8(response.message.payload).unwrap()); //! } -//! ``` #[macro_use] extern crate alloc; @@ -83,10 +88,11 @@ extern crate alloc; #[cfg(test)] extern crate quickcheck; -pub use self::client::CoAPClient; +pub use self::client::UdpCoAPClient; pub use self::observer::Observer; -pub use self::server::{CoAPServer, Server}; +pub use self::server::Server; pub mod client; -pub mod message; +#[cfg(feature = "dtls")] +pub mod dtls; mod observer; pub mod server; diff --git a/src/message/mod.rs b/src/message/mod.rs deleted file mode 100644 index e11f92ad4..000000000 --- a/src/message/mod.rs +++ /dev/null @@ -1,43 +0,0 @@ -use bytes::BytesMut; -use tokio::io; - -use tokio_util::codec::{Decoder, Encoder}; - -use coap_lite::Packet; - -pub struct Codec {} - -impl Codec { - pub fn new() -> Codec { - Codec {} - } -} - -impl Decoder for Codec { - type Item = Packet; - type Error = io::Error; - - fn decode(&mut self, buf: &mut BytesMut) -> Result, io::Error> { - if buf.len() == 0 { - return Ok(None); - } - let result = (|| { - let packet = Ok(Some(Packet::from_bytes(buf).map_err(|cause| { - io::Error::new(io::ErrorKind::InvalidData, cause.to_string()) - })?)); - packet - })(); - buf.clear(); - result - } -} - -impl Encoder for Codec { - type Error = io::Error; - - fn encode(&mut self, my_packet: Packet, buf: &mut BytesMut) -> Result<(), io::Error> { - buf.extend_from_slice(&my_packet.to_bytes() - .map_err(|cause| io::Error::new(io::ErrorKind::InvalidData, cause.to_string()))?[..]); - Ok(()) - } -} diff --git a/src/observer.rs b/src/observer.rs index 76f8c633a..727996b2d 100644 --- a/src/observer.rs +++ b/src/observer.rs @@ -10,12 +10,13 @@ use log::{debug, warn}; use std::{ collections::{hash_map::Entry, HashMap, HashSet}, net::SocketAddr, + sync::Arc, time::Duration, }; use tokio::time::interval; use tokio_stream::wrappers::IntervalStream; -use super::server::MessageSender; +use crate::server::Responder; const DEFAULT_UNACKNOWLEDGE_MESSAGE_TRY_TIMES: usize = 10; @@ -24,7 +25,6 @@ pub struct Observer { resources: HashMap, register_resources: HashMap, unacknowledge_messages: HashMap, - tx_sender: MessageSender, current_message_id: u16, timer: Fuse, } @@ -41,12 +41,11 @@ struct ResourceItem { sequence: u32, } -#[derive(Debug)] struct RegisterResourceItem { - register: String, - resource: String, - token: Vec, - unacknowledge_message: Option, + pub(crate) registered_responder: Arc, + pub(crate) resource: String, + pub(crate) token: Vec, + pub(crate) unacknowledge_message: Option, } #[derive(Debug)] @@ -57,13 +56,12 @@ struct UnacknowledgeMessageItem { impl Observer { /// Creates an observer with channel to send message. - pub fn new(tx_sender: MessageSender) -> Observer { - Observer { + pub fn new() -> Self { + Self { registers: HashMap::new(), resources: HashMap::new(), register_resources: HashMap::new(), unacknowledge_messages: HashMap::new(), - tx_sender: tx_sender, current_message_id: 0, timer: IntervalStream::new(interval(Duration::from_secs(1))).fuse(), } @@ -74,8 +72,13 @@ impl Observer { self.timer.select_next_some() } - /// filter the requests belong to the observer. - pub async fn request_handler(&mut self, request: &CoapRequest) -> bool { + /// filter the requests belong to the observer. store the responder in case it is needed + /// returns whether the request should be forwarded to the handler + pub async fn request_handler( + &mut self, + request: &mut CoapRequest, + responder: Arc, + ) -> bool { if request.message.header.get_type() == MessageType::Acknowledgement { self.acknowledge(request); return false; @@ -84,7 +87,7 @@ impl Observer { match (request.get_method(), request.get_observe_flag()) { (&Method::Get, Some(observe_option)) => match observe_option { Ok(ObserveOption::Register) => { - self.register(request).await; + self.register(request, responder).await; return false; } Ok(ObserveOption::Deregister) => { @@ -120,32 +123,38 @@ impl Observer { } } - async fn register(&mut self, request: &CoapRequest) { - let register_address = request.source.unwrap(); + async fn register( + &mut self, + request: &mut CoapRequest, + responder: Arc, + ) { + let register_address = responder.address(); let resource_path = request.get_path(); debug!("register {} {}", register_address, resource_path); // reply NotFound if resource doesn't exist if !self.resources.contains_key(&resource_path) { - if let Some(ref response) = request.response { + if let Some(ref response) = request.response.take() { let mut response2 = response.clone(); response2.set_status(Status::NotFound); - self.send_message(®ister_address, &response2.message) - .await; + let msg_serial = response2.message.to_bytes(); + if let Ok(b) = msg_serial { + responder.respond(b).await; + } } return; } self.record_register_resource( - ®ister_address, + responder.clone(), &resource_path, &request.message.get_token(), ); let resource = self.resources.get(&resource_path).unwrap(); - if let Some(ref response) = request.response { + if let Some(response) = request.response.take() { let mut response2 = response.clone(); response2.message.payload = resource.payload.clone(); response2.message.set_observe_value(resource.sequence); @@ -153,8 +162,9 @@ impl Observer { .message .header .set_type(MessageType::NonConfirmable); - self.send_message(®ister_address, &response2.message) - .await; + if let Ok(b) = response2.message.to_bytes() { + responder.respond(b).await; + } } } @@ -202,15 +212,21 @@ impl Observer { ); } - fn record_register_resource(&mut self, address: &SocketAddr, path: &String, token: &[u8]) { + fn record_register_resource( + &mut self, + responder: Arc, + path: &String, + token: &[u8], + ) { let resource = self.resources.get_mut(path).unwrap(); - let register_key = Self::format_register(&address); - let register_resource_key = Self::format_register_resource(&address, path); + let register_key = responder; + + let register_resource_key = Self::format_register_resource(®ister_key.address(), path); self.register_resources .entry(register_resource_key.clone()) .or_insert(RegisterResourceItem { - register: register_key.clone(), + registered_responder: register_key.clone(), resource: path.clone(), token: token.into(), unacknowledge_message: None, @@ -218,7 +234,7 @@ impl Observer { resource .register_resources .replace(register_resource_key.clone()); - match self.registers.entry(register_key) { + match self.registers.entry(register_key.address().to_string()) { Entry::Occupied(register) => { register .into_mut() @@ -266,7 +282,10 @@ impl Observer { let remove_register; { - let register = self.registers.get_mut(®ister_resource.register).unwrap(); + let register = self + .registers + .get_mut(®ister_resource.registered_responder.address().to_string()) + .unwrap(); assert_eq!( register.register_resources.remove(®ister_resource_key), true @@ -275,7 +294,8 @@ impl Observer { } if remove_register { - self.registers.remove(®ister_resource.register); + self.registers + .remove(®ister_resource.registered_responder.address().to_string()); } } @@ -378,25 +398,17 @@ impl Observer { message.header.set_type(MessageType::Confirmable); message.header.code = MessageClass::Response(Status::Content); - let address: SocketAddr; - { - let register_resource = self.register_resources.get(register_resource_key).unwrap(); - let resource = self.resources.get(®ister_resource.resource).unwrap(); - - message.set_token(register_resource.token.clone()); - message.set_observe_value(resource.sequence); - message.header.message_id = message_id; - message.payload = resource.payload.clone(); + let register_resource = self.register_resources.get(register_resource_key).unwrap(); + let resource = self.resources.get(®ister_resource.resource).unwrap(); - address = register_resource.register.parse().unwrap(); + message.set_token(register_resource.token.clone()); + message.set_observe_value(resource.sequence); + message.header.message_id = message_id; + message.payload = resource.payload.clone(); + if let Ok(b) = message.to_bytes() { + debug!("notify register with newest resource {:?}", &b); + register_resource.registered_responder.respond(b).await; } - - self.send_message(&address, &message).await; - } - - async fn send_message(&mut self, address: &SocketAddr, message: &Packet) { - debug!("send_message {:?} {:?}", address, message); - self.tx_sender.send((message.clone(), *address)).unwrap(); } fn gen_message_id(&mut self) -> u16 { @@ -404,10 +416,6 @@ impl Observer { return self.current_message_id; } - fn format_register(address: &SocketAddr) -> String { - format!("{}", address) - } - fn format_register_resource(address: &SocketAddr, path: &String) -> String { format!("{}${}", address, path) } @@ -415,12 +423,15 @@ impl Observer { #[cfg(test)] mod test { + use super::super::*; use super::*; - use coap_lite::CoapResponse; - use std::{io::ErrorKind, sync::mpsc, time::Duration}; + use std::{io::ErrorKind, time::Duration}; + use tokio::sync::mpsc; - async fn request_handler(req: CoapRequest) -> Option { + async fn request_handler( + mut req: Box>, + ) -> Box> { match req.get_method() { &coap_lite::RequestType::Get => { let observe_option = req.get_observe_flag().unwrap().unwrap(); @@ -431,38 +442,40 @@ mod test { } match req.response { - Some(mut response) => { + Some(ref mut response) => { response.message.payload = b"OK".to_vec(); - Some(response) } - _ => None, - } + _ => {} + }; + return req; } - #[test] - fn test_observe() { + #[tokio::test] + async fn test_observe() { let path = "/test"; let payload1 = b"data1".to_vec(); let payload2 = b"data2".to_vec(); - let (tx, rx) = mpsc::channel(); - let (tx2, rx2) = mpsc::channel(); + let (tx, mut rx) = mpsc::unbounded_channel(); + let (tx2, mut rx2) = mpsc::unbounded_channel(); let mut step = 1; let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) .recv() + .await .unwrap(); let server_address = &format!("127.0.0.1:{}", server_port); - let mut client = CoAPClient::new(server_address).unwrap(); + let client = UdpCoAPClient::new_udp(server_address).await.unwrap(); tx.send(step).unwrap(); let mut request = CoapRequest::new(); + request.set_method(coap_lite::RequestType::Put); request.set_path(path); request.message.payload = payload1.clone(); - client.send(&request).unwrap(); - client.receive().unwrap(); + client.send(&request).await.unwrap(); + client.receive().await.unwrap(); let payload1_clone = payload1.clone(); let payload2_clone = payload2.clone(); @@ -472,8 +485,9 @@ mod test { .observe(path, move |msg| { match rx.try_recv() { Ok(n) => receive_step = n, - _ => (), + _ => debug!("receive_step rx error"), } + debug!("receive on client: {:?}", &msg); match receive_step { 1 => assert_eq!(msg.payload, payload1_clone), @@ -484,66 +498,76 @@ mod test { _ => panic!("unexpected step"), } }) + .await .unwrap(); step = 2; + debug!("on step 2"); tx.send(step).unwrap(); request.message.payload = payload2.clone(); - let client2 = CoAPClient::new(server_address).unwrap(); - client2.send(&request).unwrap(); - client2.receive().unwrap(); - assert_eq!(rx2.recv_timeout(Duration::new(5, 0)).unwrap(), ()); + let client2 = UdpCoAPClient::new_udp(server_address).await.unwrap(); + client2.send(&request).await.unwrap(); + client2.receive().await.unwrap(); + assert_eq!( + tokio::time::timeout(Duration::new(5, 0), rx2.recv()) + .await + .unwrap(), + Some(()) + ); } - - #[test] - fn test_unobserve() { + #[tokio::test] + async fn test_unobserve() { let path = "/test"; let payload1 = b"data1".to_vec(); let payload2 = b"data2".to_vec(); let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) .recv() + .await .unwrap(); let server_address = &format!("127.0.0.1:{}", server_port); - let mut client = CoAPClient::new(server_address).unwrap(); + let client = UdpCoAPClient::new_udp(server_address).await.unwrap(); let mut request = CoapRequest::new(); request.set_method(coap_lite::RequestType::Put); request.set_path(path); request.message.payload = payload1.clone(); - client.send(&request).unwrap(); - client.receive().unwrap(); + client.send(&request).await.unwrap(); + client.receive().await.unwrap(); let payload1_clone = payload1.clone(); - client + let unobserve = client .observe(path, move |msg| { assert_eq!(msg.payload, payload1_clone); }) + .await .unwrap(); - client.unobserve(); - + unobserve.send(client::ObserveMessage::Terminate).unwrap(); request.message.payload = payload2.clone(); - let client3 = CoAPClient::new(server_address).unwrap(); - client3.send(&request).unwrap(); - client3.receive().unwrap(); + let client3 = UdpCoAPClient::new_udp(server_address).await.unwrap(); + client3.send(&request).await.unwrap(); + client3.receive().await.unwrap(); } - #[test] - fn test_observe_without_resource() { + #[tokio::test] + async fn test_observe_without_resource() { let path = "/test"; let server_port = server::test::spawn_server("127.0.0.1:0", request_handler) .recv() + .await .unwrap(); - let mut client = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); - let error = client.observe(path, |_msg| {}).unwrap_err(); + let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap(); + let error = client.observe(path, |_msg| {}).await.unwrap_err(); assert_eq!(error.kind(), ErrorKind::NotFound); } } diff --git a/src/server.rs b/src/server.rs index 91b73798f..8dec399c9 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,28 +1,25 @@ -use coap_lite::{ - error::HandlingError, BlockHandler, BlockHandlerConfig, CoapRequest, CoapResponse, Packet, -}; -use futures::{select, stream::FusedStream, task::Poll, SinkExt, Stream, StreamExt}; -use log::{debug, error}; +use async_trait::async_trait; +use coap_lite::{BlockHandler, BlockHandlerConfig, CoapRequest, CoapResponse, Packet}; +use log::debug; use std::{ self, future::Future, + io::ErrorKind, net::{self, IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}, - pin::Pin, - task::Context, + sync::Arc, }; use tokio::{ io, net::UdpSocket, - sync::mpsc::{self}, + select, + sync::{ + mpsc::{self, UnboundedReceiver, UnboundedSender}, + Mutex, + }, + task::JoinHandle, }; -use tokio_stream::wrappers::UnboundedReceiverStream; -use tokio_util::udp::UdpFramed; - -use super::message::Codec; -use super::observer::Observer; -pub type MessageSender = mpsc::UnboundedSender<(Packet, SocketAddr)>; -type MessageReceiver = UnboundedReceiverStream<(Packet, SocketAddr)>; +use crate::observer::Observer; #[derive(Debug)] pub enum CoAPServerError { @@ -32,197 +29,88 @@ pub enum CoAPServerError { EventSendError, } -#[derive(Debug)] -pub struct QueuedMessage { - pub address: SocketAddr, - pub message: Packet, -} +use tokio::io::Error; -pub enum Message { - NeedSend(Packet, SocketAddr), - Received(Packet, SocketAddr), +#[async_trait] +pub trait Dispatcher: Send + Sync { + async fn dispatch(&self, request: CoapRequest) -> Option; } -pub struct Server<'a, HandlerRet> -where - HandlerRet: Future>, -{ - server: CoAPServer, - observer: Observer, - block_handler: BlockHandler, - handler: Option) -> HandlerRet + Send + 'a>>, +#[async_trait] +/// This trait represents a generic way to respond to a listener. If you want to implement your own +/// listener, you have to implement this trait to be able to send responses back through the +/// correct transport +pub trait Responder: Sync + Send { + async fn respond(&self, response: Vec); + fn address(&self) -> SocketAddr; } -impl<'a, HandlerRet> Server<'a, HandlerRet> -where - HandlerRet: Future>, -{ - /// Creates a CoAP server listening on the given address. - pub fn new(addr: A) -> Result { - let (tx, rx) = mpsc::unbounded_channel(); - Ok(Server { - server: CoAPServer::new(addr, rx)?, - observer: Observer::new(tx), - block_handler: BlockHandler::new(BlockHandlerConfig::default()), - handler: None, - }) - } - - /// run the server. - pub async fn run) -> HandlerRet + Send + 'a>( - &mut self, - handler: F, - ) -> Result<(), io::Error> { - self.handler = Some(Box::new(handler)); - - loop { - select! { - message = self.server.select_next_some() => { - match message { - Ok(Message::NeedSend(packet, addr)) => { - self.send_msg(packet, addr).await?; - } - Ok(Message::Received(packet, addr)) => { - self.dispatch_msg(packet, addr).await?; - } - Err(e) => { - error!("select error: {:?}", e); - } - } - } - _ = self.observer.select_next_some() => { - self.observer.timer_handler().await; - } - complete => break, - } - } - Ok(()) - } - - /// Return the local address that the server is listening on. This can be useful when starting - /// a server on a random port as part of unit testing. - pub fn socket_addr(&self) -> std::io::Result { - self.server.socket_addr() - } +/// channel to send new requests from a transport to the CoAP server +pub type TransportRequestSender = UnboundedSender<(Vec, Arc)>; - async fn send_msg(&mut self, packet: Packet, addr: SocketAddr) -> Result<(), io::Error> { - let mut request = CoapRequest::from_packet(Packet::new(), addr); - request.response = CoapResponse::new(&packet); - match self.block_handler.intercept_response(&mut request) { - Err(err) => { - if self.handle_coap_handing_error(&mut request, err) { - return self - .server - .send((request.response.unwrap().message, addr)) - .await; - } - return Ok(()); - } - Ok(true) => { - return self - .server - .send((request.response.unwrap().message, addr)) - .await; - } - _ => { - return self.server.send((packet, addr)).await; - } - } - } +/// channel used by CoAP server to receive new requests +pub type TransportRequestReceiver = UnboundedReceiver<(Vec, Arc)>; - async fn dispatch_msg(&mut self, packet: Packet, addr: SocketAddr) -> Result<(), io::Error> { - let mut request = CoapRequest::from_packet(packet, addr); +type UdpResponseReceiver = UnboundedReceiver<(Vec, SocketAddr)>; +type UdpResponseSender = UnboundedSender<(Vec, SocketAddr)>; - match self.block_handler.intercept_request(&mut request) { - Ok(true) => { - self.server - .send((request.response.unwrap().message, addr)) - .await?; - return Ok(()); - } - Err(err) => { - if self.handle_coap_handing_error(&mut request, err) { - self.server - .send((request.response.unwrap().message, addr)) - .await?; - } - return Ok(()); - } - Ok(false) => {} - } +// listeners receive new connections +#[async_trait] +pub trait Listener: Send { + async fn listen( + self: Box, + sender: TransportRequestSender, + ) -> std::io::Result>>; +} +/// listener for a UDP socket +pub struct UdpCoapListener { + socket: UdpSocket, + multicast_addresses: Vec, + response_receiver: UdpResponseReceiver, + response_sender: UdpResponseSender, +} - let filtered = !self.observer.request_handler(&request).await; - if filtered { - return Ok(()); - } +#[async_trait] +/// A trait for handling incoming requests. Use this instead of a closure +/// if you want to modify some external state +pub trait RequestHandler: Send + Sync + 'static { + async fn handle_request( + &self, + mut request: Box>, + ) -> Box>; +} - if let Some(ref mut handler) = self.handler { - match handler(request.clone()).await { - Some(response) => { - debug!("Response: {:?}", response); - request.response = Some(response); - match self.block_handler.intercept_response(&mut request) { - Err(err) => { - if self.handle_coap_handing_error(&mut request, err) { - self.server - .send((request.response.unwrap().message, addr)) - .await?; - } - return Ok(()); - } - _ => {} - } - self.server - .send((request.response.unwrap().message, addr)) - .await?; - } - None => { - debug!("No response"); - } - } - } - Ok(()) +#[async_trait] +impl RequestHandler for F +where + F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, + HandlerRet: Future>> + Send, +{ + async fn handle_request( + &self, + request: Box>, + ) -> Box> { + self(request).await } +} - fn handle_coap_handing_error( - &mut self, - request: &mut CoapRequest, - err: HandlingError, - ) -> bool { - if request.apply_from_error(err) { - // If the error happens to need block2 handling, let's do that here... - let _ = self.block_handler.intercept_response(request); - return true; - } - false +/// A listener for UDP packets. This listener can also subscribe to multicast addresses +impl UdpCoapListener { + pub fn new(addr: A) -> Result { + let std_socket = net::UdpSocket::bind(addr)?; + std_socket.set_nonblocking(true)?; + let socket = UdpSocket::from_std(std_socket)?; + Ok(Self::from_socket(socket)) } - /// enable AllCoAP multicasts - adds the AllCoap addresses to the listener - /// - IPv4 AllCoAP multicast address is '224.0.1.187' - /// - IPv6 AllCoAp multicast addresses are 'ff0?::fd' - /// - /// Parameter segment is used with IPv6 to determine the first octet. - /// - It's value can be between 0x0 and 0xf. - /// - To join multiple segments, you have to call enable_discovery for each of the segments. - /// - /// For further details see method join_multicast - pub fn enable_all_coap(&mut self, segment: u8) { - assert!(segment <= 0xf); - let socket = self.server.socket.get_mut(); - let m = match socket.local_addr().unwrap() { - SocketAddr::V4(_val) => IpAddr::V4(Ipv4Addr::new(224, 0, 1, 187)), - SocketAddr::V6(_val) => IpAddr::V6(Ipv6Addr::new( - 0xff00 + segment as u16, - 0, - 0, - 0, - 0, - 0, - 0, - 0xfd, - )), - }; - self.join_multicast(m); + pub fn from_socket(socket: tokio::net::UdpSocket) -> Self { + let (tx, rx) = mpsc::unbounded_channel(); + Self { + socket, + multicast_addresses: Vec::new(), + response_receiver: rx, + response_sender: tx, + } } /// join multicast - adds the multicast addresses to the unicast listener @@ -249,70 +137,19 @@ where /// ff0x::fb Multicast DNS /// ff0x::fb Multicast CoAP /// ff0x::114 Used for experiments - pub fn join_multicast(&mut self, addr: IpAddr) { - self.server.join_multicast(addr); - } - - /// leave multicast - remove the multicast address from the listener - pub fn leave_multicast(&mut self, addr: IpAddr) { - self.server.leave_multicast(addr); - } -} - -pub struct CoAPServer { - receiver: MessageReceiver, - is_terminated: bool, - socket: UdpFramed, - multicast_addresses: Vec, -} - -impl CoAPServer { - /// Creates a CoAP server listening on the given address. - pub fn new( - addr: A, - rx: mpsc::UnboundedReceiver<(Packet, SocketAddr)>, - ) -> Result { - let std_socket = net::UdpSocket::bind(addr).unwrap(); - std_socket.set_nonblocking(true)?; - - let socket = UdpSocket::from_std(std_socket)?; - - Ok(CoAPServer { - receiver: UnboundedReceiverStream::new(rx), - is_terminated: false, - socket: UdpFramed::new(socket, Codec::new()), - multicast_addresses: Vec::new(), - }) - } - - /// Stop the server. - pub fn stop(&mut self) { - self.is_terminated = true; - } - - /// send the packet to the specific address. - pub async fn send(&mut self, frame: (Packet, SocketAddr)) -> Result<(), io::Error> { - self.socket.send(frame).await - } - - /// Return the local address that the server is listening on. This can be useful when starting - /// a server on a random port as part of unit testing. - pub fn socket_addr(&self) -> std::io::Result { - self.socket.get_ref().local_addr() - } - - /// join multicast - adds the multicast addresses to the listener + // pub fn join_multicast(&mut self, addr: IpAddr) { + // self.udp_server.join_multicast(addr); + // } pub fn join_multicast(&mut self, addr: IpAddr) { assert!(addr.is_multicast()); - let socket = self.socket.get_mut(); // determine wether IPv4 or IPv6 and // join the appropriate multicast address - match socket.local_addr().unwrap() { + match self.socket.local_addr().unwrap() { SocketAddr::V4(val) => { match addr { IpAddr::V4(ipv4) => { let i = val.ip().clone(); - socket.join_multicast_v4(ipv4, i).unwrap(); + self.socket.join_multicast_v4(ipv4, i).unwrap(); self.multicast_addresses.push(addr); } IpAddr::V6(_ipv6) => { /* handle IPv6 */ } @@ -322,9 +159,9 @@ impl CoAPServer { match addr { IpAddr::V4(_ipv4) => { /* handle IPv4 */ } IpAddr::V6(ipv6) => { - socket.join_multicast_v6(&ipv6, 0).unwrap(); + self.socket.join_multicast_v6(&ipv6, 0).unwrap(); self.multicast_addresses.push(addr); - //socket.set_only_v6(true)?; + //self.socket.set_only_v6(true)?; } } } @@ -334,15 +171,14 @@ impl CoAPServer { /// leave multicast - remove the multicast address from the listener pub fn leave_multicast(&mut self, addr: IpAddr) { assert!(addr.is_multicast()); - let socket = self.socket.get_mut(); // determine wether IPv4 or IPv6 and // leave the appropriate multicast address - match socket.local_addr().unwrap() { + match self.socket.local_addr().unwrap() { SocketAddr::V4(val) => { match addr { IpAddr::V4(ipv4) => { let i = val.ip().clone(); - socket.leave_multicast_v4(ipv4, i).unwrap(); + self.socket.leave_multicast_v4(ipv4, i).unwrap(); let index = self .multicast_addresses .iter() @@ -357,7 +193,7 @@ impl CoAPServer { match addr { IpAddr::V4(_ipv4) => { /* handle IPv4 */ } IpAddr::V6(ipv6) => { - socket.leave_multicast_v6(&ipv6, 0).unwrap(); + self.socket.leave_multicast_v6(&ipv6, 0).unwrap(); let index = self .multicast_addresses .iter() @@ -369,56 +205,226 @@ impl CoAPServer { } } } + /// enable AllCoAP multicasts - adds the AllCoap addresses to the listener + /// - IPv4 AllCoAP multicast address is '224.0.1.187' + /// - IPv6 AllCoAp multicast addresses are 'ff0?::fd' + /// + /// Parameter segment is used with IPv6 to determine the first octet. + /// - It's value can be between 0x0 and 0xf. + /// - To join multiple segments, you have to call enable_discovery for each of the segments. + /// + /// For further details see method join_multicast + pub fn enable_all_coap(&mut self, segment: u8) { + assert!(segment <= 0xf); + let m = match self.socket.local_addr().unwrap() { + SocketAddr::V4(_val) => IpAddr::V4(Ipv4Addr::new(224, 0, 1, 187)), + SocketAddr::V6(_val) => IpAddr::V6(Ipv6Addr::new( + 0xff00 + segment as u16, + 0, + 0, + 0, + 0, + 0, + 0, + 0xfd, + )), + }; + self.join_multicast(m); + } +} +#[async_trait] +impl Listener for UdpCoapListener { + async fn listen( + mut self: Box, + sender: TransportRequestSender, + ) -> std::io::Result>> { + return Ok(tokio::spawn(self.receive_loop(sender))); + } } -impl Drop for CoAPServer { - fn drop(&mut self) { - // unregister still existing multicast addresses - let socket = self.socket.get_mut(); - for addr in &self.multicast_addresses { - match addr { - IpAddr::V4(ipv4) => match socket.local_addr().unwrap() { - SocketAddr::V4(val) => { - socket.leave_multicast_v4(*ipv4, val.ip().clone()).unwrap(); - } - _ => { - panic!("should not happen"); +#[derive(Clone)] +struct UdpResponder { + address: SocketAddr, // this is the address we are sending to + tx: UdpResponseSender, +} + +#[async_trait] +impl Responder for UdpResponder { + async fn respond(&self, response: Vec) { + let _ = self.tx.send((response, self.address)); + } + fn address(&self) -> SocketAddr { + self.address + } +} + +impl UdpCoapListener { + pub async fn receive_loop(mut self, sender: TransportRequestSender) -> std::io::Result<()> { + loop { + let mut recv_vec = Vec::with_capacity(u16::MAX as usize); + select! { + message =self.socket.recv_buf_from(&mut recv_vec)=> { + match message { + Ok((_size, from)) => { + sender.send((recv_vec, Arc::new(UdpResponder{address: from, tx: self.response_sender.clone()}))).map_err( |_| std::io::Error::new(ErrorKind::Other, "server channel error"))?; + } + Err(e) => { + return Err(e); + } } }, - IpAddr::V6(ipv6) => { - socket.leave_multicast_v6(&ipv6, 0).unwrap(); + response = self.response_receiver.recv() => { + if let Some((bytes, to)) = response{ + debug!("sending {:?} to {:?}", &bytes, &to); + self.socket.send_to(&bytes, to).await?; + } + else { + // in case nobody is listening to us, we can just terminate, though this + // should never happen for UDP + return Ok(()); + } + } } } - // stop server - self.stop(); } } -impl Stream for CoAPServer { - type Item = Result; +#[derive(Debug)] +pub struct QueuedMessage { + pub address: SocketAddr, + pub message: Packet, +} + +struct ServerCoapState { + observer: Observer, + block_handler: BlockHandler, +} - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if let Poll::Ready(Some((p, a))) = self.receiver.poll_next_unpin(cx) { - return Poll::Ready(Some(Ok(Message::NeedSend(p, a)))); - } +pub enum ShouldForwardToHandler { + True, + False, +} - let result: Option<_> = futures::ready!(self.socket.poll_next_unpin(cx)); +impl ServerCoapState { + pub async fn intercept_request( + &mut self, + request: &mut CoapRequest, + responder: Arc, + ) -> ShouldForwardToHandler { + match self.block_handler.intercept_request(request) { + Ok(true) => return ShouldForwardToHandler::False, + Err(_err) => return ShouldForwardToHandler::False, + Ok(false) => {} + }; + let should_be_forwarded = self.observer.request_handler(request, responder).await; + if should_be_forwarded { + return ShouldForwardToHandler::True; + } else { + return ShouldForwardToHandler::False; + } + } - Poll::Ready(match result { - Some(Ok(message)) => { - let (my_packet, addr) = message; - Some(Ok(Message::Received(my_packet, addr))) + pub async fn intercept_response(&mut self, request: &mut CoapRequest) { + match self.block_handler.intercept_response(request) { + Err(err) => { + let _ = request.apply_from_error(err); } - Some(Err(e)) => Some(Err(e)), - None => None, - }) + _ => {} + } + } + pub fn new() -> Self { + Self { + observer: Observer::new(), + block_handler: BlockHandler::new(BlockHandlerConfig::default()), + } } } -impl FusedStream for CoAPServer { - fn is_terminated(&self) -> bool { - self.is_terminated +pub struct Server { + listeners: Vec>, + coap_state: Arc>, + new_packet_receiver: TransportRequestReceiver, + new_packet_sender: TransportRequestSender, +} + +impl Server { + /// Creates a CoAP server listening on the given address. + pub fn new_udp(addr: A) -> Result { + let listener: Vec> = vec![Box::new(UdpCoapListener::new(addr)?)]; + Ok(Self::from_listeners(listener)) + } + + pub fn from_listeners(listeners: Vec>) -> Self { + let (tx, rx) = mpsc::unbounded_channel(); + Server { + listeners, + coap_state: Arc::new(Mutex::new(ServerCoapState::new())), + new_packet_receiver: rx, + new_packet_sender: tx, + } + } + + async fn spawn_handles( + listeners: Vec>, + sender: TransportRequestSender, + ) -> std::io::Result>>> { + let mut handles = vec![]; + for listener in listeners.into_iter() { + let handle = listener.listen(sender.clone()).await?; + handles.push(handle); + } + return Ok(handles); + } + + /// run the server. + pub async fn run(mut self, handler: Handler) -> Result<(), io::Error> { + let _handles = Self::spawn_handles(self.listeners, self.new_packet_sender.clone()).await?; + + let handler_arc = Arc::new(handler); + // receive an input, sync our cache / states, then call custom handler + loop { + let (bytes, respond) = + self.new_packet_receiver.recv().await.ok_or_else(|| { + std::io::Error::new(ErrorKind::Other, "listen channel closed") + })?; + if let Ok(packet) = Packet::from_bytes(&bytes) { + let mut request = Box::new(CoapRequest::::from_packet( + packet, + respond.address(), + )); + let mut coap_state = self.coap_state.lock().await; + let should_forward = coap_state + .intercept_request(&mut request, respond.clone()) + .await; + + match should_forward { + ShouldForwardToHandler::True => { + let handler_clone = handler_arc.clone(); + let coap_state_clone = self.coap_state.clone(); + tokio::spawn(async move { + request = handler_clone.handle_request(request).await; + coap_state_clone + .lock() + .await + .intercept_response(request.as_mut()) + .await; + + Self::respond_to_request(request, respond).await; + }); + } + ShouldForwardToHandler::False => { + Self::respond_to_request(request, respond).await; + } + } + } + } + } + async fn respond_to_request(req: Box>, responder: Arc) { + // if we have some reponse to send, send it + if let Some(Ok(b)) = req.response.map(|resp| resp.message.to_bytes()) { + responder.respond(b).await; + } } } @@ -427,63 +433,58 @@ pub mod test { use super::super::*; use super::*; use coap_lite::{block_handler::BlockValue, CoapOption, RequestType}; - use std::{sync::mpsc, time::Duration}; + use std::time::Duration; pub fn spawn_server< - F: FnMut(CoapRequest) -> HandlerRet + Send + 'static, + F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, HandlerRet, >( ip: &'static str, request_handler: F, - ) -> mpsc::Receiver + ) -> mpsc::UnboundedReceiver where - HandlerRet: Future>, + HandlerRet: Future>> + Send, { - let (tx, rx) = mpsc::channel(); - - std::thread::Builder::new() - .name(String::from("server")) - .spawn(move || { - tokio::runtime::Runtime::new() - .unwrap() - .block_on(async move { - let mut server = server::Server::new(ip).unwrap(); - - tx.send(server.socket_addr().unwrap().port()).unwrap(); - - server.run(request_handler).await.unwrap(); - }) - }) - .unwrap(); + let (tx, rx) = mpsc::unbounded_channel(); + let _task = tokio::spawn(async move { + let sock = UdpSocket::bind(ip).await.unwrap(); + let addr = sock.local_addr().unwrap(); + let listener = Box::new(UdpCoapListener::from_socket(sock)); + let server = Server::from_listeners(vec![listener]); + tx.send(addr.port()).unwrap(); + server.run(request_handler).await.unwrap(); + }); rx } - async fn request_handler(req: CoapRequest) -> Option { + async fn request_handler( + mut req: Box>, + ) -> Box> { let uri_path_list = req.message.get_option(CoapOption::UriPath).unwrap().clone(); assert_eq!(uri_path_list.len(), 1); match req.response { - Some(mut response) => { + Some(ref mut response) => { response.message.payload = uri_path_list.front().unwrap().clone(); - Some(response) } - _ => None, + _ => {} } + return req; } pub fn spawn_server_with_all_coap< - F: FnMut(CoapRequest) -> HandlerRet + Send + 'static, + F: Fn(Box>) -> HandlerRet + Send + Sync + 'static, HandlerRet, >( ip: &'static str, request_handler: F, segment: u8, - ) -> mpsc::Receiver + ) -> mpsc::UnboundedReceiver where - HandlerRet: Future>, + HandlerRet: Future>> + Send, { - let (tx, rx) = mpsc::channel(); + let (tx, rx) = mpsc::unbounded_channel(); std::thread::Builder::new() .name(String::from("v4-server")) @@ -492,11 +493,12 @@ pub mod test { .unwrap() .block_on(async move { // multicast needs a server on a real interface - let mut server = server::Server::new((ip, 0)).unwrap(); - server.enable_all_coap(segment); - - tx.send(server.socket_addr().unwrap().port()).unwrap(); - + let sock = UdpSocket::bind((ip, 0)).await.unwrap(); + let addr = sock.local_addr().unwrap(); + let mut listener = Box::new(UdpCoapListener::from_socket(sock)); + listener.enable_all_coap(segment); + let server = Server::from_listeners(vec![listener]); + tx.send(addr.port()).unwrap(); server.run(request_handler).await.unwrap(); }) }) @@ -505,11 +507,16 @@ pub mod test { rx } - #[test] - fn test_echo_server() { - let server_port = spawn_server("127.0.0.1:0", request_handler).recv().unwrap(); + #[tokio::test] + async fn test_echo_server() { + let server_port = spawn_server("127.0.0.1:0", request_handler) + .recv() + .await + .unwrap(); - let client = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap(); let mut request = CoapRequest::new(); request.message.header.set_version(1); request @@ -522,15 +529,18 @@ pub mod test { request .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send(&request).unwrap(); + client.send(&request).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); } - #[test] - fn test_put_block() { - let server_port = spawn_server("127.0.0.1:0", request_handler).recv().unwrap(); + #[tokio::test] + async fn test_put_block() { + let server_port = spawn_server("127.0.0.1:0", request_handler) + .recv() + .await + .unwrap(); let data = "hello this is a payload"; let mut v = Vec::new(); for _ in 0..1024 { @@ -538,7 +548,7 @@ pub mod test { } let payload_size = v.len(); let server_string = format!("127.0.0.1:{}", server_port); - let mut client = CoAPClient::new(server_string.clone()).unwrap(); + let mut client = UdpCoAPClient::new_udp(server_string.clone()).await.unwrap(); let resp = client .request_path( @@ -548,6 +558,7 @@ pub mod test { None, Some(server_string.clone()), ) + .await .unwrap(); //assert_eq!(resp.message.payload, b"Created".to_vec()); let block_opt = resp @@ -565,12 +576,13 @@ pub mod test { assert_eq!(resp.message.payload, b"large".to_vec()); } - #[test] - #[ignore] - fn test_echo_server_v6() { - let server_port = spawn_server("::1:0", request_handler).recv().unwrap(); + #[tokio::test] + async fn test_echo_server_v6() { + let server_port = spawn_server("::1:0", request_handler).recv().await.unwrap(); - let client = CoAPClient::new(format!("::1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("::1:{}", server_port)) + .await + .unwrap(); let mut request = CoapRequest::new(); request.message.header.set_version(1); request @@ -583,17 +595,22 @@ pub mod test { request .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send(&request).unwrap(); + client.send(&request).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); } - #[test] - fn test_echo_server_no_token() { - let server_port = spawn_server("127.0.0.1:0", request_handler).recv().unwrap(); + #[tokio::test] + async fn test_echo_server_no_token() { + let server_port = spawn_server("127.0.0.1:0", request_handler) + .recv() + .await + .unwrap(); - let client = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap(); let mut packet = CoapRequest::new(); packet.message.header.set_version(1); packet @@ -605,18 +622,20 @@ pub mod test { packet .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send(&packet).unwrap(); + client.send(&packet).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); } - #[test] + #[tokio::test] #[ignore] - fn test_echo_server_no_token_v6() { - let server_port = spawn_server("::1:0", request_handler).recv().unwrap(); + async fn test_echo_server_no_token_v6() { + let server_port = spawn_server("::1:0", request_handler).recv().await.unwrap(); - let client = CoAPClient::new(format!("::1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("::1:{}", server_port)) + .await + .unwrap(); let mut packet = CoapRequest::new(); packet.message.header.set_version(1); packet @@ -628,32 +647,37 @@ pub mod test { packet .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send(&packet).unwrap(); + client.send(&packet).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); } - #[test] - fn test_update_resource() { + #[tokio::test] + async fn test_update_resource() { let path = "/test"; let payload1 = b"data1".to_vec(); let payload2 = b"data2".to_vec(); - let (tx, rx) = mpsc::channel(); - let (tx2, rx2) = mpsc::channel(); + let (tx, mut rx) = mpsc::unbounded_channel(); + let (tx2, mut rx2) = mpsc::unbounded_channel(); let mut step = 1; - let server_port = spawn_server("127.0.0.1:0", request_handler).recv().unwrap(); + let server_port = spawn_server("127.0.0.1:0", request_handler) + .recv() + .await + .unwrap(); - let mut client = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap(); tx.send(step).unwrap(); let mut request = CoapRequest::new(); request.set_method(RequestType::Put); request.set_path(path); request.message.payload = payload1.clone(); - client.send(&request).unwrap(); - client.receive().unwrap(); + client.send(&request).await.unwrap(); + client.receive().await.unwrap(); let mut receive_step = 1; let payload1_clone = payload1.clone(); @@ -674,26 +698,37 @@ pub mod test { _ => panic!("unexpected step"), } }) + .await .unwrap(); step = 2; tx.send(step).unwrap(); request.message.payload = payload2.clone(); - let client2 = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); - client2.send(&request).unwrap(); - client2.receive().unwrap(); - assert_eq!(rx2.recv_timeout(Duration::new(5, 0)).unwrap(), ()); + let client2 = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap(); + client2.send(&request).await.unwrap(); + client2.receive().await.unwrap(); + assert_eq!( + tokio::time::timeout(Duration::new(5, 0), rx2.recv()) + .await + .unwrap(), + Some(()) + ); } - #[test] - fn multicast_server_all_coap() { + #[tokio::test] + async fn multicast_server_all_coap() { // segment not relevant with IPv4 let segment = 0x0; let server_port = spawn_server_with_all_coap("0.0.0.0", request_handler, segment) .recv() + .await .unwrap(); - let client = CoAPClient::new(format!("127.0.0.1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("127.0.0.1:{}", server_port)) + .await + .unwrap(); let mut request = CoapRequest::new(); request.message.header.set_version(1); request @@ -706,12 +741,14 @@ pub mod test { request .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send(&request).unwrap(); + client.send(&request).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); - let client = CoAPClient::new(format!("224.0.1.187:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("224.0.1.187:{}", server_port)) + .await + .unwrap(); let mut request = CoapRequest::new(); request.message.header.set_version(1); request @@ -724,24 +761,27 @@ pub mod test { request .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send_all_coap(&request, segment).unwrap(); + client.send_all_coap(&request, segment).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); } //This test right now does not work on windows #[cfg(unix)] - #[test] + #[tokio::test] #[ignore] - fn multicast_server_all_coap_v6() { + async fn multicast_server_all_coap_v6() { // use segment 0x04 which should be the smallest administered scope let segment = 0x04; let server_port = spawn_server_with_all_coap("::0", request_handler, segment) .recv() + .await .unwrap(); - let client = CoAPClient::new(format!("::1:{}", server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("::1:{}", server_port)) + .await + .unwrap(); let mut request = CoapRequest::new(); request.message.header.set_version(1); request @@ -754,13 +794,15 @@ pub mod test { request .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send(&request).unwrap(); + client.send(&request).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); // use 0xff02 to keep it within this network - let client = CoAPClient::new(format!("ff0{}::fd:{}", segment, server_port)).unwrap(); + let client = UdpCoAPClient::new_udp(format!("ff0{}::fd:{}", segment, server_port)) + .await + .unwrap(); let mut request = CoapRequest::new(); request.message.header.set_version(1); request @@ -773,9 +815,9 @@ pub mod test { request .message .add_option(CoapOption::UriPath, b"test-echo".to_vec()); - client.send_all_coap(&request, segment).unwrap(); + client.send_all_coap(&request, segment).await.unwrap(); - let recv_packet = client.receive().unwrap(); + let recv_packet = client.receive().await.unwrap(); assert_eq!(recv_packet.message.payload, b"test-echo".to_vec()); } @@ -788,11 +830,13 @@ pub mod test { .unwrap() .block_on(async move { // multicast needs a server on a real interface - let mut server = server::Server::new(("0.0.0.0", 0)).unwrap(); - server.join_multicast(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 1))); - server.join_multicast(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))); - server.leave_multicast(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 1))); - server.leave_multicast(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))); + let sock = UdpSocket::bind(("0.0.0.0", 0)).await.unwrap(); + let mut listener = Box::new(UdpCoapListener::from_socket(sock)); + listener.join_multicast(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 1))); + listener.join_multicast(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))); + listener.leave_multicast(IpAddr::V4(Ipv4Addr::new(224, 0, 1, 1))); + listener.leave_multicast(IpAddr::V4(Ipv4Addr::new(224, 1, 1, 1))); + let server = Server::from_listeners(vec![listener]); server.run(request_handler).await.unwrap(); }) }) @@ -810,19 +854,21 @@ pub mod test { .unwrap() .block_on(async move { // multicast needs a server on a real interface - let mut server = server::Server::new(("::0", 0)).unwrap(); - server.join_multicast(IpAddr::V6(Ipv6Addr::new( + let sock = UdpSocket::bind(("0.0.0.0", 0)).await.unwrap(); + let mut listener = Box::new(UdpCoapListener::from_socket(sock)); + listener.join_multicast(IpAddr::V6(Ipv6Addr::new( 0xff02, 0, 0, 0, 0, 0, 1, 0x1, ))); - server.join_multicast(IpAddr::V6(Ipv6Addr::new( + listener.join_multicast(IpAddr::V6(Ipv6Addr::new( 0xff02, 0, 0, 0, 0, 1, 0, 0x2, ))); - server.leave_multicast(IpAddr::V6(Ipv6Addr::new( + listener.leave_multicast(IpAddr::V6(Ipv6Addr::new( 0xff02, 0, 0, 0, 0, 0, 1, 0x1, ))); - server.join_multicast(IpAddr::V6(Ipv6Addr::new( + listener.join_multicast(IpAddr::V6(Ipv6Addr::new( 0xff02, 0, 0, 0, 0, 1, 0, 0x2, ))); + let server = Server::from_listeners(vec![listener]); server.run(request_handler).await.unwrap(); }) }) diff --git a/tests/test_certs/README.md b/tests/test_certs/README.md new file mode 100644 index 000000000..b719cc34a --- /dev/null +++ b/tests/test_certs/README.md @@ -0,0 +1,2 @@ +# Tests Certificates and Keys +keys and certificates generated using openssl and adapted from (webrtc-rs)[https://github.com/webrtc-rs/webrtc/tree/master/dtls/examples/certificates] diff --git a/tests/test_certs/coap_client.csr b/tests/test_certs/coap_client.csr new file mode 100644 index 000000000..881ea01ba --- /dev/null +++ b/tests/test_certs/coap_client.csr @@ -0,0 +1,7 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHHMG8CAQAwDTELMAkGA1UEBhMCTkwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AASGAlUii5DkmnGhhUZXFAbagu/NHKwRxgu2RDdNCXveTIGqHdC3RdwMRgoUUk9t +MrTfK/NoHm4G4G1wjhGVMeVWoAAwCgYIKoZIzj0EAwIDSAAwRQIhAP7gK7OUe64O +z07krukXYMQYCk8e5+ptiVxpvYJK4CYBAiBV7dghREnjXQfqHaTLMhGd+NNK/iIM +YDqyIk56dApSZA== +-----END CERTIFICATE REQUEST----- diff --git a/tests/test_certs/coap_client.pem b/tests/test_certs/coap_client.pem new file mode 100644 index 000000000..961142226 --- /dev/null +++ b/tests/test_certs/coap_client.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIITp0mIkMrvGClVEzxfYC7EYWEjihPeZfZYubOMAC3O6oAoGCCqGSM49 +AwEHoUQDQgAEhgJVIouQ5JpxoYVGVxQG2oLvzRysEcYLtkQ3TQl73kyBqh3Qt0Xc +DEYKFFJPbTK03yvzaB5uBuBtcI4RlTHlVg== +-----END EC PRIVATE KEY----- diff --git a/tests/test_certs/coap_client.pub.pem b/tests/test_certs/coap_client.pub.pem new file mode 100644 index 000000000..12ec7088c --- /dev/null +++ b/tests/test_certs/coap_client.pub.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBYTCCAQegAwIBAgIDAKvNMAoGCCqGSM49BAMCMA0xCzAJBgNVBAYTAk5MMB4X +DTI0MDEwMzAwMjU0NVoXDTI2MDEwMjAwMjU0NVowDTELMAkGA1UEBhMCTkwwWTAT +BgcqhkjOPQIBBggqhkjOPQMBBwNCAASGAlUii5DkmnGhhUZXFAbagu/NHKwRxgu2 +RDdNCXveTIGqHdC3RdwMRgoUUk9tMrTfK/NoHm4G4G1wjhGVMeVWo1YwVDASBgNV +HREECzAJggdjb2FwLnJzMB0GA1UdDgQWBBTUQW4lxsK5oobySmuh09ErNzuS0jAf +BgNVHSMEGDAWgBR111V1OWcCGw1LzOPCSldCTViCATAKBggqhkjOPQQDAgNIADBF +AiEA9fmS39TTwMzFl5vOodbaZkW/AALOuXMl0dwsEM1XCjcCIH0FpCf/oRGVkue8 +muY0CJKbZL96xiq0cokH1LiLyEdK +-----END CERTIFICATE----- diff --git a/tests/test_certs/coap_server.csr b/tests/test_certs/coap_server.csr new file mode 100644 index 000000000..bdddd5e80 --- /dev/null +++ b/tests/test_certs/coap_server.csr @@ -0,0 +1,7 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIHIMG8CAQAwDTELMAkGA1UEBhMCTkwwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNC +AATz4a33hLlTBl0Z5GriykNPe4p08uV748n1kZxGiGu5DUO40HLbGBU4glXheMhU +/D/bqBvUqtgh3e11B5NrArWioAAwCgYIKoZIzj0EAwIDSQAwRgIhAN3ULw0uWvw3 +ES4zuU2Nbten6F3HnjvI4r94mWdENwUJAiEA1aQoDg1fxe808Jqqzo3XXE7qpwHE +RkXKji301dsv/S8= +-----END CERTIFICATE REQUEST----- diff --git a/tests/test_certs/coap_server.pem b/tests/test_certs/coap_server.pem new file mode 100644 index 000000000..f951ccf5a --- /dev/null +++ b/tests/test_certs/coap_server.pem @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEICfZkO1MZQ+9u7en3PnZnDRIKP9TNbOJYuMGQV7knEaWoAoGCCqGSM49 +AwEHoUQDQgAE8+Gt94S5UwZdGeRq4spDT3uKdPLle+PJ9ZGcRohruQ1DuNBy2xgV +OIJV4XjIVPw/26gb1KrYId3tdQeTawK1og== +-----END EC PRIVATE KEY----- diff --git a/tests/test_certs/coap_server.pub.pem b/tests/test_certs/coap_server.pub.pem new file mode 100644 index 000000000..1417dfa18 --- /dev/null +++ b/tests/test_certs/coap_server.pub.pem @@ -0,0 +1,10 @@ +-----BEGIN CERTIFICATE----- +MIIBUDCB96ADAgECAhRHnYPbxtNBofKynpvbnDQu7vc5fTAKBggqhkjOPQQDAjAN +MQswCQYDVQQGEwJOTDAeFw0yNDAxMDMwMDIzNTNaFw0yNTAxMDIwMDIzNTNaMA0x +CzAJBgNVBAYTAk5MMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE8+Gt94S5UwZd +GeRq4spDT3uKdPLle+PJ9ZGcRohruQ1DuNBy2xgVOIJV4XjIVPw/26gb1KrYId3t +dQeTawK1oqM1MDMwEgYDVR0RBAswCYIHY29hcC5yczAdBgNVHQ4EFgQUdddVdTln +AhsNS8zjwkpXQk1YggEwCgYIKoZIzj0EAwIDSAAwRQIga8djTXXxFgXDvg9DJOlu +JiyVXNtEm8+imToYFqXIj1ECIQC2P4ujsYgU9RYgv4elTf+AZwD/lFfuFXriMhLs +F7KAyA== +-----END CERTIFICATE----- diff --git a/tests/test_certs/extfile.conf b/tests/test_certs/extfile.conf new file mode 100644 index 000000000..4c17e0589 --- /dev/null +++ b/tests/test_certs/extfile.conf @@ -0,0 +1 @@ +subjectAltName = DNS:coap.rs